Spring Security自定义认证逻辑实现图片验证码登录( 二 )


additionalAuthenticationChecks 方法用于比对密码,逻辑比较简单,就是将 password 加密后与事先保存好的密码做比对 。代码如下:
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {if (authentication.getCredentials() == null) {this.logger.debug("Failed to authenticate since no credentials provided");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));} else {String presentedPassword = authentication.getCredentials().toString();if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {this.logger.debug("Failed to authenticate since password does not match stored value");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}}}实操自定义认证我们复用之前的项目
springboot-security-inmemory,通过 postman 进行测试,不需要额外构建 html 页面 。
改动内容包括自定义 DaoAuthenticationProvider 实现类,重写
additionalAuthenticationChecks 方法,以及生成图片验证码 。
项目增加如下依赖:
<dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency>复制代码创建 VerifyService 获取验证码图片
@Servicepublic class VerifyService {public Producer getProducer() {Properties properties = new Properties();properties.setProperty("kaptcha.image.width", "150");properties.setProperty("kaptcha.image.height", "50");properties.setProperty("kaptcha.textproducer.char.string", "0123456789");properties.setProperty("kaptcha.textproducer.char.length", "4");Config config = new Config(properties);DefaultKaptcha defaultKaptcha = new DefaultKaptcha();defaultKaptcha.setConfig(config);return defaultKaptcha;}}这段配置很简单,我们就是提供了验证码图片的宽高、字符库以及生成的验证码字符长度 。
VerifyCodeController 文件中增加图片返回接口:
@RestController@Slf4jpublic class VerifyCodeController {@AutowiredVerifyService verifyService;@GetMApping("/verify-code")public void getVerifyCodePng(HttpServletRequest request, HttpServletResponse resp)throws IOException {resp.setDateHeader("Expires", 0);resp.setHeader("Cache-Control","no-store, no-cache, must-revalidate");resp.addHeader("Cache-Control", "post-check=0, pre-check=0");resp.setHeader("Pragma", "no-cache");resp.setContentType("image/jpeg");Producer producer = verifyService.getProducer();String text = producer.createText();HttpSession session = request.getSession();session.setAttribute("verify_code", text);BufferedImage image = producer.createImage(text);try (ServletOutputStream out = resp.getOutputStream()) {ImageIO.write(image, "jpg", out);}}}自定义 DaoAuthenticationProvider 实现类
public class MyAuthenticationProvider extends DaoAuthenticationProvider {@Overrideprotected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {// 验证码比对HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String code = req.getParameter("code");HttpSession session = req.getSession(false);String verify_code = (String) session.getAttribute("verify_code");if (code == null || verify_code == null || !code.equals(verify_code)) {throw new AuthenticationServiceException("验证码错误");}// 密码比对super.additionalAuthenticationChecks(userDetails, authentication);}}案例比较简单,生成验证码图片时,顺便存放到 session 中,登录验证时从 session 中获取验证码字符串,然后与传来的验证码进行比对 。
修改 SecurityConfig
@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {@BeanPasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}@Override@Beanprotected AuthenticationManager authenticationManager() throws Exception {ProviderManager manager = new ProviderManager(Arrays.asList(myAuthenticationProvider()));return manager;}@Bean@Overrideprotected UserDetailsService userDetailsService() {InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();manager.createUser(User.withUsername("hresh").password("123").roles("admin").build());return manager;}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable().authorizeRequests().antMatchers("/verify-code").permitAll().antMatchers("/code").permitAll().anyRequest().authenticated().and().formLogin().successHandler((req, resp, auth) -> {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();out.write(new ObjectMapper().writeValueAsString(Result.ok(auth.getPrincipal())));out.flush();out.close();}).failureHandler((req, resp, e) -> {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();out.write(new ObjectMapper().writeValueAsString(Result.failed(e.getMessage())));out.flush();out.close();}).permitAll();}@BeanMyAuthenticationProvider myAuthenticationProvider() {MyAuthenticationProvider myAuthenticationProvider = new MyAuthenticationProvider();myAuthenticationProvider.setPasswordEncoder(passwordEncoder());myAuthenticationProvider.setUserDetailsService(userDetailsService());return myAuthenticationProvider;}}


推荐阅读