Verification code logic
I have also done verification codes in projects before. There are many codes on the Internet that generate verification codes, and some third-party jar packages can also generate beautiful verification codes. The verification code logic is very simple. It is to put an image tag on the login page, and src points to a controller. This controller returns the generated image and returns the output stream to the page. While generating the image, the text on the image is placed in the session. When logging in, bring the entered verification code and take it out from the session. Compare the two. The teacher said that using Spring Security integrated verification code is the same as what I said, but it is more standardized and general.
spring security is a series of filter chains, so here the verification code is also declared as a filter, added before the filter chain's login filter, and then customize an exception class to respond to the error message of the verification code.
Code structure:
The verification code is placed in the core project and the configuration is done in the browser project.
Main code:
1. ImageCode:
First, the ImageCode class encapsulates the verification code image, text, and expiration time
package com.imooc.security.core.validate.code;import java.awt.image.BufferedImage;import java.time.LocalDateTime;import java.time.LocalTime;/** * Verification code* ClassName: ImageCode * @Description: Verification code* @author lihaoyang * @date March 1, 2018*/public class ImageCode { private BufferedImage image; private String code; private LocalDateTime expireTime;//Expiration time point/** * * <p>Description: </p> * @param image * @param code * @param expireTn How many seconds does it expire*/ public ImageCode(BufferedImage image, String code, int expireTn) { super(); this.image = image; this.code = code; //Expiration time = current time + number of expired seconds this.expireTime = LocalDateTime.now().plusSeconds(expireTn); } public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) { super(); this.image = image; this.code = code; this.expireTime = expireTime; } /** * Whether the verification code has expired* @Description: Whether the verification code has expired* @param @return true Expired, false Not expired* @return boolean true Expired, false Not expired* @throws * @author lihaoyang * @date March 2, 2018*/ 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: a tool class for generating verification codes. Here http://www.cnblogs.com/lihaoyang/p/7131512.html, of course, you can also use third-party jar packages, it doesn't matter.
ValidateCodeException : Encapsulated verification code exception
/** * @Title: ValidateCodeException.java * @Package com.imooc.security.core.validate.code * @Description: TODO * @author lihaoyang * @date March 2, 2018*/package com.imooc.security.core.validate.code;import org.springframework.security.core.AuthenticationException;/** * ClassName: ValidateCodeException * @Description: Verification code error exception, authentication exception inheriting spring security* @author lihaoyang * @date March 2, 2018*/public class ValidateCodeException extends AuthenticationException { /** * @Fields serialVersionUID : TODO */ private static final long serialVersionUID = 1L; public ValidateCodeException(String msg) { super(msg); }}ValidateCodeFilter : Verification code filter
Logic: Inheriting OncePerRequestFilter ensures that the filter will be called only once each time (not sure why), injected into the authentication failed processor, and called when verification failed.
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;/** * Process the login verification code filter* ClassName: ValidateCodeFilter * @Description: * OncePerRequestFilter: The tool provided by spring ensures that the filter will be called only once each time* @author lihaoyang * @date March 2, 2018*/public class ValidateCodeFilter extends OncePerRequestFilter{ //Authorization failed processor private AuthenticationFailureHandler authenticationFailureHandler; //Get session tool class private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //If it is a login request, execute if(StringUtils.equals("/authentication/form", request.getRequestURI()) &&StringUtils.equalsIgnoreCase(request.getMethod(), "post")){ try { validate(new ServletWebRequest(request)); } catch (ValidateCodeException e) { //Calling the error handler, and finally calling its own authenticationFailureHandler.onAuthenticationFailure(request, response, e); return ;//Ending the method, no longer calling the filter chain} } //Not a login request, call other filter chain filterChain.doFilter(request, response); } /** * Verification verification code* @Description: Verification verification code* @param @param request * @param @throws ServletRequestBindingException * @return void * @throws ValidateCodeException * @author lihaoyang * @date March 2, 2018*/ private void validate(ServletWebRequest request) throws ServletRequestBindingException { //Take out the ImageCode object in the session ImageCode imageCodeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY); //Take out the verification code in the request String imageCodeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode"); //Check if(StringUtils.isBlank(imageCodeInRequest)){ throw new ValidateCodeException("Verification code cannot be empty"); } if(imageCodeInSession == null){ throw new ValidateCodeException("Verification code does not exist, please refresh the verification code"); } if(imageCodeInSession.isExpired()){ //Remove the expired verification code from the session sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY); throw new ValidateCodeException("Verification code has expired, please refresh the verification code"); } if(!StringUtils.equalsIgnoreCase(imageCodeInSession.getCode(), imageCodeInRequest)){ throw new ValidateCodeException("Verification code error"); } //Verification is passed, remove the verification code in the session sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY); } public AuthenticationFailureHandler getAuthenticationFailureHandler() { return authenticationFailureHandler; } public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { this.authenticationFailureHandler = authenticationFailureHandler; }}ValidateCodeController : Generate verification code 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;/** * Verification CodeControl * ClassName: ValidateCodeController * @Description: TODO * @author lihaoyang * @date March 1, 2018*/@RestControllerpublic class ValidateCodeController { public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE"; //Get 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); }}Filter configuration in BrowserSecurityConfig :
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.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypt.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.authentication.AuthenticationFailureHandler;import org.springframework.security.web.authentication.AuthenticationFailureHandler;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import com.imooc.security.core.properties.SecurityProperties;import com.imooc.security.core.validate.code.ValidateCodeFilter;@Configuration //This is a configuration public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter{ //Read the login page configuration of the user configuration @Autowired private SecurityProperties securityProperties; //Customized processor after successful login @Autowired private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler; //Custom authentication failed processor @Autowired private AuthenticationFailureHandler imoocAuthenticationFailureHandler; //Note that it is org.springframework.security.crypto.password.PasswordEncoder @Bean public PasswordEncoder passwordcoder(){ //BCryptPasswordEncoder implements PasswordEncoder return new BCryptPasswordEncoder(); } // Version 2: Configurable login page @Override protected void configure(HttpSecurity http) throws Exception { //ValidateCodeFilter ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter(); //Use your own error handling in the verification code filter to validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler); //Implement the interface that needs authentication to jump form login, security = authentication + authorization //http.httpBasic() //This is the default pop-up box authentication // http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)//Load the verification code filter before the login filter.formLogin() //Form authentication.loginPage("/authentication/require") //Process user authentication BrowserSecurityController //Log in filter UsernamePasswordAuthenticationFilter The default login url is "/login", which can be changed here.loginProcessingUrl("/authentication/form") .successHandler(imoocAuthenticationSuccessHandler)//Custom authentication postprocessor.failureHandler(imoocAuthenticationFailureHandler) //Processing after login failure.and() .authorizeRequests() //The following are all authorized configurations///authentication/require: handle login, securityProperties.getBrowser().getLoginPage(): user-configured login page.antMatchers("/authentication/require", securityProperties.getBrowser().getLoginPage(),//Save the login page but not filter, otherwise an error "/verifycode/image").permitAll() //Verification code.anyRequest() //Any request.authenticated() //Identity authentication is required.and() .csrf().disable() //Crypto protection is turned off; }}Login page: The login page is made relatively roughly. In fact, the verification code can be checked when the verification code input loses focus. You can also use a click image to refresh the verification code function, so I won't do it here.
<body> demo login page. <br> <form action="/authentication/form" method="post"> <table> <tr> <td> Username: </td> <td><input type="text" name="username"/></td> <td></td> </tr> <tr> <td> Password: </td> <td><input type="password" name="password"/></td> <td></td> </tr> <tr> <td> Verification code: </td> <td> <input type="text" name="imageCode"/> </td> <td> <img src="/verifycode/image"/> </td> </tr> <tr> <td colspan="2" align="right"><button type="submit">Login</button></td> </tr> </table> </form> </body>
Visit http://localhost:8080/demo-login.html:
Respond to custom exception information
The general function is no problem. But it is not universal enough, such as the width and height of the verification code picture, the expiration time, the filtered url, and the verification code logic are all written to death. These can be made alive, and now the benefits of making verification codes into a filter are reflected. We can configure urls that need to be filtered. Sometimes it may not only require verification codes on the login page, which is more general.
1. The basic parameters of the verification code for generalization modification can be matched
It is made into configurable, and the application refers to the module and configures it himself. If it does not configure, use the default configuration. Moreover, the configuration can be declared in the request url or in the application. The teacher is indeed a teacher, and the code is very versatile!
The effect I want to achieve is to make such a configuration in application.properties:
#Verification code image width, height, number of characters imooc.security.code.image.width = 100imooc.security.code.image.height = 30imooc.security.code.image.length = 6
Then you can control the effect of the verification code. Because the verification code is also divided into image verification code and SMS verification code, you can use a level .code.image, which uses springboot's custom configuration file and needs to declare the corresponding java class:
The code attribute needs to be declared in SecurityProperties:
package com.imooc.security.core.properties;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;/** * Custom configuration items* ClassName: SecurityProperties * @Description: Custom configuration items* This class will read all configuration items starting with imooc.security in application.properties* * imooc.security.browser.loginPage = /demo-login.html * The browser configuration will be read into BrowserProperties* This is divided by points, and the level and level correspond to the attributes of the class* @author lihaoyang * @date February 28, 2018*/@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;/** * Verification code configuration* ClassName: ValidateCodeProperties * @Description: Verification code configuration, the verification code includes image verification code, SMS verification code, etc., so a layer is included* @author lihaoyang * @date March 2, 2018*/public class ValidateCodeProperties { //Default configuration 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;/** * Image verification code configuration class* ClassName: ImageCodeProperties * @Description: Image verification code configuration class* @author lihaoyang * @date March 2, 2018*/public class ImageCodeProperties { //Picture width private int width = 67; //Picture height private int height = 23; //Number of verification code characters private int length = 4; //Expiration time 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; }}For request-level configuration, if the request contains the verification code parameters, use the request:
The createImageCode method of ValidateCodeController is used to control it to determine whether the request parameters have these parameters. If so, it is passed to the verification code generation class VerifyCode, which can be dynamically controlled during generation.
private ImageCode createImageCode(HttpServletRequest request, HttpServletResponse response) { //First read the length, width, and number parameters of characters from the request, if there is, use it, and the default 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; }Experiment: Do application-level configuration in demo project
Log in to the form to make request-level configuration
<img src="/verifycode/image?width=200"/>
access:
The length is the parameter 200 of the requesting stage band, the height is 30, and the characters are 6 configured.
2. The interface for verification code interception of universal modification can be configured
The first effect is to dynamically configure the interfaces that need to be intercepted in application.properties :
ImageCodeProperties has added a new attribute: private String url ; //The intercepted url is to match the configuration of the above image.
Core, verification code filter needs to be modified:
1. Declare a set set in the interceptor to store the urls configured in the configuration file that need to be intercepted.
2. Implement the InitializingBean interface. Purpose: When all other parameters are assembled, initialize the value of the urls that needs to be intercepted, and rewrite the afterPropertiesSet method to implement it.
3. Inject SecurityProperties and read the configuration file
4. Instantiate the AntPathMatcher tool class, which is a matcher
5. Set the BrowserSecurityConfig in the browser project to call the afterPropertiesSet method.
6. Configure the URL to be filtered in the application.properties of the demo project that references the module.
ValidateCodeFilter:
/** * Process the login verification code filter* ClassName: ValidateCodeFilter * @Description: * Inherit the tools provided by OncePerRequestFilter: spring to ensure that the filter will be called only once each time* Implement the purpose of the InitializingBean interface: * When other parameters are assembled, initialize the value of the urls that needs to be intercepted* @author lihaoyang * @date March 2, 2018*/public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean{ // Authentication failed processor private AuthenticationFailureHandler authenticationFailureHandler; //Get session tool class private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); //The url collection that needs to be intercepted private Set<String> urls = new HashSet<>(); //Read configuration private SecurityProperties securityProperties; //Spring tool class private AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public void afterPropertiesSet() throws ServletException { super.afterPropertiesSet(); //Read the urls of the configured intercepted String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrl(), ","); for (String configUrl : configUrls) { urls.add(configUrl); } //Log in requests must intercept urls.add("/authentication/form"); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { /** * Configurable verification code verification* Determine whether the requested url and the configured url match, filter it if it matches*/ 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) { //Calling the error handler, and finally calling its own authenticationFailureHandler.onAuthenticationFailure(request, response, e); return ;//Ending the method, no longer calling the filter chain} } //Not a login request, call other filter chains filterChain.doFilter(request, response); } //Omitting irrelevant code,, }BrowserSecurityConfig:
Configure url:
#Verification code intercept interface configuration imooc.security.code.image.url = /user,/user/*
Test: /user /user/1 was intercepted
Visit the login page without writing verification code:
Consistent with expectations. At this point, dynamic configuration of the intercept interface is completed
3. The verification code generation logic can be configurable
The better-written programs generally have open interfaces, allowing users to customize the implementation. If they do not implement, use the default implementation. Let’s do this so that the generation of verification codes can be implemented by themselves. If you want to make the verification code generation logic configurable, you cannot just write a class of the image verification code generator. You need to extract the verification code generation into an interface ValidateCodeGenerator, a method to generate verification code generator(). Because the verification code also has image verification code, SMS verification code, etc., we make a default implementation in our verification module, such as the image verification code implementation ImageCodeGenerator. In ImageCodeGenerator, we do not add @Component annotation to this class. Then use the configuration class ValidateCodeBeanConfig that writes a verification code bean. This configuration class configures various required verification code implementation beans, such as image verification code implementation imageCodeGenerator, SMS verification code, etc. Their return types are all ValidateCodeGenerator. Use the @ConditionalOnMissingBean(name="imageCodeGenerator") annotation. It can be judged that if the current spring container has a bean named imageCodeGenerator, it will be used, and if there is no configuration, so if others refer to your module, if others implement the verification code to generate the ValidateCodeGenerator interface, they configure the name of the implementation class to be imageCodeGenerator, and use their own implementation, so that the program is extensible.
Main code:
Code generator interface ValidateCodeGenerator:
package com.imooc.security.core.validate.code;import org.springframework.web.context.request.ServletWebRequest;/** * Verification code generation interface* ClassName: ValidateCodeGenerator * @Description: TODO * @author lihaoyang * @date March 2, 2018*/public interface ValidateCodeGenerator { /** * Image verification code generation interface* @Description: TODO * @param @param request * @param @return * @return ImageCode * @throws * @author lihaoyang * @date March 2, 2018*/ ImageCode generator(ServletWebRequest request);}Image verification code generator implements 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;/** * Image verification code generation class* ClassName: ImageCodeGenerator * @Description: TODO * @author lihaoyang * @date March 2, 2018*/public class ImageCodeGenerator implements ValidateCodeGenerator { @Autowired private SecurityProperties securityProperties; @Override public ImageCode generator(ServletWebRequest request) { //First read from the request whether there are any length, width, and characters parameters. If there is, use it, and the default 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.Condition.Condition.Condition.Condition.Condition.OnMissingBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import com.imooc.security.core.properties.SecurityProperties;/** * Configure the bean of the actual implementation class of the ValidateCodeGenerator * ClassName: ValidateCodeBeanConfig * @Description: * Configure the bean of the actual implementation class of the ValidateCodeGenerator * Such as the implementation of the image verification code and the implementation of the SMS verification code * @author lihaoyang * @date March 5, 2018*/@Configurationpublic class ValidateCodeBeanConfig { @Autowired private SecurityProperties securityProperties; /** * @Description: * @ConditionalOnMissingBean annotation means that when the spring container does not have an imageCodeGenerator, the function is to make the program more extensible. The configuration class is configured in the core module. This means that if the project that refers to the module * has its own implementation, which implements the ValidateCodeGenerator interface and defines its own implementation. The name is also called imageCodeGenerator, * is used to implement the application-level implementation, and if there is no such implementation, use this default implementation. * @param @return * @return ValidateCodeGenerator * @throws * @author lihaoyang * @date March 5, 2018*/ @Bean @ConditionalOnMissingBean(name="imageCodeGenerator") public ValidateCodeGenerator imageCodeGenerator(){ ImageCodeGenerator codeGenerator = new ImageCodeGenerator(); codeGenerator.setSecurityProperties(securityProperties); return codeGenerator; }}In this way, if a module refers to this verification code module, it customizes the implementation, such as:
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("generated verification code for the demo project implementation,,"); return null; }}In this way, when ValidateCodeBeanConfig configures the verification code bean, it will use the user's custom implementation.
The complete code is placed in github: https://github.com/lhy1234/spring-security
Summarize
The above is the example code of the Spring Security image verification code function introduced to you by the editor. I hope it will be helpful to you. If you have any questions, please leave me a message and the editor will reply to you in time. Thank you very much for your support to Wulin.com website!