首页 > 其他分享 >SpringSecurity-图片验证码

SpringSecurity-图片验证码

时间:2024-01-19 14:55:41浏览次数:23  
标签:int request 验证码 private SpringSecurity new public 图片

图片验证码生成

Core模块

封装验证码类

public class ImageCode {

    private BufferedImage image;

    /**

     * code是一个随机数,图片是根据随机数生成的,

     * 存放到session里面,后面用户提交登录请求时候要去验证的

     */

    private String code;

    /**

     * 过期时间

     */

    private LocalDateTime expireTime;

    public ImageCode(BufferedImage image,String code,int expireIn){

      this.image=image;

      this.code=code;

        /**

     * 过期时间传递的参数应该是一个秒数:根据这个秒数去计算过期时间
         */
        this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
    }

    public BufferedImage getImage() {
        return image;
    }

    public void setImage(BufferedImage image) {
        this.image = image;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public LocalDateTime getExpireTime() {
        return expireTime;
    }

    public void setExpireTime(LocalDateTime expireTime) {
        this.expireTime = expireTime;
    }
}

随机数生成图片

@RestController
public class ValidateCodeController {
    private static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";

    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    @GetMapping("/code/image")
    public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        /**
         * 1.根据随机数生成图片
         * 2.将随机数存到session中
         * 3.将生成图片写到接口的响应中
         */
        ImageCode imageCode = createImageCode(request);
        sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
        ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
    }

    private ImageCode createImageCode(HttpServletRequest request) {
        int width = 67;
        int height = 23;
        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);
        }

        String sRand = "";
        for (int i = 0; i < 4; i++) {
            String rand = String.valueOf(random.nextInt(10));
            sRand += 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, 60);
    }

    /**
     * 生成随机背景条纹
     *
     * @param fc
     * @param bc
     * @return
     */
    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);
    }
}

前端获取验证码

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="/authentication/form" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td>图形验证码</td>
            <td>
                <input type="text" name="imageCode">
                <img src="/code/image"> 
            </td>
        </tr>
        <tr>
            <td colspan="2"><button type="submit">登录</button></td>
        </tr>
    </table>
</form>
</body>
</html>

授权模块添加:允许验证码生成请求permitAll()

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    @Bean
    public PasswordEncoder  passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    /**
     * 定义web安全配置类:覆盖config方法
     * 1.参数为HttpSecurity
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /**
         * 定义了任何请求都需要表单认证
         */
       http.formLogin()//表单登录---指定了身份认证方式
          // .loginPage("/login.html")
           .loginPage("/authentication/require")
           .loginProcessingUrl("/authentication/form")//配置UsernamePasswordAuthenticationFilter需要拦截的请求
           .successHandler(myAuthenticationSuccessHandler)//表单登录成功之后用自带的处理器
           .failureHandler(myAuthenticationFailureHandler)//表单登录失败之后用自带的处理器
       // http.httpBasic()//http的basic登录
          .and()
          .authorizeRequests()//对请求进行授权
          .antMatchers("/authentication/require",securityProperties.getBrowser().getLoginPage(),"/code/image").permitAll()//对匹配login.html的请求允许访问
          .anyRequest()//任何请求
          .authenticated()
           .and()
           .csrf().disable();//都需要认证
    }
}

 

图片验证码认证

实现登录请求时候 都是在实现Spring提供的接口;并且加密解密实现都是Spring已经提供的,但是spring并没有提供图形验证码,因为spring security他的基本原理就是一个过滤器链。在这个链上可以加入自己写的过滤器。在UsernamePasswordAuthticationFilter前加一个自定义的过滤器。 extends OncePerRequestFilter。实现:doFilterInternal方法。

校验验证码

创建校验验证码过滤器

(1)继承OncePerRequestFilter,由spring提供,它能保证过滤器只被调用一次。

(2)重写doFilterInternal方法,校验验证码逻辑

(3)定义校验验证码异常

