系列博文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; } }