On the basis of the previous section, we added the verification code module to the project. Security did not provide this function for us, so we needed the handwriting filter to achieve it. The title of this section seems to have nothing to do with the first section, but the idea is similar. I hope you can read it patiently, because I am trying my best to write clearly. After watching it, I may feel very confused, which is normal, because of the encapsulation, but I believe the technology will be greatly improved after watching it.

Chapter one: SpringBoot integration Security (1) to implement user authentication and determine whether to return JSON or view. Chapter two: SpringBoot integration Security (2) to implement verification code login

All right, start the text.





As usual, I’ll give you an overview of each class:

  • ImageCodeProperties:
  • ValidateCodeProperties:
  • These two classes are under the Properties package because they are used to retrieve the configuration in the Application configuration file.
  • ImageCodeGenerator: Generates the captco implementation class
  • ValidateCodeBeanConfig: Inject ImageCodeGenerator into the Spring container. Why not just add @Component injection to ImageCodeGenerator? Please see below for a detailed explanation
  • ValidateCodeGenerator: generates a verification code interface
  • ImageCode: Captcha entity class
  • ValidateCodeController: Controller class used to return a verification code to the user
  • ValidateCodeException: User-defined exception
  • ValidateCodeFilter: indicates the verification code filter

1. First we need to hand write a filter

The question then arises, what kind of filter should be written, and at what stage of the project should the filter be called? You can see in the first quarter, but at the time of return UserDetail object, the following is the security of black-box basic information (check), so we must before the filter to make a verification code processing (generation and judgment), if the verification code does not conform to the requirements, directly to the login failure handling device, and vice versa.

1.1 We need to add some code to the core configuration class BrowserSecurityConfig.
*/ ValidateCodeFilter ValidateCodeFilter = new ValidateCodeFilter(); validateCodeFilter.setFailureHandler(myFailHandler); / / set the securityproperties in validateCodeFilter. SetSecurityProperties (securityproperties); / / call the assembly need initialization method of image authentication code url validateCodeFilter. AfterPropertiesSet (); HTTP / / before UsernamePasswordAuthenticationFilter filter with a filter to make verification code .addFilterBefore(validateCodeFilter,UsernamePasswordAuthenticationFilter.class) .formLogin() ....Copy the code

First, I create a ValidateCodeFilter object, set its failure handler and securityProperties configuration class, and call the afterPropertiesSet() method in it. In total, three methods of the object are called. As for how to do it, slowly read down.

Second, before UsernamePasswordAuthenticationFilter filter with a filter to make verification code. Namely: addFilterBefore (validateCodeFilter, UsernamePasswordAuthenticationFilter. Class)

