“This is the sixth day of my participation in the November Gwen Challenge.The final text challenge in 2021”.

preface

At the heart of SpringSecurity is a set of filter chains. The framework itself provides some default security filters, but with real-world development scenarios we can also customize filters to support our business needs, most commonly captcha logic.

Graphic verification code

The implementation idea is to add a layer of filtering before the verification user name and password and verify the verification code. The graphic verification code is obtained through the request. When the request is successful, the plaintext information of the verification code is saved in the session for the subsequent verification.

1. Prepare configuration

Reference dependencies for the project

<dependency>
    <groupId>cloud.agileframework</groupId>
    <artifactId>spring-boot-starter-kaptcha</artifactId>
    <version>2.0.0</version>
</dependency>
Copy the code
  • Configure a KaptCHA instance
  • Add it to WebSecurityConfig

Here is no detailed introduction, can refer to the relevant information

@Bean
public Producer kaptcha(a) {
    // Set the basic parameters of the graphic verification code
    Properties properties = new Properties();
    // Image width
    properties.setProperty("kaptcha.image.width"."150");
    // Image length
    properties.setProperty("kaptcha.image.height"."50");
    / / character set
    properties.setProperty("kaptcha.textproducer.char.string"."0123456789");
    // The length of the character
    properties.setProperty("kaptcha.textproducer.char.length"."4");
    Config config = new Config(properties);
    // Use the default graphic verification code implementation, can also be customized
    DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
    defaultKaptcha.setConfig(config);
    return defaultKaptcha;
}
Copy the code
  • Create a CaptchaControlle to get a graphic verification code
@Controller
public class CaptchaController {

    @Autowired
    private Producer captchaProducer;

    @GetMapping("/captcha.jpg")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // Set the content type
        response.setContentType("image/jpeg");
        // Create captcha text
        String capText = captchaProducer.createText();
        // Set the captcha text to session
        request.getSession().setAttribute("captcha", capText);
        // Create a captcha image
        BufferedImage capImage = captchaProducer.createImage(capText);
        // Get the response output stream
        ServletOutputStream outputStream = response.getOutputStream();
        // Write the image captcha data to the response output stream
        ImageIO.write(capImage, "jpg", outputStream);
        // Push and close the response output stream
        try {
            outputStream.flush();
        } finally{ outputStream.close(); }}}Copy the code

2. Custom graphic verification code verification filter

This is implemented by inheriting OncePerRequestFilter, which ensures that a request only passes through this filter once

1. Customize a verification exception class

  • Creating an exception Packageexception
  • Inheritance AuthenticationException
/** * Verification code fails */
public class VerificationCodeException extends AuthenticationException {
    public VerificationCodeException(String msg) {
        super(msg); }}Copy the code

2. Customize exception handling handler

  • Creating a processor packagehandler
  • Implement AuthenticationFailureHandler
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
        httpServletResponse.setContentType("application/json; charset=UTF-8");
        httpServletResponse.setStatus(401);
        PrintWriter out = httpServletResponse.getWriter();
        out.write("{\n" +
                " \"error_code\": 401,\n" +
                " \"error_name\":" + "\" " + e.getClass().getName() + "\",\n" +
                "\"message\": \" Request failed," + e.getMessage() + "\"\n" +
                "}"); }}Copy the code

3. Customize the verification code filter

  • Creating a filter packagefilter
  • Inheritance OncePerRequestFilter
public class VerificationCodeFilter extends OncePerRequestFilter {

    private final AuthenticationFailureHandler authenticationFailureHandler = new MyAuthenticationFailureHandler();

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        // Verification codes are not verified for non-login requests
        String requestURI = httpServletRequest.getRequestURI();
        if (!"/myLogin".equalsIgnoreCase(httpServletRequest.getRequestURI())) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        } else {
            try {
                verificationCode(httpServletRequest);
                filterChain.doFilter(httpServletRequest, httpServletResponse);
            } catch (VerificationCodeException e) {
                System.out.println(e);
                authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
            } catch(ServletException e) { e.printStackTrace(); }}}private void verificationCode(HttpServletRequest httpServletRequest) throws VerificationCodeException {
        String captcha = httpServletRequest.getParameter("captcha");
        HttpSession session = httpServletRequest.getSession();
        String captchaCode = (String) session.getAttribute("captcha");
        if (StringUtils.isNotEmpty(captchaCode)) {
            // Clear the verification code after the verification, regardless of success or failure
            session.removeAttribute("captcha");
        }
        // The verification fails to throw an exception
        if(StringUtils.isEmpty(captcha) || StringUtils.isEmpty(captchaCode) || ! captcha.equals(captchaCode))throw new VerificationCodeException("Graph verification code verification is abnormal"); }}Copy the code

4. Modify configure

  • Permit request verification codeapi/captcha.jpg
  • Add verification failure handling
  • Add filter before UsernamePasswordAuthenticationFilter
@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .antMatchers("/app/api/**"."/captcha.jpg").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginPage("/myLogin.html")
                // Specify the path to process the login request. Modify the path of the request. The default path is /login
                .loginProcessingUrl("/mylogin").permitAll()
                .failureHandler(new MyAuthenticationFailureHandler())
                .and()
                .csrf().disable();
        / / add filter before UsernamePasswordAuthenticationFilter
        http.addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class);
    }
Copy the code

Login page

Space is limited, here will not post the complete code, there is a need to leave the email to send the code, in fact, is relatively simple.

<! doctypehtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="Width =device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>The login</title>
</head>
<body>
<div class="main">
    <div class="title">
        <span>Password to login</span>
    </div>

    <div class="title-msg">
        <span>Please enter your login account and password</span>
    </div>

    <form class="login-form" action="/mylogin" method="post" novalidate>
        <! -- Input box -->
        <div class="input-content">
            <! --autoFocus-->
            <div>
                <input type="text" autocomplete="off"
                       placeholder="Username" name="username" required/>
            </div>

            <div style="margin-top: 16px">
                <input type="password"
                       autocomplete="off" placeholder="Login password" name="password" required maxlength="32"/>
            </div>
            <div style="margin-top: 16px; display: flex">
            <! -- Added graphic verification code input box -->
                <input type="text" name="captcha" placeholder="Verification code" width="180px" />
                <img src="/captcha.jpg" alt="captcha" height="42px" width="150px" style="margin-left: 20px">
            </div>
        </div>
        <! -- Login button -->
        <div style="text-align: center">
            <button type="submit" class="enter-btn">The login</button>
        </div>

        <div class="foor">
            <div class="left"><span>Forget your password?</span></div>

            <div class="right"><span>Sign up for an account</span></div>
        </div>
    </form>
</div>
</body>
Copy the code

Four, test,

  • Start the project
  • Access to the API:http://localhost:8080/user/api/hi

  • Enter the correct user name, password, and verification code
  • Access to success
  • Page showshi,user.
  • Restart the project
  • Enter a correct user name and password, but an incorrect verification code
  • The visit to fail
  • Return failure message
{
"error_code": 401."error_name": "com.yang.springsecurity.exception.VerificationCodeException"."message": "Request failed. Graph verification code is abnormal."
}
Copy the code