Spring Security 圖片驗(yàn)證碼功能的實(shí)例代碼
驗(yàn)證碼邏輯
以前在項(xiàng)目中也做過(guò)驗(yàn)證碼,生成驗(yàn)證碼的代碼網(wǎng)上有很多,也有一些第三方的jar包也可以生成漂亮的驗(yàn)證碼。驗(yàn)證碼邏輯很簡(jiǎn)單,就是在登錄頁(yè)放一個(gè)image標(biāo)簽,src指向一個(gè)controller,這個(gè)Controller返回把生成的圖片以輸出流返回給頁(yè)面,生成圖片的同時(shí)把圖片上的文本放在session,登錄的時(shí)候帶過(guò)來(lái)輸入的驗(yàn)證碼,從session中取出,兩者對(duì)比。這位老師講的用Spring Security集成驗(yàn)證碼,大體思路和我說(shuō)的一樣,但更加規(guī)范和通用些。
spring security是一系列的過(guò)濾器鏈,所以在這里驗(yàn)證碼也聲明為過(guò)濾器,加在過(guò)濾器鏈的 登錄過(guò)濾器之前,然后自定義一個(gè)異常類,來(lái)響應(yīng)驗(yàn)證碼的錯(cuò)誤信息。

代碼結(jié)構(gòu):
驗(yàn)證碼代碼放在core項(xiàng)目,在browser項(xiàng)目做一下配置。

