Add graphic verification code to spring security

Keywords: security

preface

In the process of using the Spring Security framework, there is often a need to add additional data, such as authentication code, user type, etc. during login authentication. The following describes how to implement it.
Note: my project is based on the Spring Boot framework. If it is configured in xml, please study it by yourself.
Implement custom WebAuthenticationDetails
This class provides the function of obtaining additional information carried by users when logging in. By default, WebAuthenticationDetails provides remoteAddress and sessionId information. Developers can get WebAuthenticationDetails through getDetails() of Authentication. We write a custom class CustomWebAuthenticationDetails, which inherits from WebAuthenticationDetails and adds the data we care about (the following is a token field).

For this demand, there are many online solutions, such as using filter and custom Provider
My method is to add an AuthenticationProvider. The specific implementation is as follows:
1. Add WebAuthenticationDetails implementation class to save verification code information

public class CustomWebAuthenticationDetails extends WebAuthenticationDetails {

    private String imageCode;

    private String session_imageCode;

    private long session_imageTime;

    public CustomWebAuthenticationDetails(HttpServletRequest request) {
        super(request);
        this.imageCode = request.getParameter("imageCode");
        this.session_imageCode = (String)request.getSession().getAttribute("session_imageCode");
        String session_verifyTime = (String)request.getSession().getAttribute("session_imageTime");
        if(session_verifyTime == null) {
            this.session_imageTime= 0L;
        } else {
            this.session_imageTime= Long.parseLong(session_verifyTime);
        }
    }

    public String getImageCode(){
        return imageCode;
    }

    public String getSession_imageCode() {
        return session_imageCode;
    }

    public long getSession_imageTime() {
        return session_imageTime;
    }
}

Note: on the login page, you can put the token field in the form form, or directly add it to the parameters of the url, and then send additional data to the background.
2. Add AuthenticationDetailsSource implementation class
This interface is used to fill in the details of the user's login information during Spring Security login. The default implementation is WebAuthenticationDetailsSource, and the above default implementation WebAuthenticationDetails is generated. We write a class to implement AuthenticationDetailsSource, which is used to generate CustomWebAuthenticationDetails customized above.

@Component
public class CustomAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new CustomWebAuthenticationDetails(context);
    }
}

3. Customize the AuthenticationProvider implementation class and add it to the authentication list
AuthenticationProvider provides login authentication processing logic. We implement this interface and write our own authentication logic.

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        CustomWebAuthenticationDetails details = (CustomWebAuthenticationDetails) authentication.getDetails();  
        String imageCode = details.getImageCode();
        String session_imageCode = details.getSession_imageCode();
        long session_imageTime = details.getSession_imageTime();

        if(imageCode == null || session_imageCode == null) {
            throw new ImageCodeIllegalException("Verification code error");
        }

        if(!imageCode.equals(session_imageCode)) {
            throw new ImageCodeIllegalException("Verification code error");
        }else{
            long nowTime = System.currentTimeMillis();
            if((nowTime - session_imageTime)/1000 > 60) { //More than 60s, timeout
                throw new ImageCodeIllegalException("Verification code timed out");
            }
        }
       return null; //If you want to have a password verification provider in the future, you need to return null directly here
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

4. Add configuration in the implementation class of WebSecurityConfigurerAdapter

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Inject
    private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource;

    @Inject
    private AuthenticationProvider customAuthenticationProvider;

    @Inject
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(customAuthenticationProvider);//a key
    }

     @Override
    protected void configure(HttpSecurity http) throws Exception {
        ......
        .and()
            .formLogin()
            .loginProcessingUrl("/api/login")
            .usernameParameter("username")
            .passwordParameter("password")
           .authenticationDetailsSource(authenticationDetailsSource) //a key
           .permitAll()
        ......
    }
}

In order to achieve the effect of "burn after use", the operation of clearing the verification code is added to the subsequent processing classes of login success and failure

@Component
public class AjaxAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Autowired
    private PasswordService passwordService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
        Authentication authentication)
        throws IOException, ServletException {

            //Remove verification code
            request.getSession().removeAttribute("session_verifyObj");
            request.getSession().removeAttribute("session_verifyObjTime");
            response.setStatus(HttpServletResponse.SC_OK);
    }
}
@Component
public class AjaxAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Autowired
    private PasswordService passwordService;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
        AuthenticationException exception) throws IOException, ServletException {

            //Remove verification code
            request.getSession().removeAttribute("session_imgeCode");
            request.getSession().removeAttribute("session_imageTime");

            ......
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed");
    }
}

Then add relevant configurations in the implementation class of WebSecurityConfigurerAdapter

    ......
    .and()
    .formLogin()
    .loginProcessingUrl("/api/login")
    .successHandler(ajaxAuthenticationSuccessHandler) //a key
    .failureHandler(ajaxAuthenticationFailureHandler) //a key
    .usernameParameter("username")
    .passwordParameter("password")
    .authenticationDetailsSource(authenticationDetailsSource)
    .permitAll()
    ......

Posted by havenpets on Fri, 24 Sep 2021 21:35:49 -0700