框架建设收集

spring boot 之 security(四) 图片验证码

2019-10-15  本文已影响0人  _大叔_

一、创建一个验证码实体类

package com.wt.cloud.validate;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.time.LocalDateTime;
import java.util.Random;
import java.util.UUID;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
/**
 * 功能描述: 图片验证码实体类
 * @author : big uncle
 * @date : 2019/10/14 14:13
 */
public class ImageCode {

    private BufferedImage image;

    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 static ImageCode createImage(){
        //设置字体样式
        Font font = new Font("宋体", Font.PLAIN, 25);
        //图片大小
        int width = 1000;
        int height = 1000;

        //创建一个图片缓冲区
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
        //获取图片处理对象
        Graphics graphics = image.getGraphics();
        //填充背景色
        graphics.setColor(getRandomColor());
        graphics.fillRect(1, 1, width - 1, height - 1);
        //设定边框颜色
        graphics.setColor(getRandomColor());
        graphics.drawRect(0, 0, width - 1, height - 1);
        //设置干扰线
        graphics.setColor(getRandomColor());
        Random random = new Random();
        for(int i=0;i<20;i++){
            int x = random.nextInt(width - 1);
            int y = random.nextInt(height - 1);
            int x1 = random.nextInt(width - 1);
            int y1 = random.nextInt(height - 1);
            graphics.drawLine(x, y, x1, y1);
        }
        //写入文字
        graphics.setColor(getRandomColor());
        graphics.setFont(font);
        String content = UUID.randomUUID().toString().replace("-","").substring(0,4);
        graphics.drawString(content, 100, 100);
        //释放资源
        graphics.dispose();
        return new ImageCode(image,content,60);

    }

    /**
     * 获取随机颜色
     * @return
     */
    public static Color getRandomColor() {
        Random random = new Random();
        int r = random.nextInt(255);
        int g = random.nextInt(255);
        int b = random.nextInt(255);
        return new Color(r, g, b);
    }

}

二、创建一个controller用于获取验证码

package com.wt.cloud.validate;

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.RequestMapping;
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.io.IOException;

@RestController
@RequestMapping("/code")
public class ValiDateWeb {

    /**
     * spring 的session
     */
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    private static final String session_key = "session_key_image_code";

    @GetMapping("/imageCode")
    public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        ImageCode imageCode = ImageCode.createImage();
        sessionStrategy.setAttribute(new ServletWebRequest(request),session_key,imageCode);
        ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
    }
}

三、创建一个异常类,用于验证码校验

package com.wt.cloud;

import org.springframework.security.core.AuthenticationException;

public class ValidateException extends AuthenticationException {

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

}

四、创建一个过滤器,检验登陆时候的验证码

package com.wt.cloud.filter;

import cn.hutool.core.util.StrUtil;
import com.wt.cloud.ValidateException;
import com.wt.cloud.validate.ImageCode;
import lombok.Data;
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.io.IOException;
import java.time.LocalDateTime;

/**
 * 功能描述: OncePerRequestFilter spring提供的工具类,保证我们的过滤器始终只会被调一次
 * @author : big uncle
 * @date : 2019/10/14 15:13
 */
@Data
public class ValidateCodeFilter extends OncePerRequestFilter {

    /**
     * 失败处理器
     */
    private AuthenticationFailureHandler authenticationFailureHandler;

    /**
     * spring 的session
     */
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        // 拦截登陆uri,不是登陆则全部放过
        if(StrUtil.equalsIgnoreCase("/authentication/login",httpServletRequest.getRequestURI()) && StrUtil.equalsIgnoreCase(httpServletRequest.getMethod(),"post")){
            try{
                validate(new ServletWebRequest(httpServletRequest));
            }catch(ValidateException v){
                authenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,v);
                return;
            }
        }
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }

    // 校验
    private void validate(ServletWebRequest servletWebRequest) throws ServletRequestBindingException {
        ImageCode imageCode = (ImageCode) sessionStrategy.getAttribute(servletWebRequest,"session_key_image_code");
        String code = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(),"imageCode");
        if(StrUtil.isEmpty(code)){
            throw new ValidateException("验证码为空或不存在");
        }
        if(!StrUtil.equalsIgnoreCase(imageCode.getCode(),code)){
            throw new ValidateException("验证码不匹配");
        }
        if(LocalDateTime.now().isAfter(imageCode.getExpireTime())){
            sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeFilter.ALREADY_FILTERED_SUFFIX);
            throw new ValidateException("验证码已过期");
        }
        sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeFilter.ALREADY_FILTERED_SUFFIX);
    }
}

五、把所创建的过滤器添加到spring security的过滤器链的 UsernamePasswordAuthenticationFilter 之前

.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)

package com.wt.cloud.config;

import com.wt.cloud.filter.ValidateCodeFilter;
import com.wt.cloud.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * 功能描述: WebSecurityConfigurerAdapter web安全应用的适配器
 * @author : big uncle
 * @date : 2019/10/10 10:26
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private MyAuthenticationSuccessHandle myAuthenticationSuccessHandle;
    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭默认的basic认证拦截
//        http
//        .authorizeRequests()
//        .anyRequest()
//        .permitAll().and()
//        .logout()
//        .permitAll();
        // 让使用form表单认证
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        validateCodeFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
        http
                // 在 UsernamePasswordAuthenticationFilter 之前添加一个过滤器
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()
                // 自定义登陆页面 或 controller
                .loginPage("/web/authentication")
                // 覆盖spring security默认登陆地址。默认是login
                .loginProcessingUrl("/authentication/login")
                // 配置成功处理类
                .successHandler(myAuthenticationSuccessHandle)
                // 配置失败处理类
                .failureHandler(myAuthenticationFailureHandler)
                .and()
                // 以下都是授权的配置
                .authorizeRequests()
                // 剔除登陆页面的认证拦截,否则会在进登陆页面一直跳转;permitAll 指任何人都可以访问这个url
                .antMatchers(
                        "/web/authentication",
                        "/code/imageCode",
                        securityProperties.getWebProperties().getLoginPage()
                ).permitAll()
                // 任何请求
                .anyRequest()
                // 都需要身份认证
                .authenticated()
                .and()
                // 关闭跨站请求伪造拦截
                .csrf().disable();

    }
}

上一篇 下一篇

猜你喜欢

热点阅读