1.2 ValidateCodeFilter .java
package com.fantJ.core.validate; /** * Created by Fant.j. */ @Component Public class extends OncePerRequestFilter implements  InitializingBean{ private Logger logger = LoggerFactory.getLogger(getClass()); / * * * * / login failed processor @autowired private AuthenticationFailureHandler failureHandler; / * * * * / private Session object SessionStrategy SessionStrategy = new HttpSessionSessionStrategy (); Private Set<String> urls = new HashSet<>(); /** * security applicaiton */ @autoWired Private SecurityProperties SecurityProperties; /** * Private AntPathMatcher pathMatcher = new AntPathMatcher(); /** * This method is a method under the InitializingBean interface, Run the method after the initial configuration is complete. */ @override public void afterPropertiesSet() throws ServletException {super.afterPropertiesSet(); ValidateCodeProperties code = securityProperties.getCode(); logger.info(String.valueOf(code)); String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrl(), ","); // Add urls.addall (arrays.aslist (configUrls)) to Set Arrays; // Add urls.add("/authentication/form") to the set because the login request must have a captcha; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { boolean action = false; For (String url: urls) {/ / if the request url and configuration of the url to match the if (pathMatcher. Match (url, request getRequestURI ())) {action = true; If (action){logger.info(" blocked successfully "+ request.getrequesturi ()); Try {validate(new ServletWebRequest(request)); }catch (ValidateCodeException exception){// Returns an error message to the failed handler failureHandler.onAuthenticationFailure(request,response,exception); return; }}else {// Do nothing, call filterchain.dofilter (request,response); }} private void validate (ServletWebRequest request) throws ServletRequestBindingException {/ / verification code ImageCode removed from a session  codeInSession = (ImageCode) sessionStrategy.getAttribute(request,ValidateCodeController.SESSION_KEY); / / request from request to retrieve captcha String codeInRequest = ServletRequestUtils. GetStringParameter (request, getRequest (), "imageCode"); If (stringutils. isBlank(codeInRequest)){logger.info(" Verification code cannot be empty "); Throw new ValidateCodeException(" Verification code cannot be empty "); } if (codeInSession == null){logger.info(" Verification code does not exist "); Throw new ValidateCodeException(" Verification code does not exist "); } if (codeInsession.isexpried ()){logger.info(" Verification code expired "); sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY); Throw new ValidateCodeException(" ValidateCodeException "); } if (! StringUtils.equals(codeInSession.getCode(),codeInRequest)){ Logger. info(" Verification code mismatch "+"codeInSession:"+ codeInsession.getCode () +", codeInRequest:"+codeInRequest); Throw new ValidateCodeException(" Verification code does not match "); } / / delete the corresponding session information sessionStrategy. RemoveAttribute (request, ValidateCodeController. SESSION_KEY); } / * * * fail filter getter and setter methods * / public AuthenticationFailureHandler getFailureHandler () {return failureHandler; } public void setFailureHandler(AuthenticationFailureHandler failureHandler) { this.failureHandler = failureHandler; } /** * SecurityProperties class getter and setter method */ public SecurityProperties getSecurityProperties() {return securityProperties; } public void setSecurityProperties(SecurityProperties securityProperties) { this.securityProperties = securityProperties; }}Copy the code

Follow the order in which the ValidateCodeFilter class calls the three methods in 1.1.

  1. validateCodeFilter.setFailureHandler(myFailHandler);

Call the getter and setter method of the logon failure handler.

So the question is, we injected the logon failure handler class in section 1, why set it up here?

The answer is simple, because a new filter calls it. Furthermore, the ValidateCode filter is applied before the user validates.

  1. validateCodeFilter.setSecurityProperties(securityProperties);

This is the Security Applicaiton configuration property getter and setter method

This code does not say what, the reason is the same as above. Because in the core configuration class BrowserSecurityConfig, we inject the object and then pass the object. Not passing should also work because there is an injection of this object in this class, but passing is definitely not an error.

  1. afterPropertiesSet()methods

    This method is a method under the InitializingBean interface that runs after the initial configuration. The purpose of this method is to make a collection of urls that we need to validate with captcha in our business logic. And then it intercepts (in the doFilterInternal() method it intercepts the URL, and then it goes further)

    Then I post the application configuration
# graphical verification code configuration fantJ. Security. Code. The image. The length = 6 fantJ.. The security code. The image. The width = 100 fantJ.security.code.image.url=/user,/user/*Copy the code

According to securityProperties. GetCode (). The getImage (). The getUrl () we can see that I add the Code under securityProperties object, under the Code to add the Image objects, Image by adding the Url object. (I’ll post the code in full later) to get the URL in the configuration, cut it by commas, and put it in the Set.

  1. DoFilterInternal () in ValidateCodeFilter

    By calling the afterPropertiesSet() method, we’ve got the set of urls we need to intercept. I then determine if the URI in the Request is in the set. If so, intercept the request and callvalidate();The validate() method will pull the captcha from the session and from the request. For comparison and verification, see comments for details. ImageCode is the captcha entity class.
3. The Controller class generates a verification code

As mentioned above, we need to get the verification code from the request request. Before that, we need to generate the verification code in the Controller and return it to the user, who can fill in and submit it before we get it from the request request.

package com.fantJ.core.validate; /** * Created by Fant.J. */ @RestController public class ValidateCodeController { public static final String SESSION_KEY  = "SESSION_KEY_IMAGE_CODE"; / * * * introduction session * / private SessionStrategy SessionStrategy = new HttpSessionSessionStrategy (); @Autowired private ValidateCodeGenerator imageCodeGenerator; @GetMapping("/code/image") public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException { ImageCode imageCode = imageCodeGenerator.createCode(new ServletWebRequest(request)); / / put the random number in Session sessionStrategy. SetAttribute (new ServletWebRequest (request), SESSION_KEY, imageCode); / / to the response response ImageIO. Write (imageCode. GetImage (), "JPEG", the response. GetOutputStream ()); }}Copy the code

We randomly generate the verification code, put it into the session, and return it to the Response client.

4. Generate a verification code class

ImageCode .java

package com.fantJ.core.validate; /** * Created by fant.j. */ public class ImageCode {/** * private BufferedImage image; /** * random number */ private String code; Private LocalDateTime expireTime; public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) { this.image = image; this.code = code; this.expireTime = expireTime; } public ImageCode(BufferedImage image, String code, int expireIn) { this.image = image; this.code = code; This.expiretime = localDatetime.now ().plusseconds (expireIn); } public Boolean isExpried(){return localDatetime.now ().isafter (expireTime);} public Boolean isExpried(){localDatetime.now ().isafter (expireTime); }... getter and setter }Copy the code

We upload a parameter int expireIn, take the current time plus, and compare the current time with the time after plus in isExpried() to determine whether the captchas have expired.

The ValidatecodeGenerator. Java interface class

/** * Created by fant.j. */ public interface ValidateCodeGenerator {/** * Created by ImageCode */ createCode(ServletWebRequest request); }Copy the code

Why do we need an interface? For encapsulation, if we want to write a better captcha generator in the future, we can just inherit the interface implementation method without changing the original code.

ImageCodeGenerator.java

