JavaWeb-SpringSecurity图片验证ImageCode

 

 

  系列博文

  项目已上传至guthub  传送门

  JavaWeb-SpringSecurity初认识  传送门

  JavaWeb-SpringSecurity在数据库中查询登陆用户  传送门

  JavaWeb-SpringSecurity自定义登陆页面  传送门

  JavaWeb-SpringSecurity实现需求-判断请求是否以html结尾  传送门

  JavaWeb-SpringSecurity自定义登陆配置  传送门

  JavaWeb-SpringSecurity图片验证ImageCode  传送门

  JavaWeb-SpringSecurity记住我功能  传送门

  JavaWeb-SpringSecurity使用短信验证码登陆  传送门

 

 

  创建一个validate.code存放编写验证码校验代码,创建ImageCode.class图片验证码工具类

  

 

package com.Gary.GaryRESTful.validate.code;

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

//图片验证码
public class ImageCode {
    
    //给前台展示的图片
    private BufferedImage image;
    
    //验证码
    private String code;
    
    //过期时间
    private LocalDateTime expireTime;
    
    public ImageCode(BufferedImage image,String code,int expreTime)
    {
        this.image = image;
        this.code = code;
        this.expireTime = LocalDateTime.now().plusSeconds(expreTime);
    }
    
    public ImageCode(BufferedImage image,String code,LocalDateTime expireTime)
    {
        this.image = image;
        this.code = code;
        this.expireTime = expireTime;
    }
    
    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;
    }
    
    
    
}
ImageCode.class

 

  在controller层中创建ValidateCodeController.class

  图片验证三步骤

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

 

package com.Gary.GaryRESTful.controller;

import java.io.IOException;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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 com.Gary.GaryRESTful.validate.code.ImageCode;

@RestController
public class ValidateCodeController {

    //操作Session
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
    private String sessionKey = "session_key_image_code";
    
    @GetMapping("/code/image")
    public void createCode(HttpServletRequest request,HttpServletResponse response) throws IOException
    {
        //生成随机数的图片
        ImageCode imageCode = createImageCode();
        
        //将随机数放入到session中
        sessionStrategy.setAttribute(new ServletWebRequest(request), sessionKey, imageCode);
        
        //将我们生成的图片写到接口的响应的输出流中
        ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
        
    }
    
    //生成图片验证码(验证码,图片,失效的时间)
    private ImageCode createImageCode()
    {
        return null;
    }
    
}
ValidateCodeController.class

 

  编写验证码的逻辑

    1、定义验证码图片长与宽

    2、获取画笔工具

    3、设置画笔工具的颜色

    4、画长方形

    5、改变画笔工具的颜色

    6、画干扰线

    7、改变画笔工具的颜色

    8、画数据

    9、关闭画笔工具

 

  登陆页面login.html

<form action="/loginPage" method="post">
    
        用户名:
        <input type="text" name="username">
        <br>
        密码:
        <input type="password" name="password">
        <br>
        图片验证码:
        <input type="text" name="imageCode">
        <img src="/code/image">
        <input type="submit">
    
</form>

 

  ValidateCodeController.java中实现绘画验证码方法createImageCode()

//生成图片验证码(验证码,图片,失效的时间)
    private ImageCode createImageCode()
    {
        //定义图片的长和宽
        int width = 67;
        int height = 23;
        
        //生成一张图片
        BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        
        //获得画笔工具
        Graphics g = image.getGraphics();
        
        //画一个矩形
        g.setColor(new Color(255,255,255));
        g.fillRect(0, 0, width, height);
        
        //画干扰线
        g.setColor(new Color(0,0,0));
        //设置字体
        g.setFont(new Font("Time New Roman",Font.ITALIC,20));
        Random random = new Random();
        
        for(int i=0;i<20;i++)
        {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int x1 = random.nextInt(12);
            int y1 = random.nextInt(12);
            //(x,y)到(x+x1,y+y1)
            g.drawLine(x, y, x+x1, y+y1);
        }
        
        //画数据
        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, 16);
        }
        
        g.dispose();
        
        //生成我们自己的验证码数据(图片,验证码,过期时间)
        return new ImageCode(image,sRand,60);
    }

 

package com.Gary.GaryRESTful.controller;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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 com.Gary.GaryRESTful.validate.code.ImageCode;

