• Spring Security自定义验证码登录


    本文内容来自王松老师的《深入浅出Spring Security》,自己在学习的时候为了加深理解顺手抄录的,有时候还会写一些自己的想法。

            验证码登录也是项目中一个常见的需求,但是Spring Security并未提供自动化配置方案。所以需要开发者自行定义。这里我们通过自定义认证逻辑实现添加登录验证码功能。

            生成验证码我们使用开源库kaptcha,首先引入kaptcha依赖,代码如下:

    1. com.github.penggle
    2. kaptcha
    3. 2.3.2

            然后对kaptcha进行配置:

    1. @Configuration
    2. public class KaptchaConfig {
    3. @Bean
    4. public Producer kaptcha() {
    5. Properties properties = new Properties();
    6. properties.setProperty("kaptcha.image.width", "150");
    7. properties.setProperty("kaptcha.image.height", "50");
    8. properties.setProperty("kaptcha.textproducer.char.string", "0123456789");
    9. properties.setProperty("kaptcha.textproducer.char.length", "4");
    10. Config config = new Config(properties);
    11. DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
    12. defaultKaptcha.setConfig(config);
    13. return defaultKaptcha;
    14. }
    15. }

            配置一个Producer实例,只要配置一个生成的图片验证码的宽带、长度、生成字符、验证码的长度等信息。配置完成可以在Controller中定义一个验证码接口了:

    1. @Autowired
    2. private Producer producer;
    3. @GetMapping("/vc.jpg")
    4. public void getVerifyCode(HttpServletResponse resp, HttpSession session) {
    5. resp.setContentType("image/jpeg");
    6. String text = producer.createText();
    7. session.setAttribute("kaptcha", text);
    8. BufferedImage image = producer.createImage(text);
    9. try (ServletOutputStream out = resp.getOutputStream()) {
    10. ImageIO.write(image, "jpg", out);
    11. } catch (Exception e) {
    12. e.printStackTrace();
    13. }
    14. }

            这个验证码接口中,主要做了两件事:

    • 生成验证码,并将文本存入HttpSession中
    • 根据验证码文本生成验证码图片,并通过IO流写出到前端。

            接下修改登录表单,加入验证码,代码如下:

    1. "en">
    2. "UTF-8">
    3. 自定义登录
    4. "//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
    5. "login">
    6. "container">
    7. "login-row" class="row justify-content-center align-items-center">
    8. "login-column" class="col-md-6">
    9. "login-box" class="col-md-12">
    10. "login-form" class="form" action="/doLogin" method="post">
    11. "text-center text-info">登录

    12. "form-group">

    13. "text" name="uname" id="username" class="form-control">
  • "form-group">

  • "text" name="passwd" id="password" class="form-control">
  • "form-group">

  • "text" name="kaptcha" id="kaptcha" class="form-control">
  • "/vc.jpg" alt="">
  • "form-group">
  • "submit" name="submit" class="btn btn-info btn-md" value="登录">
  •         在登录表单中增加一个输入验证码的输入框,验证码的图片地址就是我们在Controller中定义的验证码接口地址。

            接来下就是验证码的校验了。经过前面的学习,我们已经了解到身份认证实际上就是在AuthenticationProvider的authenticate方法中完成的。所以,验证码的校验,我们可以在该方法执行前进行,需要配置如下类:

    1. /**
    2. * @author tlh
    3. * @date 2022/11/18 22:14
    4. */
    5. public class KaptchaAuthenticationProvider extends DaoAuthenticationProvider {
    6. @Override
    7. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    8. HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    9. String kaptcha = req.getParameter("kaptcha");
    10. String sessionKaptcha = (String) req.getSession().getAttribute("kaptcha");
    11. if (kaptcha != null && kaptcha.equalsIgnoreCase(sessionKaptcha)) {
    12. return super.authenticate(authentication);
    13. }
    14. throw new AuthenticationServiceException("验证码输入错误");
    15. }
    16. }

            这里重写authenticate方法,在authenticate方法中从RequestContextHolder获取当前请求,进而取到验证码参数和存储在HttpSession中的验证码文本进行对比。比如通过的话就执行父类的authenticate方法,不通过的话就抛出异常。

            我们将我们定义的KaptchaAuthenticationProvider通过配置类放入Spring的IOC容器中去:

    1. /**
    2. * @author tlh
    3. * @date 2022/11/18 21:22
    4. */
    5. @Configuration
    6. public class KaptchaConfig {
    7. @Autowired
    8. private UserDetailsService userDetailsService;
    9. @Bean
    10. public AuthenticationProvider getAuthenticationProvider() {
    11. KaptchaAuthenticationProvider kaptchaAuthenticationProvider = new KaptchaAuthenticationProvider();
    12. kaptchaAuthenticationProvider.setUserDetailsService(userDetailsService);
    13. return kaptchaAuthenticationProvider;
    14. }
    15. }

            这里需要注意的是我们在创建AuthenticationProvider的是需要一个UserDetailsService,这里的UserDetailsService是我们在前面学习的通过MyBatis从数据库中加载用户信息的时候定义的,如果有疑惑的的伙伴可以在去回顾下。

    1. /**
    2. * @author tlh
    3. * @date 2022/11/17 23:52
    4. */
    5. @Component
    6. public class MyUserDetailsService implements UserDetailsService {
    7. @Autowired
    8. private UserMapper userMapper;
    9. @Override
    10. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    11. User user = userMapper.loadUserByUsername(username);
    12. if (user == null) {
    13. throw new UsernameNotFoundException("用户不存在");
    14. }
    15. user.setRoles(userMapper.getRolesByUid(user.getId()));
    16. return user;
    17. }
    18. }

            最后在SecurityConfig中配置AuthenticationManager,代码如下:

    1. /**
    2. * @author tlh
    3. * @date 2022/11/16 21:11
    4. */
    5. @Configuration
    6. public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    7. @Autowired
    8. private AuthenticationProvider authenticationProvider;
    9. @Override
    10. protected void configure(HttpSecurity http) throws Exception {
    11. http.authorizeRequests()
    12. //放行验证码生成接口
    13. .antMatchers("/vc.jpg").permitAll()
    14. .anyRequest().authenticated()
    15. .and()
    16. .formLogin()
    17. .loginPage("/login.html")
    18. .loginProcessingUrl("/doLogin")
    19. .successHandler(getAuthenticationSuccessHandler())
    20. .failureUrl("/login.html")
    21. .usernameParameter("uname")
    22. .passwordParameter("passwd")
    23. .permitAll()
    24. .and()
    25. .csrf().disable();
    26. }
    27. @Bean
    28. @Override
    29. public AuthenticationManager authenticationManagerBean() throws Exception {
    30. ProviderManager providerManager = new ProviderManager(authenticationProvider);
    31. return providerManager;
    32. }
    33. /**
    34. * 登录成功处理器
    35. *
    36. * @return
    37. */
    38. private AuthenticationSuccessHandler getAuthenticationSuccessHandler() {
    39. return new AuthenticationSuccessHandler() {
    40. @Override
    41. public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    42. response.setContentType("application/json;charset=utf-8");
    43. Map respMap = new HashMap<>(2);
    44. respMap.put("code", "200");
    45. respMap.put("msg", "登录成功");
    46. ObjectMapper objectMapper = new ObjectMapper();
    47. String jsonStr = objectMapper.writeValueAsString(respMap);
    48. response.getWriter().write(jsonStr);
    49. }
    50. };
    51. }
    52. }

            另外需要注意,在configure(HttpSecurity)方法中给验证码接口配置放行:antMatchers("/vc.jpg").permitAll()。配置完成后,启动项目,浏览中输入:localhost:8080/login.html,就能看到如下图:

            此时,输入正确的用户名、密码以及验证码就可以成功登录。 

  • 相关阅读:
    SpringBoot整合task实现定时任务、@EnableScheduling、@Scheduled
    说话人识别声纹识别CAM++,ECAPA-TDNN等算法
    一文学会Amazon transit GateWay
    【笔记】文献阅读[YOLOV3]-An Incremental Improvement
    计算机网络基础一
    SPI协议讲解与总结
    nvidia nx onnx转trt模型报错
    SAP的MIGO移动类型
    Python解决图文验证码登录识别(1)
    Java stream sorted使用 Comparator 进行多字段排序
  • 原文地址:https://blog.csdn.net/qq_27062249/article/details/127942151