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;}}
推荐阅读
- 什么是AOP,AOP能做什么?AOP的特点,Spring AOP的实现
- 聊聊 SpringBoot3 的 Micrometer Tracing
- SpringBoot中实现处理API接口敏感数据的脱敏
- 当我准备用SpringEvent优雅的解耦时,连续两个bug把我搞懵了
- 利用敬业签桌面便签软件实现钉钉添加自定义机器人定时提醒群成员
- 完全自定义实现SpringMVC核心组件
- 体验一下正式发布的SpringBoot3.0
- 微信上线超赞新玩法:可以自定义表情包!
- SpringBoot 使用 Feign 无废话 All-in-one 指南
- 二次封装 Spring Data JPA/MongoDB,打造更易用的数据访问层