@RestController
public class ValidateCodeController {

    //操作Session
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
    private String sessionKey = "session_key_image_code";
    
    @GetMapping("/code/image")
    public void createCode(HttpServletRequest request,HttpServletResponse response) throws IOException
    {
        //生成随机数的图片
        ImageCode imageCode = createImageCode();
        
        //将随机数放入到session中
        sessionStrategy.setAttribute(new ServletWebRequest(request), sessionKey, imageCode);
        
        //将我们生成的图片写到接口的响应的输出流中
        ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
        
    }
    
    //生成图片验证码(验证码,图片,失效的时间)
    private ImageCode createImageCode()
    {
        //定义图片的长和宽
        int width = 67;
        int height = 23;
        
        //生成一张图片
        BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        
        //获得画笔工具
        Graphics g = image.getGraphics();
        
        //画一个矩形
        g.setColor(new Color(255,255,255));
        g.fillRect(0, 0, width, height);
        
        //画干扰线
        g.setColor(new Color(0,0,0));
        //设置字体
        g.setFont(new Font("Time New Roman",Font.ITALIC,20));
        Random random = new Random();
        
        for(int i=0;i<20;i++)
        {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int x1 = random.nextInt(12);
            int y1 = random.nextInt(12);
            //(x,y)到(x+x1,y+y1)
            g.drawLine(x, y, x+x1, y+y1);
        }
        
        //画数据
        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, 16);
        }
        
        g.dispose();
        
        //生成我们自己的验证码数据(图片,验证码,过期时间)
        return new ImageCode(image,sRand,60);
    }
    
}
ValidateCodeController.java

 

  处理验证码拦截器,添加处理图片验证码的Filter

  如果验证码登陆成功,则放行,否则进行拦截

  配置验证码拦截器ValidateCodeException.java,继承AuthenticationException.java

//AuthenticationException是springsecurity中所有异常的基类
public class ValidateCodeException extends AuthenticationException{

    public ValidateCodeException(String msg) {
        super(msg);
        // TODO Auto-generated constructor stub
    }

}

 

  检验验证码是否正确方法

//校验验证码是否正确
    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        
        // 获得session域中正确的验证码
        ImageCode codeInSession =  (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.sessionKey);
        // 获得request域中的用户输入的验证码imageCode
        String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");

        // 判断用户输入的验证码是否为空
        if(StringUtils.isEmpty(codeInRequest))
        {
            throw new ValidateCodeException("验证码不能为空");
        }
        
        // 判断session域中的验证码是否为null
        if(codeInSession == null)
        {
            throw new ValidateCodeException("验证码不存在");
        }
        
        // 判断验证码是否过期
        if(codeInSession.isExpried())
        {
            throw new ValidateCodeException("验证码已过期");
        }
        
        // 校验两个验证码是否匹配
        if(!StringUtils.pathEquals(codeInSession.getCode(), codeInRequest))
        {
            //System.out.println("正确的:"+codeInSession.getCode());
            //System.out.println("用户输入的:"+codeInRequest);
            throw new ValidateCodeException("验证码不匹配");
        }
        
        // 将验证码从session域中移除
        sessionStrategy.removeAttribute(request, ValidateCodeController.sessionKey);
        
    }

 

  如果用户验证码正确则放行用户登陆步骤,当用户登陆输入正确输入账号密码时,则给与用户下一步操作,否则返回"坏的凭证"

  验证码如果没有输入正确,不会放行用户登陆步骤

  SecurityConfig.java配置

//Web应用安全适配器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    //告诉SpringSecurity密码用什么加密的
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
    
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private LoginFailureHandler loginFailureHandler;
    
    protected void configure(HttpSecurity http) throws Exception{
        
        //声明我们自己写的过滤器
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        //给过滤器赋值
        validateCodeFilter.setAuthenticationFailureHandler(loginFailureHandler);
        
        //表单验证(身份认证)
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
            
            .formLogin()
            //自定义登陆页面
            .loginPage("/require")
            //如果URL为loginPage,则用SpringSecurity中自带的过滤器去处理该请求
            .loginProcessingUrl("/loginPage")
            //配置登陆成功调用loginSuccessHandler
            .successHandler(loginSuccessHandler)
            //配置登陆失败调用loginFailureHandler
            .failureHandler(loginFailureHandler)
            .and()
            //请求授权
            .authorizeRequests()
            //在访问我们的URL时,我们是不需要省份认证,可以立即访问
            .antMatchers("/login.html","/require","/code/image").permitAll()
            //所有请求都被拦截,跳转到(/login请求中)
            .anyRequest()
            //都需要我们身份认证
            .authenticated()
            //SpringSecurity保护机制
            .and().csrf().disable();
    }

 

 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Gary登陆页面</h1>
    <form action="/loginPage" method="post">
    
        用户名:
        <input type="text" name="username">
        <br>
        密码:
        <input type="password" name="password">
        <br>
        图片验证码:
        <input type="text" name="imageCode">
        <img src="/code/image">
        <input type="submit">
    
    </form>