AuthenticationException , security定义的抽象异常,是所有身份认证抛出异常的基类

  1. 校验验证码过滤器加入security过滤器链,并在UsernamePasswordAuthenticationFilter之前

验证码过滤器

public class ValidateCodeFilter extends OncePerRequestFilter {
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private SessionStrategy sessionStrategy;

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        logger.info("验证码过滤器:doFilterInternal: requestURI:[{}]  requestMethod:[{}]",request.getRequestURI(),request.getMethod());
        /**
         * 如果是需要认证请求,我们进行家宴
         * 如果校验失败,使用我们自定义的校验失败处理类处理
         * 如果不需要认证,我们放行进入下一个Filter
         */
        if(StringUtils.equals("/authentication/form",request.getRequestURI()) && StringUtils.endsWithIgnoreCase(request.getMethod(),"post")){
           try{
               validate(new ServletWebRequest(request));
           }catch (ValidateCodeException e){
               authenticationFailureHandler.onAuthenticationFailure(request,response,e);
return;
           }
        }
        filterChain.doFilter(request,response);
    }

    private void validate(ServletWebRequest servletWebRequest) throws ServletRequestBindingException {
           //1.获取存放到session中的验证码
        ImageCode codeInSession = (ImageCode)sessionStrategy.getAttribute(servletWebRequest, ValidateCodeController.SESSION_KEY);
           //2.获取请求中的验证码
        String codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "imageCode");
          if(StringUtils.isBlank(codeInRequest)){
              throw new ValidateCodeException("验证码的值不能为空");
          }

          if(codeInSession == null){
              throw new ValidateCodeException("验证码不存在")
          }

          if(codeInSession.isExpried()){
              sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeController.SESSION_KEY);
              throw new ValidateCodeException("验证码已过期");
          }

          if(StringUtils.equals(codeInSession.getCode(),codeInRequest)){
              throw new ValidateCodeException("验证码不匹配")
          }

          sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeController.SESSION_KEY);
    }
}

自定义验证码校验异常ValidateCodeException

public class ValidateCodeException extends AuthenticationException {//security定义的抽象异常,是所有身份认证抛出异常的基类
    public ValidateCodeException(String msg, Throwable t) {
        super(msg, t);
    }

    public ValidateCodeException(String msg) {
        super(msg);
    }
}

 

校验验证码过滤器加入security过滤器链,并在UsernamePasswordAuthenticationFilter之前

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    @Bean
    public PasswordEncoder  passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    /**
     * 定义web安全配置类:覆盖config方法
     * 1.参数为HttpSecurity
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /**
         * 定义了任何请求都需要表单认证
         */
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        validateCodeFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);

        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
               .formLogin()//表单登录---指定了身份认证方式
          // .loginPage("/login.html")
           .loginPage("/authentication/require")
           .loginProcessingUrl("/authentication/form")//配置UsernamePasswordAuthenticationFilter需要拦截的请求
           .successHandler(myAuthenticationSuccessHandler)//表单登录成功之后用自带的处理器
           .failureHandler(myAuthenticationFailureHandler)//表单登录失败之后用自带的处理器
       // http.httpBasic()//http的basic登录
          .and()
          .authorizeRequests()//对请求进行授权
          .antMatchers("/authentication/require",securityProperties.getBrowser().getLoginPage(),"/code/image").permitAll()//对匹配login.html的请求允许访问
          .anyRequest()//任何请求
          .authenticated()
           .and()
           .csrf().disable();//都需要认证
    }
}

自定义错误处理器

@Slf4j
@Component
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Autowired
    private SecurityProperties securityProperties;
    //@Resource
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        log.info("【登录失败】");
        HashMap<String, Object> map = new HashMap<>();
        if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){ //JSON
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setContentType("application/json;charset=UTF-8");
            map.put("msg",exception.getMessage());
            response.getWriter().write(objectMapper.writeValueAsString(ResponseEntity.status(500).body(map)));
        }else{
            super.onAuthenticationFailure(request,response,exception);
        }
    }
}

