系列博文html
项目已上传至guthub 传送门java
JavaWeb-SpringSecurity初认识 传送门mysql
JavaWeb-SpringSecurity在数据库中查询登录用户 传送门git
JavaWeb-SpringSecurity自定义登录页面 传送门github
JavaWeb-SpringSecurity实现需求-判断请求是否以html结尾 传送门web
JavaWeb-SpringSecurity自定义登录配置 传送门spring
JavaWeb-SpringSecurity图片验证ImageCode 传送门sql
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; } }
在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; } }
编写验证码的逻辑
一、定义验证码图片长与宽
二、获取画笔工具
三、设置画笔工具的颜色
四、画长方形
五、改变画笔工具的颜色
六、画干扰线
七、改变画笔工具的颜色
八、画数据
九、关闭画笔工具
登录页面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); } }
处理验证码拦截器,添加处理图片验证码的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>
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(); } }
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; } }
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); } }
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 } }
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; } }
优化:增长代码的重用性
在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
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()); } }
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; } }
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; } }
package com.Gary.GaryRESTful.properties; public class ValidateCodeProperties { //图片验证码 private ImageCodeProperties image; public ImageCodeProperties getImage() { return image; } public void setImage(ImageCodeProperties image) { this.image = image; } }
优化:配置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/*
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(); } }
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; } }