</body>
</html>
login.html

 

package com.Gary.GaryRESTful.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.Gary.GaryRESTful.filter.ValidateCodeFilter;
import com.Gary.GaryRESTful.handler.LoginFailureHandler;
import com.Gary.GaryRESTful.handler.LoginSuccessHandler;


//Web应用安全适配器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    //告诉SpringSecurity密码用什么加密的
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
    
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private LoginFailureHandler loginFailureHandler;
    
    protected void configure(HttpSecurity http) throws Exception{
        
        //声明我们自己写的过滤器
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        //给过滤器赋值
        validateCodeFilter.setAuthenticationFailureHandler(loginFailureHandler);
        
        //表单验证(身份认证)
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
            
            .formLogin()
            //自定义登陆页面
            .loginPage("/require")
            //如果URL为loginPage,则用SpringSecurity中自带的过滤器去处理该请求
            .loginProcessingUrl("/loginPage")
            //配置登陆成功调用loginSuccessHandler
            .successHandler(loginSuccessHandler)
            //配置登陆失败调用loginFailureHandler
            .failureHandler(loginFailureHandler)
            .and()
            //请求授权
            .authorizeRequests()
            //在访问我们的URL时,我们是不需要省份认证,可以立即访问
            .antMatchers("/login.html","/require","/code/image").permitAll()
            //所有请求都被拦截,跳转到(/login请求中)
            .anyRequest()
            //都需要我们身份认证
            .authenticated()
            //SpringSecurity保护机制
            .and().csrf().disable();
    }
    
}
SecurityConfig.java

 

package com.Gary.GaryRESTful.validate.code;

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

//图片验证码
public class ImageCode {
    
    //给前台展示的图片
    private BufferedImage image;
    
    //验证码
    private String code;
    
    //过期时间
    private LocalDateTime expireTime;
    
    public ImageCode(BufferedImage image,String code,int expreTime)
    {
        this.image = image;
        this.code = code;
        this.expireTime = LocalDateTime.now().plusSeconds(expreTime);
    }
    
    public ImageCode(BufferedImage image,String code,LocalDateTime expireTime)
    {
        this.image = image;
        this.code = code;
        this.expireTime = expireTime;
    }
    
    //判断验证码是否过去
    public boolean isExpried()
    {
        //判断当前时间是否在过期时间之后
        return LocalDateTime.now().isAfter(expireTime);
    }
    
    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;
    }
    
    
    
}
ImageCode.java

 

package com.Gary.GaryRESTful.controller;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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 com.Gary.GaryRESTful.validate.code.ImageCode;

@RestController
public class ValidateCodeController {

    //操作Session
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
    public SessionStrategy getSessionStrategy() {
        return sessionStrategy;
    }

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

    public static String getSessionKey() {
        return sessionKey;
    }

    public static void setSessionKey(String sessionKey) {
        ValidateCodeController.sessionKey = sessionKey;
    }

    public static String sessionKey = "session_key_image_code";
    
    @GetMapping("/code/image")
    public void createCode(HttpServletRequest request,HttpServletResponse response) throws IOException
    {
        //生成随机数的图片
        ImageCode imageCode = createImageCode();
        
        //将随机数放入到session中
        sessionStrategy.setAttribute(new ServletWebRequest(request), sessionKey, imageCode);
        
        //将我们生成的图片写到接口的响应的输出流中
        ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
        
    }
    