升级重构

在以上基础上升级重构图形验证码主要是下面3个方面:

  1. 验证码基本参数可配置
  2. 验证码拦截的接口可配置
  3. 验证码的生成逻辑可配置

验证码基本参数可配置

  1. 验证码长宽可配置
  2. 验证码位数可配置
  3. 验证码有效时间可配置

IMG_256

多级覆盖

默认配置

将验证码长宽可配置、验证码位数可配置、验证码有效时间可配置作为属性参数写到配置中;

ImageCodeProperties:
public class ImageCodeProperties {
    private int width = 67;
    private int height = 23;
    private int length = 4;
    private int expireIn = 60;
    //getter setter
}

配置成多个类级别配置:ValidateCodeProperties;

public class ValidateCodeProperties {
    private ImageCodeProperties image = new ImageCodeProperties();

    public ImageCodeProperties getImage() {
        return image;
    }
    public void setImage(ImageCodeProperties image) {
        this.image = image;
    }
}

封装到主配置中

//此类读取配置文件里所有以yxm.security开头的配置
@ConfigurationProperties(prefix = "yxm.security")
public class SecurityProperties {
    //其中yxm.security.browser开头的配置否会读取到BrowserProperties中
    private BrowserProperties browser = new BrowserProperties();

    private ValidateCodeProperties code = new ValidateCodeProperties();
    //getter setter
}

应用级配置

在应用模块中的配置文件中设置,覆盖默认配置

yxm.security.code.image.length=6 //默认配置是4

请求级配置

public ImageCode generate(ServletWebRequest request) {
        //从request前端获取不到值 就用默认配置,能获取到值就用前端传递过来的配置
        int width = ServletRequestUtils.getIntParameter(request.getRequest(),"width",securityProperties.getCode().getImage().getWidth());
        int height = ServletRequestUtils.getIntParameter(request.getRequest(),"height",securityProperties.getCode().getImage().getHeight());

        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);
        }

        String sRand = "";
        for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++) {
            String rand = String.valueOf(random.nextInt(10));
            sRand += 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, securityProperties.getCode().getImage().getExpireIn());
}

页面请求参数设置为:200,应用demo配置中改为100;

tr>
            <td>图形验证码</td>
            <td>
                <input type="text" name="imageCode">
                <img src="/code/image?width=200">
            </td>
        </tr>

yxm.security.code.image.width=100

启动应用,发现:我们请求的url中width=200的值会覆盖掉:spring-security-demo里面的width=100的值;spring-security-demo里面的length=6的值覆盖掉系统默认的4。

验证码拦截的接口可配

ValidateCodeFilter过滤器的doFilterInternal方法只拦截/authentication/form接口,通过配置可拦截多个接口校验验证码

  1. ImageCodeProperties新增一个属性:private String url; //拦截的url

IMG_256

  2. application.yml

# security配置
wzl:
  security:
    browser:
      loginPage: "/signIn.html" #当前模块resources/static/
#      loginType: "REDIRECT"
    code:
      image:
        length: 5
        width: 100
        url: "/user,/user/**" #验证码校验的接口

  3. ValidateCodeFilter验证码拦截器

    1)在拦截器里声明一个set集合,用来存储配置文件里配置的需要拦截的urls。

    2)实现InitializingBean接口,目的: 在其他参数都组装完毕的时候,初始化需要拦截的urls的值,重写afterPropertiesSet方法来实现。

    3)注入SecurityProperties,读取配置文件

    4)实例化AntPathMatcher工具类,这是一个匹配器

    5)在browser项目的BrowserSecurityConfig里设置调用一下afterPropertiesSet方法。

    6)在引用该模块的demo项目的application.properties里配置要过滤的url

ValidateCodeFilter