package com.fantJ.core.validate.code; /** * Created by fant.j. */ public class ImageCodeGenerator implements ValidateCodeGenerator {/** * */ private SecurityProperties securityProperties; @override public ImageCode createCode(ServletWebRequest Request) {// Override public ImageCode createCode(ServletWebRequest Request) { Otherwise, int width = is used in configuration properties ServletRequestUtils.getIntParameter(request.getRequest(),"width",securityProperties.getCode().getImage().getWidth()); // int height = ServletRequestUtils.getIntParameter(request.getRequest(),"height",securityProperties.getCode().getImage().getHeight()); / / image authentication code number character int length = securityProperties. GetCode (). The getImage (). GetLength (); . / / expiration time int expireIn = securityProperties getCode (). The getImage () getExpireIn (); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); Random random = new Random(); g.setColor(getRandColor(200, 250)); g.fillRect(0, 0, width, height); g.setFont(new Font("Times New Roman", Font.ITALIC, 20)); g.setColor(getRandColor(160, 200)); for (int i = 0; i < 155; i++) { int x = random.nextInt(width); int y = random.nextInt(height); int xl = random.nextInt(12); int yl = random.nextInt(12); g.drawLine(x, y, x + xl, y + yl); } String sRand = ""; for (int i = 0; i < length; 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 + 6, 16); } g.dispose(); return new ImageCode(image, sRand, expireIn); } private Color getRandColor(int fc, int BC) {Random Random = new Random(); if (fc > 255) { fc = 255; } if (bc > 255) { bc = 255; } int r = fc + random.nextInt(bc - fc); int g = fc + random.nextInt(bc - fc); int b = fc + random.nextInt(bc - fc); return new Color(r, g, b); } public SecurityProperties getSecurityProperties() { return securityProperties; } public void setSecurityProperties(SecurityProperties securityProperties) { this.securityProperties = securityProperties; }}Copy the code

Finally, inject the class into the Spring container. ValidateCodeBeanConfig.java

package com.fantJ.core.validate.code; /** * Created by fant.j. */ @configuration Public class ValidateCodeBeanConfig {@autoWired private SecurityProperties securityProperties; ConditionalOnMissingBean(name = "imageCodeGenerator") /** ** ** trigger ValidateCodeGenerator The imageCodeGenerator bean was checked earlier. */ public ValidateCodeGenerator imageCodeGenerator(){ ImageCodeGenerator codeGenerator = new ImageCodeGenerator(); codeGenerator.setSecurityProperties(securityProperties); return codeGenerator; }}Copy the code

ConditionalOnMissingBean This annotation is essentially equivalent to adding an @Component annotation to the ImageCodeGenerator class. The difference is that this annotation is a conditional annotation, meaning that if the ImageCodeGenerator class is not in the container, I create it, and if it is, I do nothing.

Why is that? If we later rewrite a better generated captchas class, we can simply inject it with the @Component annotation, leaving the original code alone and not worrying about bean name conflicts.

This is the class used to generate captcha. Results the following




5. Exception classes and Properties classes

ValidateCodeException .java

package com.fantJ.core.validate; import org.springframework.security.core.AuthenticationException; /** * Created by fant.j. */ public class ValidateCodeException extends AuthenticationException {public ValidateCodeException(String msg) { super(msg); }}Copy the code

SecurityProperties.java

package com.fantJ.core.properties; import org.springframework.boot.context.properties.ConfigurationProperties; /** * Created by fant.j. */ @configurationProperties (prefix = "fantj. Security ") public class SecurityProperties {/** * Private BrowserProperties browser = new BrowserProperties(); /** * Private ValidateCodeProperties Code = new ValidateCodeProperties(); getter and setter... }Copy the code

ValidateCodeProperties.java

package com.fantJ.core.properties; /** * Created by fant.j. */ public class ValidateCodeProperties {/** * Graphic ValidateCodeProperties */ private ImageCodeProperties image = new ImageCodeProperties(); getter and setter... }Copy the code

ImageCodeProperties.java

package com.fantJ.core.properties; /** * public class ImageCodeProperties {/** * private int width = 67; /** * private int height = 23; /** * private int length = 4; /** * private int expireIn = 60; /** * private String url; getter and setter ... }Copy the code

Finally, a login page demo is included

<! DOCTYPE HTML > < HTML > <head> <meta charset="UTF-8"> <title> Login </title> </head> <body> <h2> Login </h2> <h3> form login </h3> <form Action ="/authentication/form" method="post"> <table> <tr> < TD > < / tr > < tr > < td > password: < / td > < td > < input type = "password" name = "password" > < / td > < / tr > < tr > < td > authentication code: < / td > < td > < input type="text" name="imageCode"> <img src="/code/image? Width = 100 "> < / td > < / tr > < tr > < td colspan =" 2 "> < button type =" submit "> login < / button > < / td > < / tr > < / table > < / form > < / body > </html>Copy the code

Results show


The login page

The verification code is empty

Verification code error