    //生成图片验证码(验证码,图片,失效的时间)
    private ImageCode createImageCode()
    {
        //定义图片的长和宽
        int width = 67;
        int height = 23;
        
        //生成一张图片
        BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        
        //获得画笔工具
        Graphics g = image.getGraphics();
        
        //画一个矩形
        g.setColor(new Color(255,255,255));
        g.fillRect(0, 0, width, height);
        
        //画干扰线
        g.setColor(new Color(0,0,0));
        //设置字体
        g.setFont(new Font("Time New Roman",Font.ITALIC,20));
        Random random = new Random();
        
        for(int i=0;i<20;i++)
        {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int x1 = random.nextInt(12);
            int y1 = random.nextInt(12);
            //(x,y)到(x+x1,y+y1)
            g.drawLine(x, y, x+x1, y+y1);
        }
        
        //画数据
        String sRand = "";
        for(int i = 0;i<4;i++)
        {
            String rand =String.valueOf(random.nextInt(10));
            //System.out.println(rand);
            sRand += rand;
            //每一个字都改变一下颜色
            g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110),20+random.nextInt(110)));
            //画每一个数据
            g.drawString(rand, 13*i, 16);
        }
        
        g.dispose();
        
        //生成我们自己的验证码数据(图片,验证码,过期时间)
        return new ImageCode(image,sRand,60000);
    }
    
}
ValidateCodeController.java

 

package com.Gary.GaryRESTful.exception;

import org.springframework.security.core.AuthenticationException;


//AuthenticationException是springsecurity中所有异常的基类
public class ValidateCodeException extends AuthenticationException{

    public ValidateCodeException(String msg) {
        super(msg);
        // TODO Auto-generated constructor stub
    }

}
ValidateCodeException.java

 

package com.Gary.GaryRESTful.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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.StringUtils;
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 com.Gary.GaryRESTful.controller.ValidateCodeController;
import com.Gary.GaryRESTful.exception.ValidateCodeException;
import com.Gary.GaryRESTful.validate.code.ImageCode;


//继承OncePerRequestFilter,保证这个filter只会执行一次
public class ValidateCodeFilter extends OncePerRequestFilter{

    private AuthenticationFailureHandler authenticationFailureHandler;
    
    //操作session域的工具
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
    //Filter执行
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        
        //loginPage
        if(StringUtils.pathEquals("/loginPage", request.getRequestURI()) && StringUtils.startsWithIgnoreCase(request.getMethod(), "post"))
        {
            //filter才会执行
            try
            {
                validate(new ServletWebRequest(request));
            }
            catch(ValidateCodeException e) {
                //判处验证码的异常
                authenticationFailureHandler.onAuthenticationFailure(request,response,e);
                //一旦出现异常,我们就不就不能继续执行(应该放行),应该return
                return;
            }
            
        }
        
        //放行
        filterChain.doFilter(request, response);
        
    }

    //校验验证码是否正确
    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        
        // 获得session域中正确的验证码
        ImageCode codeInSession =  (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.sessionKey);
        // 获得request域中的用户输入的验证码imageCode
        String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");

        // 判断用户输入的验证码是否为空
        if(StringUtils.isEmpty(codeInRequest))
        {
            throw new ValidateCodeException("验证码不能为空");
        }
        
        // 判断session域中的验证码是否为null
        if(codeInSession == null)
        {
            throw new ValidateCodeException("验证码不存在");
        }
        
        // 判断验证码是否过期
        if(codeInSession.isExpried())
        {
            throw new ValidateCodeException("验证码已过期");
        }
        
        // 校验两个验证码是否匹配
        if(!StringUtils.pathEquals(codeInSession.getCode(), codeInRequest))
        {
            //System.out.println("正确的:"+codeInSession.getCode());
            //System.out.println("用户输入的:"+codeInRequest);
            throw new ValidateCodeException("验证码不匹配");
        }
        
        // 将验证码从session域中移除
        sessionStrategy.removeAttribute(request, ValidateCodeController.sessionKey);
        
    }

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

}
ValidateCodeFilter.java

 

 

优化:增加代码的重用性

  在GaryRESTful.properties包下创建ValidateCodeProperties.class,用于管理配置图片验证码的功能,再创建一个ImageCodeProperties.class,用于管理图片验证码的生成

  优化图片验证码的生成

  

  application.properties

#配置登陆方式
gary.security.loginType = JSON

server.port=8081

#验证码长度
gary.security.code.image.length = 6
#验证码图片的长
gary.security.code.image.width = 100

 

 

 

#datasource
spring.datasource.url=jdbc:mysql:///springsecurity?serverTimezone=UTC&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.dricer-class-name=com.mysql.jdbc.Driver

#jpa
#打印出数据库语句
spring.jpa.show-sql=true
#更新数据库表
spring.jpa.hibernate.ddl-auto=update

#配置登陆方式
gary.security.loginType = JSON

server.port=8081

#验证码长度
gary.security.code.image.length = 6
#验证码图片的长
gary.security.code.image.width = 100
application.properties

 

package com.Gary.GaryRESTful.controller;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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 com.Gary.GaryRESTful.properties.GarySecurityProperties;
import com.Gary.GaryRESTful.validate.code.ImageCode;

@RestController
public class ValidateCodeController {
    
    //操作Session
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
    public static String sessionKey = "session_key_image_code";
    
    @Autowired
    private GarySecurityProperties garySecurityProperties;
    
    public SessionStrategy getSessionStrategy() {
        return sessionStrategy;
    }

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

    public static String getSessionKey() {
        return sessionKey;
    }

    public static void setSessionKey(String sessionKey) {
        ValidateCodeController.sessionKey = sessionKey;
    }


    
    @GetMapping("/code/image")
    public void createCode(HttpServletRequest request,HttpServletResponse response) throws IOException
    {
        //生成随机数的图片
        ImageCode imageCode = createImageCode(request);
        
        //将随机数放入到session中
        sessionStrategy.setAttribute(new ServletWebRequest(request), sessionKey, imageCode);
        
        //将我们生成的图片写到接口的响应的输出流中
        ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
        
    }
    
    //生成图片验证码(验证码,图片,失效的时间)
    private ImageCode createImageCode(HttpServletRequest request)
    {
        //定义图片的长和宽
        int width = ServletRequestUtils.getIntParameter(request, "width", garySecurityProperties.getCode().getImage().getWidth());
        int height =  ServletRequestUtils.getIntParameter(request, "height", garySecurityProperties.getCode().getImage().getHeight());;
        
        //生成一张图片
        BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        
        //获得画笔工具
        Graphics g = image.getGraphics();
        
        //画一个矩形
        g.setColor(new Color(255,255,255));
        g.fillRect(0, 0, width, height);
        
        //画干扰线
        g.setColor(new Color(0,0,0));
        //设置字体
        g.setFont(new Font("Time New Roman",Font.ITALIC,20));
        Random random = new Random();
        
        for(int i=0;i<20;i++)
        {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int x1 = random.nextInt(12);
            int y1 = random.nextInt(12);
            //(x,y)到(x+x1,y+y1)
            g.drawLine(x, y, x+x1, y+y1);
        }
        
        //画数据
        String sRand = "";
        for(int i = 0;i<garySecurityProperties.getCode().getImage().getLength();i++)
        {
            String rand =String.valueOf(random.nextInt(10));
            //System.out.println(rand);
            sRand += rand;
            //每一个字都改变一下颜色
            g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110),20+random.nextInt(110)));
            //画每一个数据
            g.drawString(rand, 13*i, 16);
        }
        
        g.dispose();
        
        //生成我们自己的验证码数据(图片,验证码,过期时间)
        return new ImageCode(image,sRand,garySecurityProperties.getCode().getImage().getExpireIn());
    }
    
}
ValidateCodeController.java

 

package com.Gary.GaryRESTful.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "gary.security")
public class GarySecurityProperties {
    
    //LoginType登陆的方式,默认为JSON(restful设计风格)
    private LoginType loginType = LoginType.JSON;

    private ValidateCodeProperties code = new ValidateCodeProperties();
    
    public ValidateCodeProperties getCode() {
        return code;
    }

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

    public LoginType getLoginType() {
        return loginType;
    }

    public void setLoginType(LoginType loginType) {
        this.loginType = loginType;
    }
    
    
    
}
GarySecurityProperties.java

 

package com.Gary.GaryRESTful.properties;

public class ImageCodeProperties {

    private int width = 67;
    private int height = 23;
    
    private int length = 4;
    private int expireIn = 60;
    