/**
 * 验证码过滤器
 * @date 2020/2/24
 **/
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {

    private AuthenticationFailureHandler authenticationFailureHandler;

    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    private Logger logger = LoggerFactory.getLogger(getClass());
    //需要校验的url都在这里面添加
    private Set<String> urls = new HashSet<>();

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    //此处不用注解@Autowire 而是使用setter方法将在WebSecurityConfig设置
    private SecurityProperties securityProperties;

    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();

        //我们获取配置的ImageCodeProperties里面的url,转化为数据,添加到urls里面去
        String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrl(), ",");
        for (String configUrl:configUrls) {
            urls.add(configUrl);
        }
        //"/authentication/form"一定会校验验证码的
        urls.add("/authentication/form");

    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        logger.info("验证码过滤器:doFilterInternal: requestURI:[{}]  requestMethod:[{}]",request.getRequestURI(),request.getMethod());
        /**
         * 如果是需要认证请求,我们进行家宴
         * 如果校验失败,使用我们自定义的校验失败处理类处理
         * 如果不需要认证,我们放行进入下一个Filter
         */

        //在afterPropertiesSet执行之后,url初始化完毕之后,但是此时我们判断不能用StringUtils.equals,我们我们urls里面有 url: /user,/user/* 带星号的配置
        // 用户请求有可能是/user/1、/user/2  我们需要使用Spring的 AntPathMatcher
        boolean action = false;
        for (String url:urls) {
            //如果配置的url和请求的url相同时候,需要校验
            if(antPathMatcher.match(url,request.getRequestURI())){
                action = true;
            }
        }
        if(action){
            try{
                validate(new ServletWebRequest(request));
            }catch (ValidateCodeException e){
                authenticationFailureHandler.onAuthenticationFailure(request,response,e);
                //抛出异常校验失败,不再走小面过滤器执行链
                return;
            }
        }
        filterChain.doFilter(request,response);
    }

    private void validate(ServletWebRequest servletWebRequest) throws ServletRequestBindingException {
        //1.获取存放到session中的验证码
        ImageCode codeInSession = (ImageCode)sessionStrategy.getAttribute(servletWebRequest, ValidateCodeController.SESSION_KEY);
        //2.获取请求中的验证码
        String codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "imageCode");
        if(StringUtils.isBlank(codeInRequest)){
            throw new ValidateCodeException("验证码的值不能为空");
        }

        if(codeInSession == null){
            throw new ValidateCodeException("验证码不存在");
        }

        if(codeInSession.isExpried()){
            sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeController.SESSION_KEY);
            throw new ValidateCodeException("验证码已过期");
        }

        if(!StringUtils.equals(codeInSession.getCode(),codeInRequest)){
            throw new ValidateCodeException("验证码不匹配");
        }

        sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeController.SESSION_KEY);
    }

    public AuthenticationFailureHandler getAuthenticationFailureHandler() {
        return authenticationFailureHandler;
    }

    public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
        this.authenticationFailureHandler = authenticationFailureHandler;
    }

    public SecurityProperties getSecurityProperties() {
        return securityProperties;
    }

    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }
}

WebSecurityConfig

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    @Bean
    public PasswordEncoder  passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    /**
     * 定义web安全配置类:覆盖config方法
     * 1.参数为HttpSecurity
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 图片验证码过滤器
            //创建验证码过滤器,验证器使用自定义错误处理
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter(myAuthenticationFailureHandler);
        validateCodeFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
         //配置验证码过滤url
        validateCodeFilter.setSecurityProperties(securityProperties);
        validateCodeFilter.afterPropertiesSet();

        /**
         * 定义了任何请求都需要表单认证
         */
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        validateCodeFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);

        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
               .formLogin()//表单登录---指定了身份认证方式
          // .loginPage("/login.html")
           .loginPage("/authentication/require")
           .loginProcessingUrl("/authentication/form")//配置UsernamePasswordAuthenticationFilter需要拦截的请求
           .successHandler(myAuthenticationSuccessHandler)//表单登录成功之后用自带的处理器
           .failureHandler(myAuthenticationFailureHandler)//表单登录失败之后用自带的处理器
       // http.httpBasic()//http的basic登录
          .and()
          .authorizeRequests()//对请求进行授权
          .antMatchers("/authentication/require",securityProperties.getBrowser().getLoginPage(),"/code/image").permitAll()//对匹配login.html的请求允许访问
          .anyRequest()//任何请求
          .authenticated()
           .and()
           .csrf().disable();//都需要认证
    }
}

配置url

yxm.security.code.image.length=6

yxm.security.code.image.width=100

yxm.security.code.image.url=/user,/user/*

验证码的生成逻辑可配置

生成验证码逻辑改为接口,有默认实现类,其它模块(demo)实现了该接口,生成验证码就使用模块生的产逻辑。

  1. 验证码生成提取成一个接口ValidateCodeGenerator,一个生成验证码的方法generator()
  2. 实现类ImageCodeGenerator,不加@Component注解
  3. 验证码bean的配置类ValidateCodeBeanConfig,使用@ConditionalOnMissingBean(name="imageCodeGenerator")注解,如果spring容器没有则使用默认的验证码生成器
  4. demo模块实现ValidateCodeGenerator(@Component("imageCodeGenerator")),替换默认验证码生成器

声明接口

声明一个接口(校验码生成器):ValidateCodeGenerator;将Controller里面生成的方法挪动到接口里面去。

public interface ValidateCodeGenerator {
    /**
     * 生成验证码
     * @param request
     * @return
     */
    ImageCode generate(ServletWebRequest request);
}

 

实现类:ImageCodeGenerator

@Service
public class ImageCodeGenerator implements ValidateCodeGenerator {

    private SecurityProperties securityProperties;

    @Override
    public ImageCode generate(ServletWebRequest request) {
        //从request前端获取不到值 就用默认配置,能获取到值就用前端传递过来的配置
        int width = ServletRequestUtils.getIntParameter(request.getRequest(),"width",securityProperties.getCode().getImage().getWidth());
        int height = ServletRequestUtils.getIntParameter(request.getRequest(),"height",securityProperties.getCode().getImage().getHeight());

        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);
        }

        String sRand = "";
        for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++) {
            String rand = String.valueOf(random.nextInt(10));
            sRand += 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, securityProperties.getCode().getImage().getExpireIn());
    }


    /**
     * 生成随机背景条纹
     * @param fc
     * @param bc
     * @return
     */
    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);
    }

    public SecurityProperties getSecurityProperties() {
        return securityProperties;
    }

    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }
}

 

修改生成逻辑

ValidateCodeGenerator里面的实现逻辑注入到ValidateCodeController里面去

@RestController
public class ValidateCodeController {
    public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";

    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    @Autowired
    private ValidateCodeGenerator imageCodeGenerator;


    @GetMapping("/code/image")
    public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        /**
         * 1.根据随机数生成图片
         * 2.将随机数存到session中
         * 3.将生成图片写到接口的响应中
         */
        ImageCode imageCode = imageCodeGenerator.generate(new ServletWebRequest(request));
        sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
        ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
    }
}

 

验证码bean的配置类ValidateCodeBeanConfig

@Configuration
public class ValidateCodeBeanConfig {
    @Autowired
    private SecurityProperties securityProperties;

    /*
    * 这个配置与我们在ImageCodeGenerator上面加一个注解是类似的,但是这样配置灵活,
    * 可以添加注解:@ConditionalOnMissingBean 作用是:在初始化这个bean的时候,
    * 先到spring容器去查找imageCodeGenerator,如果有一个imageCodeGenerator时候,
    * 就不会再用下面代码去创建
    **/
@Bean @ConditionalOnMissingBean(name="imageCodeGenerator")
    public ValidateCodeGenerator imageCodeGenerator(){//方法的名字就是放到Spring容器里bean的名字
        ImageCodeGenerator imageCodeGenerator = new ImageCodeGenerator();
        imageCodeGenerator.setSecurityProperties(securityProperties);
        return imageCodeGenerator;
    }
}

 

应用层实现ValidateCodeGenerator

在spring-security-demo里面添加一个:DemoImageCodeGenrator,去实现ValidateCodeGenerator;Bean的名字和ValidateCodeBeanConfig里面配置的一直,这样的话在spring-security-web和spring-security-demo里面都有。

@Component("imageCodeGenrator")
public class DemoImageCodeGenrator implements ValidateCodeGenerator {
    @Override
    public ImageCode generate(ServletWebRequest request) {
        System.out.println("更高级的图形验证码逻辑");
        return null;
    }
}

 

标签:int,request,验证码,private,SpringSecurity,new,public,图片
From: https://www.cnblogs.com/wangzhilei-src/p/17974560

相关文章

  • 若依框架入门一源码分析一登录验证码
    若依框架入门一源码分析一关于登录页面的验证码问题前端页面的验证码开关设置的是true,但是打开画面验证码没有被显示,原因是后端代码判断了redis中是否有值,有则覆盖前端<el-form-itemprop="code"v-if="captchaEnabled"><el-inputv-model="loginForm......
  • rosbag 包提取图片和点云数据
    环境Ubuntu20.04ROSnoeticPython3.8.10⚠️注意:Python版本很重要,建议用3.8.10版本,如果使用更新的版本,会导致程序需要的库版本不对应,会报错。建议使用conda创建一个虚拟环境最佳,创建指令:condacreate-nros_envpython=3.8.10。安装所需的包在创建的Python3.8.......
  • fastapi接收图片文件
    #api接口,主函数importbase64importioimportcv2importosimporttimefromPILimportImageimportnumpyasnpfromfastapiimportFastAPI,HTTPExceptionimportuvicornapp=FastAPI()##############################################@app.post("/upload......
  • 上传图片,必填增加校验
    <el-form-itemlabel="产品照片:"class="product-manual-box":prop="`infoTabs[${index}].productfmId`":rules="{required:true,message:'请上传产品照片',trigger:'change'}">......
  • 根据后端id去请求拿取图片组件
    1.组件<template><img :src="srcImg?srcImg:defaultImage?getDefaultImage:''" v-bind="{...otheAttribute}" alt=""/></template><script>import{getFileInfoApi}from'@/a......
  • Python_python读写图片以及对应的库比较
    图片读写通过numpy来做数据计算的沟通JPEG是一种有损格式, 图像PNG,是一种无损格式cv2.imdecode()作用是将图像数据从存储格式中解析出来并转化为OpenCV中的图像格式 imdecode得到的影像波段顺序是RGBnp.fromfile将文本或二进制文件中数据构造成数组 cv2.imencod......
  • RTSP流截图并剔除花屏图片
    大致代码如下:importcv2importnumpyasnpfromfastapiimportHTTPExceptionRgbRangeType=tuple[tuple[int,int,int],tuple[int,int,int]]classValidationError(HTTPException):def__init__(self,detail:str,status_code=400)->None:supe......
  • C# 正则获取网页图片地址
    stringFaPiaoUrl="";stringFaPiaoImageUrl="";stringHTMLSTR=NetTools.GetR......
  • SpringSecurity-认证流程源码级详解
    自定义用户认证逻辑处理用户信息获取逻辑:UserDetailsService处理用户校验逻辑:UserDetails处理密码加密解密:PasswordEncoder认证处理流程以表单认证为例:从发起认证请求到认证过滤器,接着认证成功后,响应从认证过滤器返回的整个过程。SpringSecurity做了什么,设计到了哪些类?他......
  • Nginx 限制url访问图片资源文件
    nginx配置 location^~/uploads/{//图片文件路径valid_referersblockedwww.baidu.com;//允许访问的域名if($invalid_referer){#rewrite^/http://www.hugwww.com/daolian.gif;retur......