SpringSecurity实现图形验证码功能
⒈封装验证码类
1 package cn.coreqi.security.validate; 2 3 import java.awt.image.BufferedImage; 4 import java.time.LocalDateTime; 5 6 public class ImageCode { 7 private BufferedImage image; 8 private String code; 9 private LocalDateTime expireTime; //过期时间 10 11 public ImageCode(BufferedImage image, String code, Integer expireIn) { 12 this.image = image; 13 this.code = code; 14 this.expireTime = LocalDateTime.now().plusSeconds(expireIn); 15 } 16 17 public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) { 18 this.image = image; 19 this.code = code; 20 this.expireTime = expireTime; 21 } 22 23 public boolean isExpried(){ 24 return LocalDateTime.now().isAfter(expireTime); 25 } 26 27 public BufferedImage getImage() { 28 return image; 29 } 30 31 public void setImage(BufferedImage image) { 32 this.image = image; 33 } 34 35 public String getCode() { 36 return code; 37 } 38 39 public void setCode(String code) { 40 this.code = code; 41 } 42 43 public LocalDateTime getExpireTime() { 44 return expireTime; 45 } 46 47 public void setExpireTime(LocalDateTime expireTime) { 48 this.expireTime = expireTime; 49 } 50 }
⒉封装验证码控制器
1 package cn.coreqi.security.controller; 2 3 import cn.coreqi.security.validate.ImageCode; 4 import com.sun.image.codec.jpeg.JPEGCodec; 5 import com.sun.image.codec.jpeg.JPEGImageEncoder; 6 import org.springframework.social.connect.web.HttpSessionSessionStrategy; 7 import org.springframework.social.connect.web.SessionStrategy; 8 import org.springframework.web.bind.annotation.GetMapping; 9 import org.springframework.web.bind.annotation.RestController; 10 import org.springframework.web.context.request.ServletWebRequest; 11 12 import javax.imageio.ImageIO; 13 import javax.servlet.http.HttpServletRequest; 14 import javax.servlet.http.HttpServletResponse; 15 import java.awt.*; 16 import java.awt.image.BufferedImage; 17 import java.io.IOException; 18 import java.util.Random; 19 20 @RestController 21 public class ValidateController { 22 23 public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE"; 24 private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); 25 26 @GetMapping("code/image") 27 public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException { 28 ImageCode imageCode = createImageCode(request); 29 sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode); 30 31 response.setHeader("Pragma","No-cache"); 32 response.setHeader("Cache-Control","no-cache"); 33 //response.setDateHeader("Expires", 0); 34 35 JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(response.getOutputStream()); 36 encoder.encode(imageCode.getImage()); 37 38 //ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream()); //当tomcat下temp文件夹不存在则"Can't create output stream" 39 } 40 41 private ImageCode createImageCode(HttpServletRequest request) { 42 int width = 67; 43 int height = 23; 44 BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); 45 46 Graphics g = image.getGraphics(); 47 48 Random random = new Random(); 49 50 g.setColor(getRandColor(200,250)); 51 g.fillRect(0,0,width,height); 52 g.setFont(new Font("Times New Roman",Font.ITALIC,20)); 53 g.setColor(getRandColor(160,200)); 54 for (int i = 0;i < 155; i++){ 55 int x = random.nextInt(width); 56 int y = random.nextInt(height); 57 int xl = random.nextInt(12); 58 int yl = random.nextInt(12); 59 g.drawLine(x,y,x+xl,y+yl); 60 } 61 String sRand = ""; 62 for(int i = 0;i < 4; i++){ 63 String rand = String.valueOf(random.nextInt(10)); 64 sRand += rand; 65 g.setColor(new Color(20 + random.nextInt(110),20 + random.nextInt(110),20 + random.nextInt(110))); 66 g.drawString(rand,13 * i + 6,16); 67 } 68 g.dispose(); 69 return new ImageCode(image,sRand,60); 70 } 71 72 /** 73 * 生成随机背景条纹 74 * @param fc 75 * @param bc 76 * @return 77 */ 78 private Color getRandColor(int fc, int bc) { 79 Random random = new Random(); 80 if(fc > 255){ 81 fc = 255; 82 } 83 if(bc > 255){ 84 bc = 255; 85 } 86 int r = fc + random.nextInt(bc - fc); 87 int g = fc + random.nextInt(bc - fc); 88 int b = fc + random.nextInt(bc - fc); 89 return new Color(r,g,b); 90 } 91 }
⒊放行验证码的Rest地址
⒋表单添加验证码
1 <tr> 2 <td>图形验证码:</td> 3 <td> 4 <input type="text" name="imageCode"> 5 <img src="/code/image"> 6 </td> 7 </tr>
⒌声明一个验证码异常,用于抛出特定的验证码异常
1 package cn.coreqi.security.validate; 2 3 import org.springframework.security.core.AuthenticationException; 4 5 public class ValidateCodeException extends AuthenticationException { 6 public ValidateCodeException(String msg) { 7 super(msg); 8 } 9 }
⒍创建一个过滤器,用于验证请求中的验证码是否正确
1 package cn.coreqi.security.Filter; 2 3 import cn.coreqi.security.validate.ImageCode; 4 import cn.coreqi.security.validate.ValidateCodeException; 5 import org.springframework.security.web.authentication.AuthenticationFailureHandler; 6 import org.springframework.social.connect.web.HttpSessionSessionStrategy; 7 import org.springframework.social.connect.web.SessionStrategy; 8 import org.springframework.util.StringUtils; 9 import org.springframework.web.bind.ServletRequestBindingException; 10 import org.springframework.web.bind.ServletRequestUtils; 11 import org.springframework.web.context.request.ServletWebRequest; 12 import org.springframework.web.filter.OncePerRequestFilter; 13 import cn.coreqi.security.controller.*; 14 15 import javax.servlet.FilterChain; 16 import javax.servlet.ServletException; 17 import javax.servlet.http.HttpServletRequest; 18 import javax.servlet.http.HttpServletResponse; 19 import java.io.IOException; 20 21 public class ValidateCodeFilter extends OncePerRequestFilter { 22 23 private AuthenticationFailureHandler authenticationFailureHandler; 24 25 private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); 26 27 public AuthenticationFailureHandler getAuthenticationFailureHandler() { 28 return authenticationFailureHandler; 29 } 30 31 public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { 32 this.authenticationFailureHandler = authenticationFailureHandler; 33 } 34 35 public SessionStrategy getSessionStrategy() { 36 return sessionStrategy; 37 } 38 39 public void setSessionStrategy(SessionStrategy sessionStrategy) { 40 this.sessionStrategy = sessionStrategy; 41 } 42 43 @Override 44 protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { 45 if (httpServletRequest.equals("/authentication/form") && httpServletRequest.getMethod().equals("post")) { 46 try { 47 validate(new ServletWebRequest(httpServletRequest)); 48 49 }catch (ValidateCodeException e){ 50 authenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e); 51 return; 52 } 53 } 54 filterChain.doFilter(httpServletRequest,httpServletResponse); //如果不是登录请求,直接调用后面的过滤器链 55 } 56 57 private void validate(ServletWebRequest request) throws ServletRequestBindingException { 58 ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request,ValidateController.SESSION_KEY); 59 String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),"imageCode"); 60 if(!StringUtils.hasText(codeInRequest)){ 61 throw new ValidateCodeException("验证码的值不能为空!"); 62 } 63 if(codeInSession == null){ 64 throw new ValidateCodeException("验证码不存在!"); 65 } 66 if(codeInSession.isExpried()){ 67 sessionStrategy.removeAttribute(request,ValidateController.SESSION_KEY); 68 throw new ValidateCodeException("验证码已过期!"); 69 } 70 if(!codeInSession.getCode().equals(codeInRequest)){ 71 throw new ValidateCodeException("验证码不正确!"); 72 } 73 sessionStrategy.removeAttribute(request,ValidateController.SESSION_KEY); 74 } 75 }
⒎在SpringSecurity过滤器链中注册我们的过滤器
1 package cn.coreqi.security.config; 2 3 import cn.coreqi.security.Filter.ValidateCodeFilter; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Configuration; 7 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 import org.springframework.security.crypto.password.NoOpPasswordEncoder; 10 import org.springframework.security.crypto.password.PasswordEncoder; 11 import org.springframework.security.web.authentication.AuthenticationFailureHandler; 12 import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 13 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 14 15 @Configuration 16 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 17 18 @Autowired 19 private AuthenticationSuccessHandler coreqiAuthenticationSuccessHandler; 20 21 @Autowired 22 private AuthenticationFailureHandler coreqiAuthenticationFailureHandler; 23 24 @Bean 25 public PasswordEncoder passwordEncoder(){ 26 return NoOpPasswordEncoder.getInstance(); 27 } 28 29 @Override 30 protected void configure(HttpSecurity http) throws Exception { 31 ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter(); 32 validateCodeFilter.setAuthenticationFailureHandler(coreqiAuthenticationFailureHandler); 33 34 //http.httpBasic() //httpBasic登录 BasicAuthenticationFilter 35 http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) //加载用户名密码过滤器的前面 36 .formLogin() //表单登录 UsernamePasswordAuthenticationFilter 37 .loginPage("/coreqi-signIn.html") //指定登录页面 38 //.loginPage("/authentication/require") 39 .loginProcessingUrl("/authentication/form") //指定表单提交的地址用于替换UsernamePasswordAuthenticationFilter默认的提交地址 40 .successHandler(coreqiAuthenticationSuccessHandler) //登录成功以后要用我们自定义的登录成功处理器,不用Spring默认的。 41 .failureHandler(coreqiAuthenticationFailureHandler) //自己体会把 42 .and() 43 .authorizeRequests() //对授权请求进行配置 44 .antMatchers("/coreqi-signIn.html","/code/image").permitAll() //指定登录页面不需要身份认证 45 .anyRequest().authenticated() //任何请求都需要身份认证 46 .and().csrf().disable(); //禁用CSRF 47 //FilterSecurityInterceptor 整个SpringSecurity过滤器链的最后一环 48 } 49 }