spring security 图片验证码和记住我

  • 图像生成
  • 前端获取
  • 提交验证,在密码验证前进行验证
  • 判断验证码是否正确 图片验证码对象
  • <code> @Data
    public class ImageCode {
       private BufferedImage image;
       private String code;
       private LocalDateTime expireTime;
       public ImageCode() {
      }
       public ImageCode(BufferedImage image, String code, int expireIn) {
           this.image = image;
           this.code = code;
           this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
      }
       public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
           this.image = image;
           this.code = code;
           this.expireTime = expireTime;
      }
       public boolean isExpire() {
           return LocalDateTime.now().isAfter(expireTime);
      }
    }/<code>

    生成图像

    <code>        /***
           **图像生成
           */
       private ImageCode createImageCode() {
           int width = 100; // 验证码图片宽度
           int height = 36; // 验证码图片长度
           int length = 4; // 验证码位数
           int expireIn = 120; // 验证码有效时间 120s

           BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
           Graphics g = image.getGraphics();

           Random random = new Random();

           g.setColor(getRandColor(200, 250));
           g.fillRect(0, 0, width, height);
           g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
           g.setColor(getRandColor(160, 200));
           for (int i = 0; i < 155; i++) {
               int x = random.nextInt(width);
               int y = random.nextInt(height);
               int xl = random.nextInt(12);
               int yl = random.nextInt(12);
               g.drawLine(x, y, x + xl, y + yl);
          }

           StringBuilder sRand = new StringBuilder();
           for (int i = 0; i < length; i++) {
               String rand = String.valueOf(random.nextInt(10));
               sRand.append(rand);
               g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
               g.drawString(rand, 13 * i + 6, 16);
          }
           g.dispose();
           return new ImageCode(image, sRand.toString(), expireIn);
      }

       private Color getRandColor(int fc, int bc) {
           Random random = new Random();
           if (fc > 255) {
               fc = 255;
          }
           if (bc > 255) {
               bc = 255;
          }
           int r = fc + random.nextInt(bc - fc);
           int g = fc + random.nextInt(bc - fc);
           int b = fc + random.nextInt(bc - fc);
           return new Color(r, g, b);
      }/<code>

    接口:

    <code>@GetMapping("/code/image")
       public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
           ImageCode imageCode = createImageCode();
           //存放到redis或session中
           ImageIO.write(imageCode.getImage(), "jpeg", response.getOutputStream());
      }/<code>

    界面获取

    <code> 
               
               

           /<code>

    校验验证码

    <code>/**
    * @program: security_root
    * @description: 验证码校验类
    * @author: xs-shuai.com
    * @create: 2020-02-27 14:19
    **/
    @Configuration
    public class ValidateCodeFilter extends OncePerRequestFilter {

       /****
        * 失败处理
        */
       @Autowired
       private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

      //注入redis 或session


       /***
        * 处理验证码校验是否正确
        * @param httpServletRequest
        * @param httpServletResponse
        * @param filterChain

        */
       @Override
       protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
           if (StringUtils.equalsIgnoreCase("/login", httpServletRequest.getRequestURI())
                   && StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(), "post")) {
               try {
                   validateCode(new ServletWebRequest(httpServletRequest));
              } catch (ValidateCodeException e) {
                   myAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
                   return;
              }
          }
           filterChain.doFilter(httpServletRequest, httpServletResponse);
      }

           /**
           **校验验证码 抛出异常 验证码错误 或验证码为空

           */
       private void validateCode(ServletWebRequest servletWebRequest) throws ServletRequestBindingException {
          // ImageCode codeInSession = 获取到请求时放入的验证码
           String codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "imageCode");

           if (StringUtils.isBlank(codeInRequest)) {
               throw new ValidateCodeException("验证码不能为空!");
          }
           if (codeInSession == null) {
               throw new ValidateCodeException("验证码不存在!");
          }
           if (codeInSession.isExpire()) {
              //认证成功删除保存的验证码对象
               throw new ValidateCodeException("验证码已过期!");
          }
           if (!StringUtils.equalsIgnoreCase(codeInSession.getCode(), codeInRequest)) {
               throw new ValidateCodeException("验证码不正确!");
          }
           sessionStrategy.removeAttribute(servletWebRequest, ValidateController.SESSION_KEY_IMAGE_CODE);

      }
    }
    ​/<code>

    修改配置

    <code>@Configuration
    public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
       //认证成功的handler 跳转首页或提示页面后后跳转
       @Autowired
       private MyAuthenticationSuccessHandler authenticationSuccessHandler;
       //认证失败的handler 提示失败信息
       @Autowired
       private MyAuthenticationFailureHandler authenticationFailureHandler;

       //验证码验证的filter
       @Autowired
       private ValidateCodeFilter validateCodeFilter;

       @Override
       protected void configure(HttpSecurity http) throws Exception {
           //密码验证前加入
           http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                  .formLogin() // 表单登录
                   // http.httpBasic() // HTTP Basic
                  .loginPage("/login.html") // 登录跳转 URL
                  .loginProcessingUrl("/login") // 处理表单登录 URL
                  .successHandler(authenticationSuccessHandler) // 处理登录成功
                  .failureHandler(authenticationFailureHandler) // 处理登录失败
                  .and()
                  .authorizeRequests() // 授权配置
                  .antMatchers("/authentication/require","/code/image", "/login.html").permitAll() // 登录跳转 URL 无需认证
                  .anyRequest()  // 所有请求
                  .authenticated() // 都需要认证
                  .and().csrf().disable();
      }

       @Bean
       public PasswordEncoder passwordEncoder() {
           return new BCryptPasswordEncoder();
      }
       
    }
    ​/<code>

    记住我

    配置数据源

    <code>//数据源配置
    spring:
    datasource:
      url: jdbc:mysql://192.168.0.109:3307/security_learn?useUnicode=yes&characterEncoding=UTF-8&useSSL=false
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver
    ​/<code>

    持久化到数据源 创建表结构

    <code>CREATE TABLE persistent_logins (
      username VARCHAR (64) NOT NULL,
      series VARCHAR (64) PRIMARY KEY,
      token VARCHAR (64) NOT NULL,
      last_used TIMESTAMP NOT NULL
    )/<code>

    表单实现 注意:name一定是remeber-me

    <code> 记住我/<code>

    配置类

    <code>@Configuration
    public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
       @Autowired
       private MyAuthenticationSucessHandler authenticationSucessHandler;

       @Autowired
       private MyAuthenticationFailureHandler authenticationFailureHandler;

       @Autowired
       private ValidateCodeFilter validateCodeFilter;

       @Autowired
       private DataSource dataSource;
       @Autowired
       private UserDetailService userDetailService;


       /**
       PersistentTokenRepository为一个接口类,
       这里我们用的是数据库持久化,所以实例用的是PersistentTokenRepository的实现类JdbcTokenRepositoryImpl。
       JdbcTokenRepositoryImpl需要指定数据源,所以我们将配置好的数据源对象DataSource
        注入进来并配置到JdbcTokenRepositoryImpl的dataSource属性中。
       createTableOnStartup属性用于是否启动项目时创建保存token信息的数据表,这里设置为false,我们自己手动创建

        */
       @Bean
       public PersistentTokenRepository persistentTokenRepository() {
           JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
           jdbcTokenRepository.setDataSource(dataSource);
           jdbcTokenRepository.setCreateTableOnStartup(false);
           return jdbcTokenRepository;
      }

       @Override
       protected void configure(HttpSecurity http) throws Exception {
           //密码验证前加入
           http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                  .formLogin() // 表单登录
                   // http.httpBasic() // HTTP Basic
                  .loginPage("/login.html") // 登录跳转 URL
                  .loginProcessingUrl("/login") // 处理表单登录 URL
                  .successHandler(authenticationSucessHandler) // 处理登录成功
                  .failureHandler(authenticationFailureHandler) // 处理登录失败
                  .and()
                   //>>>>>>>>记住我的配置<<<<
                  .rememberMe()
                  .tokenRepository(persistentTokenRepository()) // 配置 token 持久化仓库
                  .tokenValiditySeconds(3600) // remember 过期时间,单为秒
                  .userDetailsService(userDetailService) // 处理自动登录逻辑
                  .and()
                  .authorizeRequests() // 授权配置
                  .antMatchers("/authentication/require","/code/image", "/login.html").permitAll() // 登录跳转 URL 无需认证
                  .anyRequest()  // 所有请求
                  .authenticated() // 都需要认证
                  .and().csrf().disable();
      }

       @Bean
       public PasswordEncoder passwordEncoder() {
           return new BCryptPasswordEncoder();
      }
    }
    ​/<code>



    分享到:


    相關文章: