【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)

前言

本篇博客主要分享:登录模块中基于图片验证码的一些骚操作。


实现图形验证码功能:

1.开发生成图形验证码接口

  • 根据随机数生成图片
package com.zcw.security.core.validate.code;

import lombok.Data;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.time.LocalDateTime;

/**
 * @ClassName : ImageCode
 * @Description :生成图片验证码
 * @Author : Zhaocunwei
 * @Date: 2020-06-22 13:24
 */
@Data
public class ImageCode {
    /**
     * 图片根据随机数生成
     */
    private BufferedImage image;
    /**
     * 随机数,一般存到session中,
     * 用户登录时需要进行校验
     */
    private String code;
    /**
     * 时间:设置过期时间
     */
    private LocalDateTime expireTime;

    public ImageCode(BufferedImage image,String code,LocalDateTime expireTime){
        this.image= image;
        this.code = code;
        this.expireTime = expireTime;
    }
    /**
     * 接收一个在多少秒之内过期
     */
    public ImageCode(BufferedImage image,String code,int expireIn){
        this.image = image;
        this.code = code;
        this.expireTime=LocalDateTime.now().plusSeconds(expireIn);
    }
}



  • 将随机数存到session中
  • 在将生成的图片写到接口的响应中
package com.zcw.security.core.validate.code;

import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

/**
 * @ClassName : ValidateCodeController
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-06-22 13:31
 */
@RestController
public class ValidateCodeController {

    private SessionStrategy  sessionStrategy= new HttpSessionSessionStrategy();
    private static final String SESSION_KEY="SESSION_KEY_IMAGE_CODE";

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

    /**
     * 根据随机数生成图片,验证码
     * @param request
     * @return
     */
    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);
    }
    /**
     * 生成随机背景条纹
     */
    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);
    }
}


  • 测试:
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第1张图片
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第2张图片

2.认证流程中加入图形验证码校验

【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第3张图片

  • 编写针对验证码的异常处理类
package com.zcw.security.core.validate.code;

import org.springframework.security.core.AuthenticationException;
/**
 * @ClassName : ValidateCodeException
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-06-22 14:24
 */
public class ValidateCodeException extends AuthenticationException {
    public ValidateCodeException(String msg){
        super(msg);
    }
}


  • 修改javaBean

package com.zcw.security.core.validate.code;

import lombok.Data;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.time.LocalDateTime;

/**
 * @ClassName : ImageCode
 * @Description :生成图片验证码
 * @Author : Zhaocunwei
 * @Date: 2020-06-22 13:24
 */
public class ImageCode {
    /**
     * 图片根据随机数生成
     */
    private BufferedImage image;
    /**
     * 随机数,一般存到session中,
     * 用户登录时需要进行校验
     */
    private String code;
    /**
     * 时间:设置过期时间
     */
    private LocalDateTime expireTime;

    private boolean expried;

    public ImageCode(BufferedImage image,String code,LocalDateTime expireTime){
        this.image= image;
        this.code = code;
        this.expireTime = 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;
    }

    public boolean isExpried() {
        return LocalDateTime.now().isAfter(expireTime);
    }

    public void setExpried(boolean expried) {
        this.expried = expried;
    }
}


  • 添加过滤器
package com.zcw.security.core.validate.code;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.IOException;

/**
 * @ClassName : ValidateCodeFilter
 * @Description :实现我们的过滤器,每次只被调用一次
 * @Author : Zhaocunwei
 * @Date: 2020-06-22 14:03
 */
public class ValidateCodeFilter extends OncePerRequestFilter {
    private AuthenticationFailureHandler authenticationFailureHandler;
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain) throws ServletException, IOException {
        //过滤器只有在登录的情况下生效,
        if(StringUtils.equals("/authentication/form",httpServletRequest.getRequestURI())
        && StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(),"post")){
            try {
                validate(new ServletWebRequest(httpServletRequest));
            }catch (ValidateCodeException e){
                //自定义失败处理器
                authenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
            }
        }
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }

    /**
     * 从session里面获取参数,然后进行校验
     * @param servletWebRequest
     */
    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request,
                ValidateCodeController.SESSION_KEY);
        String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),"imageCode");
        if(StringUtils.isBlank(codeInRequest)){
            throw new ValidateCodeException("验证码的值不能为空");
        }
        if(codeInSession == null){
            throw new ValidateCodeException("验证码不存在");
        }
        if(codeInSession.isExpried()){
            sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY);
            throw new ValidateCodeException("验证码已过期");
        }
        if(!StringUtils.equals(codeInSession.getCode(),codeInRequest)){
            throw new ValidateCodeException("验证码不匹配");
        }
        sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY);
    }
}


【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第4张图片

  • 配置类:
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第5张图片
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第6张图片
package com.zcw.security.core.validate.code;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.IOException;

/**
 * @ClassName : ValidateCodeFilter
 * @Description :实现我们的过滤器,每次只被调用一次
 * @Author : Zhaocunwei
 * @Date: 2020-06-22 14:03
 */
public class ValidateCodeFilter extends OncePerRequestFilter {
    private AuthenticationFailureHandler authenticationFailureHandler;
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain) throws ServletException, IOException {
        //过滤器只有在登录的情况下生效,
        if(StringUtils.equals("/authentication/form",httpServletRequest.getRequestURI())
        && StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(),"post")){
            try {
                validate(new ServletWebRequest(httpServletRequest));
            }catch (ValidateCodeException e){
                //自定义失败处理器
                authenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
            }
        }
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }

    /**
     * 从session里面获取参数,然后进行校验
     * @param servletWebRequest
     */
    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request,
                ValidateCodeController.SESSION_KEY);
        String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),"imageCode");
        if(StringUtils.isBlank(codeInRequest)){
            throw new ValidateCodeException("验证码的值不能为空");
        }
        if(codeInSession == null){
            throw new ValidateCodeException("验证码不存在");
        }
        if(codeInSession.isExpried()){
            sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY);
            throw new ValidateCodeException("验证码已过期");
        }
        if(!StringUtils.equals(codeInSession.getCode(),codeInRequest)){
            throw new ValidateCodeException("验证码不匹配");
        }
        sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY);
    }

    public AuthenticationFailureHandler getAuthenticationFailureHandler() {
        return authenticationFailureHandler;
    }

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


【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第7张图片

  • 测试:
  • 进行修改,不希望前端返回这么多信息
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第8张图片
  • 修改后为:
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第9张图片
  • 继续修改我们的过滤器:
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第10张图片
  • 测试
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第11张图片
  • 直接点登陆:
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第12张图片

3.图片验证码重构

验证码基本参数可配置

【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第13张图片

package com.zcw.security.core.properties;

import lombok.Data;

/**
 * @ClassName : IMageCodeProperties
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-06-22 17:45
 */
@Data
public class IMageCodeProperties {
    private int width=67;
    private int height=23;
    private int expireIn=60;
}


package com.zcw.security.core.properties;

import lombok.Data;

/**
 * @ClassName : ValidateCodeProperties
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-06-22 17:48
 */
@Data
public class ValidateCodeProperties {
    private IMageCodeProperties image= new IMageCodeProperties();
}


package com.zcw.security.core.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @ClassName : SecurityProperties
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-06-19 13:54
 */
@ConfigurationProperties(prefix = "zcw.security")
@Component
public class MySecurityProperties {
    private BrowserProperties browserProperties = new BrowserProperties();
    private ValidateCodeProperties validateCodeProperties = new ValidateCodeProperties();

    public BrowserProperties getBrowserProperties() {
        return browserProperties;
    }

    public void setBrowserProperties(BrowserProperties browserProperties) {
        this.browserProperties = browserProperties;
    }

    public ValidateCodeProperties getValidateCodeProperties() {
        return validateCodeProperties;
    }

    public void setValidateCodeProperties(ValidateCodeProperties validateCodeProperties) {
        this.validateCodeProperties = validateCodeProperties;
    }
}


我们项目中进行配置:
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第14张图片
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第15张图片

  • 修改验证码长度
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第16张图片
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第17张图片
    上面的长度英文单词写错了,特改为如下图所示:
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第18张图片
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第19张图片
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第20张图片
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第21张图片
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第22张图片

验证码拦截的接口可配置

【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第23张图片
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第24张图片
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第25张图片
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第26张图片
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第27张图片
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第28张图片
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第29张图片


package com.zcw.security.core.validate.code;
import com.zcw.security.core.properties.MySecurityProperties;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

/**
 * @ClassName : ValidateCodeFilter
 * @Description :实现我们的过滤器,每次只被调用一次
 * @Author : Zhaocunwei
 * @Date: 2020-06-22 14:03
 */
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
    private AuthenticationFailureHandler authenticationFailureHandler;
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    private Set<String> urls = new HashSet<>();
    private MySecurityProperties mySecurityProperties;
    private AntPathMatcher pathMatcher = new AntPathMatcher();

    @Override
    public void afterPropertiesSet() throws ServletException{
        super.afterPropertiesSet();
        String[] configUrls =StringUtils
                                .splitByWholeSeparatorPreserveAllTokens(mySecurityProperties.getValidateCodeProperties()
                                .getImage().getUrl(),",");
        for(String configUrl : configUrls){
            urls.add(configUrl);
        }
        //添加我们之前配置的登录请求的接口
        urls.add("/authentication/form");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain) throws ServletException, IOException {
        boolean action =false;
        for(String url :urls){
            //我们配置文件里面的url地址与前端传递过来的URL进行匹配
            if(pathMatcher.match(url, httpServletRequest.getRequestURI())){
                action =true;
            }
        }

        //过滤器只有在登录的情况下生效,
//        if(StringUtils.equals("/authentication/form",httpServletRequest.getRequestURI())
//         && StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(),"post")){
        if(action){
            try {
                validate(new ServletWebRequest(httpServletRequest));
            }catch (ValidateCodeException e){
                //自定义失败处理器
                authenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
                return;
            }
        }
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }

    /**
     * 从session里面获取参数,然后进行校验
     * @param servletWebRequest
     */
    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request,
                ValidateCodeController.SESSION_KEY);
        String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),"imageCode");
        if(StringUtils.isBlank(codeInRequest)){
            throw new ValidateCodeException("验证码的值不能为空");
        }
        if(codeInSession == null){
            throw new ValidateCodeException("验证码不存在");
        }
        if(codeInSession.isExpried()){
            sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY);
            throw new ValidateCodeException("验证码已过期");
        }
        if(!StringUtils.equals(codeInSession.getCode(),codeInRequest)){
            throw new ValidateCodeException("验证码不匹配");
        }
        sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY);
    }

    public AuthenticationFailureHandler getAuthenticationFailureHandler() {
        return authenticationFailureHandler;
    }

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

    public SessionStrategy getSessionStrategy() {
        return sessionStrategy;
    }

    public void setSessionStrategy(SessionStrategy sessionStrategy) {
        this.sessionStrategy = sessionStrategy;
    }

    public Set<String> getUrls() {
        return urls;
    }

    public void setUrls(Set<String> urls) {
        this.urls = urls;
    }

    public MySecurityProperties getMySecurityProperties() {
        return mySecurityProperties;
    }

    public void setMySecurityProperties(MySecurityProperties mySecurityProperties) {
        this.mySecurityProperties = mySecurityProperties;
    }
}




我们实际项目中配置的方案:
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第30张图片
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第31张图片
-测试 直接登录,不输入验证码,
在这里插入图片描述
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第32张图片
登录成功后:
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第33张图片
然后直接访问我们后台接口,提示:证明已经被拦截
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第34张图片

验证码的生成逻辑可配置

  • 验证码的生成器

package com.zcw.security.core.validate.code;

import org.springframework.web.context.request.ServletWebRequest;

public interface ValidateCodeGenerator {
    /**
     * 

验证码生成器

*/
ImageCode generate(ServletWebRequest request); }

实现类:
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第35张图片
把上面的验证码生成逻辑移动到我们实现类中,


package com.zcw.security.core.validate.code;

import com.zcw.security.core.properties.MySecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

/**
 * @ClassName : ImageCodeGenerator
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-06-23 10:00
 */
public class ImageCodeGenerator implements ValidateCodeGenerator {
    @Autowired
    private MySecurityProperties mySecurityProperties;
    @Override
    public ImageCode generate(ServletWebRequest request) {
        /**
         * 根据随机数生成图片,验证码
         * @param request
         * @return
         */
        private ImageCode createImageCode(ServletWebRequest request) {
            int width= ServletRequestUtils.getIntParameter(request.getRequest(),
                    "width",mySecurityProperties.getValidateCodeProperties()
                            .getImage().getWidth());
            int height=ServletRequestUtils.getIntParameter(request.getRequest(),
                    "height",mySecurityProperties.getValidateCodeProperties()
                            .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<mySecurityProperties.getValidateCodeProperties().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,
                    mySecurityProperties.getValidateCodeProperties()
                            .getImage().getExpireIn());
        }

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


  • controller层方法:
package com.zcw.security.core.validate.code;

import com.zcw.security.core.properties.IMageCodeProperties;
import com.zcw.security.core.properties.MySecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

/**
 * @ClassName : ValidateCodeController
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-06-22 13:31
 */
@RestController
public class ValidateCodeController {
    @Autowired
    private ValidateCodeGenerator  validateCodeGenerator;

    private SessionStrategy  sessionStrategy= new HttpSessionSessionStrategy();
    private static final String SESSION_KEY="SESSION_KEY_IMAGE_CODE";

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


}


  • 接口实现,变成可配置的
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第36张图片
package com.zcw.security.core.validate.code;

import com.zcw.security.core.properties.MySecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @ClassName : ValidateCodeBeanConfig
 * @Description : 配置类
 * @Author : Zhaocunwei
 * @Date: 2020-06-23 10:04
 */
@Configuration
public class ValidateCodeBeanConfig {
    @Autowired
    private MySecurityProperties mySecurityProperties;

    @Bean
    @ConditionalOnMissingBean(name ="imageCodeGenerator" )
    public ValidateCodeGenerator  imageCodeGenerator(){
        ImageCodeGenerator codeGenerator = new ImageCodeGenerator();
        codeGenerator.setMySecurityProperties(mySecurityProperties);
        return codeGenerator;
    }
}


【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第37张图片

3.实现“记住我”功能

  • 记住我功能基本原理
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第38张图片
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第39张图片
  • 记住我功能具体实现
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第40张图片
  • TokenRepository
    用来连接数据库
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第41张图片
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第42张图片
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第43张图片
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第44张图片
package com.zcw.security.browser;

import com.zcw.security.core.properties.MySecurityProperties;
import com.zcw.security.core.validate.code.ValidateCodeFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import javax.xml.ws.soap.Addressing;

/**
 * @ClassName : BrowserSecurityConfig
 * @Description :适配器类
 * @Author : Zhaocunwei
 * @Date: 2020-06-18 17:43
 */
@Component
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService  userDetailsService;
    @Autowired
    private AuthenticationFailureHandler zcwAuthenticationFailureHandler;
    @Autowired
    private MySecurityProperties mySecurityProperties;
    @Autowired
    private AuthenticationSuccessHandler zcwAuthenticationSuccessHandler;
    @Autowired
    private DataSource dataSource;
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Bean
    public PersistentTokenRepository  persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //启动时创建表,也可以直接进入源代码执行脚本,建议执行脚本,这个地方不要配置
        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        validateCodeFilter.setAuthenticationFailureHandler(zcwAuthenticationFailureHandler);
        validateCodeFilter.setMySecurityProperties(mySecurityProperties);
        //调用初始化方法
        validateCodeFilter.afterPropertiesSet();
        //表单登录
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()
                .loginPage("/authentication/require")
                .loginProcessingUrl("/authentication/form")
                .successHandler(zcwAuthenticationSuccessHandler)
                .failureHandler(zcwAuthenticationFailureHandler)
                //记住我的配置
                .and()
                .rememberMe()
                    .tokenRepository(persistentTokenRepository())
                //配置token 过期的秒数
                    .tokenValiditySeconds(mySecurityProperties
                            .getBrowserProperties()
                            .getRemeberMeSeconds())
                    .userDetailsService(userDetailsService)
                .and()
                //授权
                .authorizeRequests()
                //授权匹配器
                .antMatchers("/authentication/require",
                        mySecurityProperties.getBrowserProperties().getLoginPage(),
                        "/code/image").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .csrf().disable();//跨站攻击被禁用
    }
}



【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第45张图片
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第46张图片
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第47张图片
【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第48张图片

  • 查询数据库
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第49张图片
  • 重新启动服务器:
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第50张图片
  • 我们不登录,直接访问user接口
    在这里插入图片描述
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第51张图片
    【Spring Security技术栈开发企业级认证与授权】----使用Spring Security开发基于表单的登录(三)_第52张图片

你可能感兴趣的:(Spring,#,Spring)