主要代碼:
1,ImageCode:
首先是ImageCode類,封裝驗(yàn)證碼圖片、文本、過(guò)期時(shí)間
package com.imooc.security.core.validate.code;
import java.awt.image.BufferedImage;
import java.time.LocalDateTime;
import java.time.LocalTime;
/**
* 驗(yàn)證碼
* ClassName: ImageCode
* @Description: 驗(yàn)證碼
* @author lihaoyang
* @date 2018年3月1日
*/
public class ImageCode {
private BufferedImage image;
private String code;
private LocalDateTime expireTime;//過(guò)期時(shí)間點(diǎn)
/**
*
* <p>Description: </p>
* @param image
* @param code
* @param expireTn 多少秒過(guò)期
*/
public ImageCode(BufferedImage image, String code, int expireTn) {
super();
this.image = image;
this.code = code;
//過(guò)期時(shí)間=當(dāng)前時(shí)間+過(guò)期秒數(shù)
this.expireTime = LocalDateTime.now().plusSeconds(expireTn);
}
public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
super();
this.image = image;
this.code = code;
this.expireTime = expireTime;
}
/**
* 驗(yàn)證碼是否過(guò)期
* @Description: 驗(yàn)證碼是否過(guò)期
* @param @return true 過(guò)期,false 沒(méi)過(guò)期
* @return boolean true 過(guò)期,false 沒(méi)過(guò)期
* @throws
* @author lihaoyang
* @date 2018年3月2日
*/
public boolean isExpired(){
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;
}
}
VerifyCode:生成驗(yàn)證碼的工具類,在這里http://www.cnblogs.com/lihaoyang/p/7131512.html 當(dāng)然也可以使用第三方j(luò)ar包,無(wú)所謂。
ValidateCodeException:封裝驗(yàn)證碼異常
/**
* @Title: ValidateCodeException.java
* @Package com.imooc.security.core.validate.code
* @Description: TODO
* @author lihaoyang
* @date 2018年3月2日
*/
package com.imooc.security.core.validate.code;
import org.springframework.security.core.AuthenticationException;
/**
* ClassName: ValidateCodeException
* @Description: 驗(yàn)證碼錯(cuò)誤異常,繼承spring security的認(rèn)證異常
* @author lihaoyang
* @date 2018年3月2日
*/
public class ValidateCodeException extends AuthenticationException {
/**
* @Fields serialVersionUID : TODO
*/
private static final long serialVersionUID = 1L;
public ValidateCodeException(String msg) {
super(msg);
}
}
ValidateCodeFilter:驗(yàn)證碼過(guò)濾器
邏輯:繼承OncePerRequestFilter 保證過(guò)濾器每次只會(huì)被調(diào)用一次(不太清楚為什么),注入認(rèn)證失敗處理器,在驗(yàn)證失敗時(shí)調(diào)用。
package com.imooc.security.core.validate.code;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
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;
/**
* 處理登錄驗(yàn)證碼過(guò)濾器
* ClassName: ValidateCodeFilter
* @Description:
* OncePerRequestFilter:spring提供的工具,保證過(guò)濾器每次只會(huì)被調(diào)用一次
* @author lihaoyang
* @date 2018年3月2日
*/
public class ValidateCodeFilter extends OncePerRequestFilter{
//認(rèn)證失敗處理器
private AuthenticationFailureHandler authenticationFailureHandler;
//獲取session工具類
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//如果是 登錄請(qǐng)求 則執(zhí)行
if(StringUtils.equals("/authentication/form", request.getRequestURI())
&&StringUtils.equalsIgnoreCase(request.getMethod(), "post")){
try {
validate(new ServletWebRequest(request));
} catch (ValidateCodeException e) {
//調(diào)用錯(cuò)誤處理器,最終調(diào)用自己的
authenticationFailureHandler.onAuthenticationFailure(request, response, e);
return ;//結(jié)束方法,不再調(diào)用過(guò)濾器鏈
}
}
//不是登錄請(qǐng)求,調(diào)用其它過(guò)濾器鏈
filterChain.doFilter(request, response);
}
/**
* 校驗(yàn)驗(yàn)證碼
* @Description: 校驗(yàn)驗(yàn)證碼
* @param @param request
* @param @throws ServletRequestBindingException
* @return void
* @throws ValidateCodeException
* @author lihaoyang
* @date 2018年3月2日
*/
private void validate(ServletWebRequest request) throws ServletRequestBindingException {
//拿出session中的ImageCode對(duì)象
ImageCode imageCodeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);
//拿出請(qǐng)求中的驗(yàn)證碼
String imageCodeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
//校驗(yàn)
if(StringUtils.isBlank(imageCodeInRequest)){
throw new ValidateCodeException("驗(yàn)證碼不能為空");
}
if(imageCodeInSession == null){
throw new ValidateCodeException("驗(yàn)證碼不存在,請(qǐng)刷新驗(yàn)證碼");
}
if(imageCodeInSession.isExpired()){
//從session移除過(guò)期的驗(yàn)證碼
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
throw new ValidateCodeException("驗(yàn)證碼已過(guò)期,請(qǐng)刷新驗(yàn)證碼");
}
if(!StringUtils.equalsIgnoreCase(imageCodeInSession.getCode(), imageCodeInRequest)){
throw new ValidateCodeException("驗(yàn)證碼錯(cuò)誤");
}
//驗(yàn)證通過(guò),移除session中驗(yàn)證碼
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
}
public AuthenticationFailureHandler getAuthenticationFailureHandler() {
return authenticationFailureHandler;
}
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
}
ValidateCodeController:生成驗(yàn)證碼Control
package com.imooc.security.core.validate.code;
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;
/**
* 驗(yàn)證碼Control
* ClassName: ValidateCodeController
* @Description: TODO
* @author lihaoyang
* @date 2018年3月1日
*/
@RestController
public class ValidateCodeController {
public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
//獲取session
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@GetMapping("/verifycode/image")
public void createCode(HttpServletRequest request,HttpServletResponse response) throws IOException{
ImageCode imageCode = createImageCode(request, response);
sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, imageCode);
ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
}
private ImageCode createImageCode(HttpServletRequest request, HttpServletResponse response) {
VerifyCode verifyCode = new VerifyCode();
return new ImageCode(verifyCode.getImage(),verifyCode.getText(),60);
}
}
BrowserSecurityConfig里進(jìn)行過(guò)濾器配置:
package com.imooc.security.browser;
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.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.imooc.security.core.properties.SecurityProperties;
import com.imooc.security.core.validate.code.ValidateCodeFilter;
@Configuration //這是一個(gè)配置
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter{
//讀取用戶配置的登錄頁(yè)配置
@Autowired
private SecurityProperties securityProperties;
//自定義的登錄成功后的處理器
@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
//自定義的認(rèn)證失敗后的處理器
@Autowired
private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
//注意是org.springframework.security.crypto.password.PasswordEncoder
@Bean
public PasswordEncoder passwordencoder(){
//BCryptPasswordEncoder implements PasswordEncoder
return new BCryptPasswordEncoder();
}
//版本二:可配置的登錄頁(yè)
@Override
protected void configure(HttpSecurity http) throws Exception {
//驗(yàn)證碼過(guò)濾器
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
//驗(yàn)證碼過(guò)濾器中使用自己的錯(cuò)誤處理
validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
//實(shí)現(xiàn)需要認(rèn)證的接口跳轉(zhuǎn)表單登錄,安全=認(rèn)證+授權(quán)
//http.httpBasic() //這個(gè)就是默認(rèn)的彈框認(rèn)證
//
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)//把驗(yàn)證碼過(guò)濾器加載登錄過(guò)濾器前邊
.formLogin() //表單認(rèn)證
.loginPage("/authentication/require") //處理用戶認(rèn)證BrowserSecurityController
//登錄過(guò)濾器UsernamePasswordAuthenticationFilter默認(rèn)登錄的url是"/login",在這能改
.loginProcessingUrl("/authentication/form")
.successHandler(imoocAuthenticationSuccessHandler)//自定義的認(rèn)證后處理器
.failureHandler(imoocAuthenticationFailureHandler) //登錄失敗后的處理
.and()
.authorizeRequests() //下邊的都是授權(quán)的配置
// /authentication/require:處理登錄,securityProperties.getBrowser().getLoginPage():用戶配置的登錄頁(yè)
.antMatchers("/authentication/require",
securityProperties.getBrowser().getLoginPage(),//放過(guò)登錄頁(yè)不過(guò)濾,否則報(bào)錯(cuò)
"/verifycode/image").permitAll() //驗(yàn)證碼
.anyRequest() //任何請(qǐng)求
.authenticated() //都需要身份認(rèn)證
.and()
.csrf().disable() //關(guān)閉csrf防護(hù)
;
}
}
登陸頁(yè):登陸頁(yè)做的比較粗糙,其實(shí)驗(yàn)證碼可以在驗(yàn)證碼input失去焦點(diǎn)的時(shí)候做校驗(yàn),還可以做個(gè)點(diǎn)擊圖片刷新驗(yàn)證碼功能,這里就不做了。
<body> demo 登錄頁(yè). <br> <form action="/authentication/form" method="post"> <table> <tr> <td>用戶名:</td> <td><input type="text" name="username"/></td> <td></td> </tr> <tr> <td>密碼:</td> <td><input type="password" name="password"/></td> <td></td> </tr> <tr> <td>驗(yàn)證碼:</td> <td> <input width="100" type="text" name="imageCode"/> </td> <td> <img src="/verifycode/image"/> </td> </tr> <tr> <td colspan="2" align="right"><button type="submit">登錄</button></td> </tr> </table> </form> </body>
訪問(wèn) http://localhost:8080/demo-login.html:

響應(yīng)自定義的異常信息

大體功能已經(jīng)沒(méi)問(wèn)題了。但是不夠通用,比如驗(yàn)證碼圖片的寬高、過(guò)期時(shí)間、過(guò)濾的url、驗(yàn)證碼成邏輯都是寫(xiě)死的。這些可以做成活的,現(xiàn)在把驗(yàn)證碼做成一個(gè)過(guò)濾器的好處體現(xiàn)出來(lái)了。我們可以配置需要過(guò)濾的url,有時(shí)候可能不只是登陸頁(yè)需要驗(yàn)證碼,這樣更加通用。
1,通用性改造 之 驗(yàn)證碼基本參數(shù)可配
做成可配置的,那個(gè)應(yīng)用引用該模塊,他自己配置去,不配置就使用默認(rèn)配置。而且,配置既可以在請(qǐng)求url中聲明,也可以在應(yīng)用中聲明,老師的確是老師,代碼通用性真好!

想要實(shí)現(xiàn)的效果是,在application.properties里做這樣的配置:
#驗(yàn)證碼 圖片寬、高、字符個(gè)數(shù) imooc.security.code.image.width = 100 imooc.security.code.image.height = 30 imooc.security.code.image.length = 6
然后就能控制驗(yàn)證碼的效果,因?yàn)轵?yàn)證碼還分圖片驗(yàn)證碼、短信驗(yàn)證碼,所以多做了一級(jí).code.image,這就用到了springboot的自定義配置文件,需要聲明對(duì)應(yīng)的java類:

需要在SecurityProperties里聲明code屬性:
package com.imooc.security.core.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 自定義配置項(xiàng)
* ClassName: SecurityProperties
* @Description: 自定義配置項(xiàng)
* 這個(gè)類會(huì)讀取application.properties里所有以imooc.security開(kāi)頭的配置項(xiàng)
*
* imooc.security.browser.loginPage = /demo-login.html
* 其中的browser的配置會(huì)讀取到BrowserProperties中去
* 這是以點(diǎn)分割的,一級(jí)一級(jí)的和類的屬性對(duì)應(yīng)
* @author lihaoyang
* @date 2018年2月28日
*/
@ConfigurationProperties(prefix="imooc.security")
public class SecurityProperties {
private BrowserProperties browser = new BrowserProperties();
private ValidateCodeProperties code = new ValidateCodeProperties();
public BrowserProperties getBrowser() {
return browser;
}
public void setBrowser(BrowserProperties browser) {
this.browser = browser;
}
public ValidateCodeProperties getCode() {
return code;
}
public void setCode(ValidateCodeProperties code) {
this.code = code;
}
}
ValidateCodeProperties:
package com.imooc.security.core.properties;
/**
* 驗(yàn)證碼配置
* ClassName: ValidateCodeProperties
* @Description: 驗(yàn)證碼配置,驗(yàn)證碼有圖片驗(yàn)證碼、短信驗(yàn)證碼等,所以再包一層
* @author lihaoyang
* @date 2018年3月2日
*/
public class ValidateCodeProperties {
//默認(rèn)配置
private ImageCodeProperties image = new ImageCodeProperties();
public ImageCodeProperties getImage() {
return image;
}
public void setImage(ImageCodeProperties image) {
this.image = image;
}
}
ImageCodeProperties:
package com.imooc.security.core.properties;
/**
* 圖片驗(yàn)證碼配置類
* ClassName: ImageCodeProperties
* @Description: 圖片驗(yàn)證碼配置類
* @author lihaoyang
* @date 2018年3月2日
*/
public class ImageCodeProperties {
//圖片寬
private int width = 67;
//圖片高
private int height = 23;
//驗(yàn)證碼字符個(gè)數(shù)
private int length = 4;
//過(guò)期時(shí)間
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;
}
}
請(qǐng)求級(jí)的配置,如果請(qǐng)求里帶的有驗(yàn)證碼的參數(shù),就用請(qǐng)求里的:

在ValidateCodeController的createImageCode方法做控制,判斷請(qǐng)求參數(shù)是否有這些參數(shù),有的話,傳給驗(yàn)證碼生成類VerifyCode,在生成的時(shí)候就能動(dòng)態(tài)控制了。
private ImageCode createImageCode(HttpServletRequest request, HttpServletResponse response) {
//先從request里讀取有沒(méi)有長(zhǎng)、寬、字符個(gè)數(shù)參數(shù),有的話就用,沒(méi)有用默認(rèn)的
int width = ServletRequestUtils.getIntParameter(request, "width",securityProperties.getCode().getImage().getWidth());
int height = ServletRequestUtils.getIntParameter(request, "height",securityProperties.getCode().getImage().getHeight());
int charLength = this.securityProperties.getCode().getImage().getLength();
VerifyCode verifyCode = new VerifyCode(width,height,charLength);
return new ImageCode(verifyCode.getImage(),verifyCode.getText(),this.securityProperties.getCode().getImage().getExpireIn());
}
VerifyCode:
public VerifyCode(int w, int h, int charLength) {
super();
this.w = w;
this.h = h;
this.charLength = charLength;
}
實(shí)驗(yàn):在demo項(xiàng)目做應(yīng)用級(jí)配置

登錄表單做請(qǐng)求級(jí)配置
<img src="/verifycode/image?width=200"/>
訪問(wèn):

長(zhǎng)度為請(qǐng)求級(jí)帶的參數(shù)200,高為30,字符為配置的6個(gè)。
2,通用性改造 之 驗(yàn)證碼攔截的接口可配置
先要的效果就是再application.properties里能動(dòng)態(tài)配置需要攔截的接口:

ImageCodeProperties新增一個(gè)屬性:private String url; //攔截的url,來(lái)匹配上圖的配置。
核心,驗(yàn)證碼過(guò)濾器需要修改:
1,在攔截器里聲明一個(gè)set集合,用來(lái)存儲(chǔ)配置文件里配置的需要攔截的urls。
2,實(shí)現(xiàn)InitializingBean接口,目的: 在其他參數(shù)都組裝完畢的時(shí)候,初始化需要攔截的urls的值,重寫(xiě)afterPropertiesSet方法來(lái)實(shí)現(xiàn)。
3,注入SecurityProperties,讀取配置文件
4,實(shí)例化AntPathMatcher工具類,這是一個(gè)匹配器
5,在browser項(xiàng)目的BrowserSecurityConfig里設(shè)置調(diào)用一下afterPropertiesSet方法。
6,在引用該模塊的demo項(xiàng)目的application.properties里配置要過(guò)濾的url
ValidateCodeFilter:
/**
* 處理登錄驗(yàn)證碼過(guò)濾器
* ClassName: ValidateCodeFilter
* @Description:
* 繼承OncePerRequestFilter:spring提供的工具,保證過(guò)濾器每次只會(huì)被調(diào)用一次
* 實(shí)現(xiàn) InitializingBean接口的目的:
* 在其他參數(shù)都組裝完畢的時(shí)候,初始化需要攔截的urls的值
* @author lihaoyang
* @date 2018年3月2日
*/
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean{
//認(rèn)證失敗處理器
private AuthenticationFailureHandler authenticationFailureHandler;
//獲取session工具類
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
//需要攔截的url集合
private Set<String> urls = new HashSet<>();
//讀取配置
private SecurityProperties securityProperties;
//spring工具類
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
//讀取配置的攔截的urls
String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrl(), ",");
for (String configUrl : configUrls) {
urls.add(configUrl);
}
//登錄的請(qǐng)求一定攔截
urls.add("/authentication/form");
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
/**
* 可配置的驗(yàn)證碼校驗(yàn)
* 判斷請(qǐng)求的url和配置的是否有匹配的,匹配上了就過(guò)濾
*/
boolean action = false;
for(String url:urls){
if(antPathMatcher.match(url, request.getRequestURI())){
action = true;
}
}
if(action){
try {
validate(new ServletWebRequest(request));
} catch (ValidateCodeException e) {
//調(diào)用錯(cuò)誤處理器,最終調(diào)用自己的
authenticationFailureHandler.onAuthenticationFailure(request, response, e);
return ;//結(jié)束方法,不再調(diào)用過(guò)濾器鏈
}
}
//不是登錄請(qǐng)求,調(diào)用其它過(guò)濾器鏈
filterChain.doFilter(request, response);
}
//省略無(wú)關(guān)代碼,,,
}
BrowserSecurityConfig:

配置url:
#驗(yàn)證碼攔截的接口配置 imooc.security.code.image.url = /user,/user/*
測(cè)試:/user /user/1 被攔截了


訪問(wèn)登錄頁(yè),不寫(xiě)驗(yàn)證碼:

和預(yù)期一致。至此,動(dòng)態(tài)配置攔截接口完成
3,驗(yàn)證碼的生成邏輯可配置
寫(xiě)的比較好的程序,一般都開(kāi)放接口,可以讓用戶去自定義實(shí)現(xiàn),如果不實(shí)現(xiàn)就用默認(rèn)的實(shí)現(xiàn),下面來(lái)做這件事,使驗(yàn)證碼的生成可以自己實(shí)現(xiàn)。如果要想把驗(yàn)證碼的生成邏輯做成可配置的,就不能只寫(xiě)一個(gè)圖片驗(yàn)證碼生成器的類了,需要把驗(yàn)證碼生成提取成一個(gè)接口ValidateCodeGenerator,一個(gè)生成驗(yàn)證碼的方法generator()。因?yàn)轵?yàn)證碼還有圖片驗(yàn)證碼、短信驗(yàn)證碼等,這樣,我們?cè)谧约旱尿?yàn)證模塊里做一個(gè)默認(rèn)的實(shí)現(xiàn),如圖片驗(yàn)證碼的實(shí)現(xiàn)ImageCodeGenerator,在ImageCodeGenerator里我們不在該類上加@Component注解。然后使用寫(xiě)一個(gè)驗(yàn)證碼bean的配置類ValidateCodeBeanConfig,這個(gè)配置類配置各種需要的驗(yàn)證碼實(shí)現(xiàn)類bean如圖片驗(yàn)證碼實(shí)現(xiàn)imageCodeGenerator、短信驗(yàn)證碼等,他們返回類型都是ValidateCodeGenerator,使用@ConditionalOnMissingBean(name="imageCodeGenerator")注解,可以判斷如果當(dāng)前spring容器有名字為imageCodeGenerator的bean時(shí),就使用,沒(méi)有的話再配置,這樣如果別人引用了你的該模塊,如果別人自己實(shí)現(xiàn)了驗(yàn)證碼生成ValidateCodeGenerator接口,他們配置了實(shí)現(xiàn)類的name為imageCodeGenerator,就用他們自己的實(shí)現(xiàn),這樣就做到了程序的可擴(kuò)展性。

主要代碼:
代碼生成器接口ValidateCodeGenerator:
package com.imooc.security.core.validate.code;
import org.springframework.web.context.request.ServletWebRequest;
/**
* 驗(yàn)證碼生成接口
* ClassName: ValidateCodeGenerator
* @Description: TODO
* @author lihaoyang
* @date 2018年3月2日
*/
public interface ValidateCodeGenerator {
/**
* 圖片驗(yàn)證碼生成接口
* @Description: TODO
* @param @param request
* @param @return
* @return ImageCode
* @throws
* @author lihaoyang
* @date 2018年3月2日
*/
ImageCode generator(ServletWebRequest request);
}
圖片驗(yàn)證碼生成器實(shí)現(xiàn)ImageCodeGenerator:
package com.imooc.security.core.validate.code;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import com.imooc.security.core.properties.SecurityProperties;
/**
* 圖片驗(yàn)證碼生成類
* ClassName: ImageCodeGenerator
* @Description: TODO
* @author lihaoyang
* @date 2018年3月2日
*/
public class ImageCodeGenerator implements ValidateCodeGenerator {
@Autowired
private SecurityProperties securityProperties;
@Override
public ImageCode generator(ServletWebRequest request) {
//先從request里讀取有沒(méi)有長(zhǎng)、寬、字符個(gè)數(shù)參數(shù),有的話就用,沒(méi)有用默認(rèn)的
int width = ServletRequestUtils.getIntParameter(request.getRequest(), "width",securityProperties.getCode().getImage().getWidth());
int height = ServletRequestUtils.getIntParameter(request.getRequest(), "height",securityProperties.getCode().getImage().getHeight());
int charLength = this.securityProperties.getCode().getImage().getLength();
VerifyCode verifyCode = new VerifyCode(width,height,charLength);
return new ImageCode(verifyCode.getImage(),verifyCode.getText(),this.securityProperties.getCode().getImage().getExpireIn());
}
public SecurityProperties getSecurityProperties() {
return securityProperties;
}
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
}
ValidateCodeBeanConfig:
package com.imooc.security.core.validate.code;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.imooc.security.core.properties.SecurityProperties;
/**
* 配置驗(yàn)證碼生成接口ValidateCodeGenerator的實(shí)際實(shí)現(xiàn)類的Bean
* ClassName: ValidateCodeBeanConfig
* @Description:
* 配置驗(yàn)證碼生成接口ValidateCodeGenerator的實(shí)際實(shí)現(xiàn)類的Bean
* 如圖片驗(yàn)證碼的實(shí)現(xiàn)、短信驗(yàn)證碼的實(shí)現(xiàn)
* @author lihaoyang
* @date 2018年3月5日
*/
@Configuration
public class ValidateCodeBeanConfig {
@Autowired
private SecurityProperties securityProperties;
/**
* @Description:
* @ConditionalOnMissingBean注解意思是當(dāng)spring容器不存在imageCodeGenerator時(shí)才給配置一個(gè)該bean
* 作用是使程序更具可擴(kuò)展性,該配置類是配置在core模塊,這就意味著,如果引用該模塊的項(xiàng)目
* 如果有一個(gè)自己的實(shí)現(xiàn),實(shí)現(xiàn)了ValidateCodeGenerator接口,定義了自己的實(shí)現(xiàn),名字也叫imageCodeGenerator時(shí),
* 就用應(yīng)用級(jí)別的實(shí)現(xiàn),沒(méi)有的話就用這個(gè)默認(rèn)實(shí)現(xiàn)。
* @param @return
* @return ValidateCodeGenerator
* @throws
* @author lihaoyang
* @date 2018年3月5日
*/
@Bean
@ConditionalOnMissingBean(name="imageCodeGenerator")
public ValidateCodeGenerator imageCodeGenerator(){
ImageCodeGenerator codeGenerator = new ImageCodeGenerator();
codeGenerator.setSecurityProperties(securityProperties);
return codeGenerator;
}
}
這樣,如果哪個(gè)模塊引用了這個(gè)驗(yàn)證碼模塊,他自定義了實(shí)現(xiàn),如:
package com.imooc.code;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import com.imooc.security.core.validate.code.ImageCode;
import com.imooc.security.core.validate.code.ValidateCodeGenerator;
@Component("imageCodeGenerator")
public class DemoImageCodeGenerator implements ValidateCodeGenerator {
@Override
public ImageCode generator(ServletWebRequest request) {
System.err.println("demo項(xiàng)目實(shí)現(xiàn)的生成驗(yàn)證碼,,,");
return null;
}
}
這樣ValidateCodeBeanConfig在配置驗(yàn)證碼bean時(shí),就會(huì)使用使用者自定義的實(shí)現(xiàn)。
完整代碼放在了github:https://github.com/lhy1234/spring-security
總結(jié)
以上所述是小編給大家介紹的Spring Security 圖片驗(yàn)證碼功能的實(shí)例代碼,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- Springboot+SpringSecurity實(shí)現(xiàn)圖片驗(yàn)證碼登錄的示例
- Spring?Security?登錄時(shí)添加圖形驗(yàn)證碼實(shí)現(xiàn)實(shí)例
- SpringSecurity頁(yè)面授權(quán)與登錄驗(yàn)證實(shí)現(xiàn)(內(nèi)存取值與數(shù)據(jù)庫(kù)取值)
- Spring Security組件一鍵接入驗(yàn)證碼登錄和小程序登錄的詳細(xì)過(guò)程
- Spring Security 實(shí)現(xiàn)多種登錄方式(常規(guī)方式外的郵件、手機(jī)驗(yàn)證碼登錄)
- Spring?Security實(shí)現(xiàn)添加圖片驗(yàn)證功能
相關(guān)文章
java IO實(shí)現(xiàn)電腦搜索、刪除功能的實(shí)例
下面小編就為大家?guī)?lái)一篇java IO實(shí)現(xiàn)電腦搜索、刪除功能的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12
springboot項(xiàng)目打成war包部署到tomcat遇到的一些問(wèn)題
這篇文章主要介紹了springboot項(xiàng)目打成war包部署到tomcat遇到的一些問(wèn)題,需要的朋友可以參考下2017-06-06
@RequestMapping 如何使用@PathVariable 從URI中獲取參數(shù)
這篇文章主要介紹了@RequestMapping 如何使用@PathVariable 從URI中獲取參數(shù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
配置springboot項(xiàng)目動(dòng)靜分離打包分離lib方式
本文介紹了如何將Spring?Boot工程中的靜態(tài)資源和配置文件分離出來(lái),以減少jar包大小,方便修改配置文件,通過(guò)在jar包同級(jí)目錄創(chuàng)建config目錄,并在pom文件中進(jìn)行相應(yīng)配置,可以實(shí)現(xiàn)配置文件的外部化,打包后的jar包和lib文件夾中分別存放了第三方j(luò)ar包和靜態(tài)資源文件2025-02-02
SpringIntegration消息路由之Router的條件路由與過(guò)濾功能
本文詳細(xì)介紹了Router的基礎(chǔ)概念、條件路由實(shí)現(xiàn)、基于消息頭的路由、動(dòng)態(tài)路由與路由表、消息過(guò)濾與選擇性路由以及錯(cuò)誤處理與路由等方面的內(nèi)容,提高了系統(tǒng)的可維護(hù)性和可擴(kuò)展性,感興趣的朋友一起看看吧2025-04-04
使用maven對(duì)springboot項(xiàng)目進(jìn)行瘦身分離jar的多種處理方案
springboot項(xiàng)目打包一般我們都使用它自帶的spring-boot-maven-plugin插件,這個(gè)插件默認(rèn)情況下,會(huì)把所有的依賴包全部壓縮到一個(gè)jar里面,今天給大家分享幾種方案來(lái)如何減小我們的打包文件,需要的朋友可以參考下2024-02-02
詳解在IDEA中使用MyBatis Generator逆向工程生成代碼
這篇文章主要介紹了詳解在IDEA中使用MyBatis Generator逆向工程生成代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
新聞列表的分頁(yè)查詢java代碼實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了新聞列表的分頁(yè)查詢java代碼實(shí)現(xiàn),感興趣的小伙伴們可以參考一下2016-08-08