    public int getWidth() {
        return width;
    }
    public void setWidth(int width) {
        this.width = width;
    }
    public int getHeight() {
        return height;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public int getLength() {
        return length;
    }
    public void setLength(int length) {
        this.length = length;
    }
    public int getExpireIn() {
        return expireIn;
    }
    public void setExpireIn(int expireIn) {
        this.expireIn = expireIn;
    }
    
    
    
}
ImageCodeProperties.java

 

package com.Gary.GaryRESTful.properties;

public class ValidateCodeProperties {

    //图片验证码
    private ImageCodeProperties image;

    public ImageCodeProperties getImage() {
        return image;
    }

    public void setImage(ImageCodeProperties image) {
        this.image = image;
    }
    
    
    
}
ValidateCodeProperties.java

 

  优化:配置Filter哪些请求需要拦截器执行

  ImageCodeProperties.java中添加一个String类型的url

  application.properties中添加一个gary.security.code.image.url,用来配置哪些需要我们验证码的Filter

  在ValidateCodeFilter.java将用户请求的url进行切割保存到Set集合当中,遍历Set集合看是否有请求与我们request中的url一致

//在garySecurityProperties.code.image.url    /user,/user/*
    //当Bean组装好之后回调用这个函数
    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
        
        //切割用户配置的url
        String[] configUrls = StringUtils.split(garySecurityProperties.getCode().getImage().getUrl(), ",");
        
        //将数组放入urls中
        for(String configURL : configUrls)
        {
            urls.add(configURL);
        }
        //loginPage一定会用到这个Filter,所以我们必须加上
        urls.add("/loginPage");
            
    }

 

  doFilterInternal()通过循环判断是否有匹配的路径

//Filter执行
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        
        //loginPage
        //if(StringUtils.pathEquals("/loginPage", request.getRequestURI()) && StringUtils.startsWithIgnoreCase(request.getMethod(), "post"))
        
        //判断是否有匹配的路径
        boolean action = false;
        
        //循环判断
        for(String url :urls)
        {
            if(antPathMatcher.match(url, request.getRequestURI()))
            {
                action = true;
            }
        }
        
        if(action)
        {
            //filter才会执行
            try
            {
                validate(new ServletWebRequest(request));
            }
            catch(ValidateCodeException e) {
                //判处验证码的异常
                authenticationFailureHandler.onAuthenticationFailure(request,response,e);
                //一旦出现异常,我们就不就不能继续执行(应该放行),应该return
                return;
            }
            
        }
        
        //放行
        filterChain.doFilter(request, response);
        
    }

 

 

#datasource
spring.datasource.url=jdbc:mysql:///springsecurity?serverTimezone=UTC&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.dricer-class-name=com.mysql.jdbc.Driver

#jpa
#打印出数据库语句
spring.jpa.show-sql=true
#更新数据库表
spring.jpa.hibernate.ddl-auto=update

#配置登陆方式
gary.security.loginType = JSON

server.port=8081

#验证码长度
gary.security.code.image.length = 6
#验证码图片的长
gary.security.code.image.width = 100

#配置哪些需要我们验证码的Filter
gary.security.code.image.url = /user,/user/*
application.properties

 

package com.Gary.GaryRESTful.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.Gary.GaryRESTful.filter.ValidateCodeFilter;
import com.Gary.GaryRESTful.handler.LoginFailureHandler;
import com.Gary.GaryRESTful.handler.LoginSuccessHandler;
import com.Gary.GaryRESTful.properties.GarySecurityProperties;


//Web应用安全适配器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    //告诉SpringSecurity密码用什么加密的
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
    
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private LoginFailureHandler loginFailureHandler;
     
    @Autowired
    private GarySecurityProperties garySecurityProperties;
    
    protected void configure(HttpSecurity http) throws Exception{
        
        //声明我们自己写的过滤器
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        //给过滤器赋值
        validateCodeFilter.setAuthenticationFailureHandler(loginFailureHandler);
        validateCodeFilter.setGarySecurityProperties(garySecurityProperties);
        validateCodeFilter.afterPropertiesSet();
        
        //表单验证(身份认证)
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
            
            .formLogin()
            //自定义登陆页面
            .loginPage("/require")
            //如果URL为loginPage,则用SpringSecurity中自带的过滤器去处理该请求
            .loginProcessingUrl("/loginPage")
            //配置登陆成功调用loginSuccessHandler
            .successHandler(loginSuccessHandler)
            //配置登陆失败调用loginFailureHandler
            .failureHandler(loginFailureHandler)
            .and()
            //请求授权
            .authorizeRequests()
            //在访问我们的URL时,我们是不需要省份认证,可以立即访问
            .antMatchers("/login.html","/require","/code/image").permitAll()
            //所有请求都被拦截,跳转到(/login请求中)
            .anyRequest()
            //都需要我们身份认证
            .authenticated()
            //SpringSecurity保护机制
            .and().csrf().disable();
    }
    
}
SecurityConfig.java

 

package com.Gary.GaryRESTful.filter;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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.util.StringUtils;
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 com.Gary.GaryRESTful.controller.ValidateCodeController;
import com.Gary.GaryRESTful.exception.ValidateCodeException;
import com.Gary.GaryRESTful.properties.GarySecurityProperties;
import com.Gary.GaryRESTful.validate.code.ImageCode;


//继承OncePerRequestFilter,保证这个filter只会执行一次
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean{

    private AuthenticationFailureHandler authenticationFailureHandler;
    
    //操作session域的工具
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
    private GarySecurityProperties garySecurityProperties;
    
    private Set<String> urls = new HashSet<String>();
    
    //为了处理/user/*的形式 
    private AntPathMatcher antPathMatcher = new AntPathMatcher();
    
    //在garySecurityProperties.code.image.url    /user,/user/*
    //当Bean组装好之后回调用这个函数
    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
        
        //切割用户配置的url
        String[] configUrls = StringUtils.split(garySecurityProperties.getCode().getImage().getUrl(), ",");
        
        //将数组放入urls中
        for(String configURL : configUrls)
        {
            urls.add(configURL);
        }
        //loginPage一定会用到这个Filter,所以我们必须加上
        urls.add("/loginPage");
            
    }
    
    
    public GarySecurityProperties getGarySecurityProperties() {
        return garySecurityProperties;
    }

    public void setGarySecurityProperties(GarySecurityProperties garySecurityProperties) {
        this.garySecurityProperties = garySecurityProperties;
    }

    //Filter执行
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        
        //loginPage
        //if(StringUtils.pathEquals("/loginPage", request.getRequestURI()) && StringUtils.startsWithIgnoreCase(request.getMethod(), "post"))
        
        //判断是否有匹配的路径
        boolean action = false;
        
        //循环判断
        for(String url :urls)
        {
            if(antPathMatcher.match(url, request.getRequestURI()))
            {
                action = true;
            }
        }
        
        if(action)
        {
            //filter才会执行
            try
            {
                validate(new ServletWebRequest(request));
            }
            catch(ValidateCodeException e) {
                //判处验证码的异常
                authenticationFailureHandler.onAuthenticationFailure(request,response,e);
                //一旦出现异常,我们就不就不能继续执行(应该放行),应该return
                return;
            }
            
        }
        
        //放行
        filterChain.doFilter(request, response);
        
    }

    //校验验证码是否正确
    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        
        // 获得session域中正确的验证码
        ImageCode codeInSession =  (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.sessionKey);
        // 获得request域中的用户输入的验证码imageCode
        String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");

        // 判断用户输入的验证码是否为空
        if(StringUtils.isEmpty(codeInRequest))
        {
            throw new ValidateCodeException("验证码不能为空");
        }
        
        // 判断session域中的验证码是否为null
        if(codeInSession == null)
        {
            throw new ValidateCodeException("验证码不存在");
        }
        
        // 判断验证码是否过期
        if(codeInSession.isExpried())
        {
            throw new ValidateCodeException("验证码已过期");
        }
        
        // 校验两个验证码是否匹配
        if(!StringUtils.pathEquals(codeInSession.getCode(), codeInRequest))
        {
            //System.out.println("正确的:"+codeInSession.getCode());
            //System.out.println("用户输入的:"+codeInRequest);
            throw new ValidateCodeException("验证码不匹配");
        }
        
        // 将验证码从session域中移除
        sessionStrategy.removeAttribute(request, ValidateCodeController.sessionKey);
        
    }

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

}
ValidateCodeFilter.java

 

 

 

posted @ 2019-11-07 16:57  Cynical丶Gary  阅读(683)  评论(0编辑  收藏  举报