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