Spring Security series of tutorials: implementing graphic verification code based on user-defined authentication provider

 

ūüĎÜūüĎÜūüĎÜ

Don't forget to scan the code to get the information [HD Java learning roadmap]

And [full set of learning videos and supporting materials]

 

preface

In the previous chapter, brother one by one   It shows you how to add and execute custom filters in Spring Security to realize the verification code verification function. This implementation method is only one of the ways to realize the function of authentication code. Next, we will learn another implementation method, which is to use the AuthenticationProvider to realize the function of authentication code. Through this case, we will learn how to customize the AuthenticationProvider.

1, Introduction to certification provider

In the previous chapter, I took you to realize the graphic verification code effect by using the custom filter. Next, we use another way to realize the graphic verification code based on the custom authentication provider.

1. Authentication provider

In Chapter 11, Yige   I told you about the authentication and authorization implementation process of Spring Security, in which I explained the role of AuthenticationProvider. Next, let's take a look at the class diagram of AuthenticationProvider interface:

As can be seen from the above figure, AuthenticationProvider is an interface with a direct subclass AbstractUserDetailsAuthenticationProvider, which has two abstract methods: additionalAuthenticationChecks() and retrieveUser(), as shown in the following figure:

We can write a subclass to inherit the abstract user details authentification provider and copy the two abstract methods to meet our own needs. The Dao authen notation provider subclass in Spring Security implements authentication and authorization based on the database model by copying these two abstract methods.

Today, we will implement the verification function of graphic verification code by inheriting DaoAuthenticationProvider.

2. Introduction to webauthenticationdetails class

After understanding the AuthenticationProvider class above, we need to know another class WebAuthenticationDetails.

We know that there is a username password authentication token class in Spring Security, which encapsulates the user's principal and credentials information. This class also inherits the details information from its parent class AbstractAuthenticationToken.

The details information represents the additional information of the authenticated user, such as the remoteAddress and sessionId of the requesting user. These two information are defined in another WebAuthenticationDetails class, so we can use WebAuthenticationDetails to encapsulate the additional information of the user.

After understanding the above necessary API s, we can realize today's requirements.

2, Implement graphic verification code

1. Add dependent packages

As in the previous case, we can first create a new module and create a process strategy.

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

In this case, we still use the open source verification code solution kaptcha on github, so we need to add the dependency package of kaptcha on the basis of the original project.

2. Create Producer object

As in the previous case, create a CaptchaConfig configuration class, create a Producer object in this class, and configure the verification code object as necessary.

@Configuration
public class CaptchaConfig {

    @Bean
    public Producer captcha() {
¬†¬†¬†¬†¬†¬†¬†¬†//  Configure basic parameters of graphic verification code
        Properties properties = new Properties();
¬†¬†¬†¬†¬†¬†¬†¬†//  image width
        properties.setProperty("kaptcha.image.width", "150");
¬†¬†¬†¬†¬†¬†¬†¬†//  Picture length
        properties.setProperty("kaptcha.image.height", "50");
¬†¬†¬†¬†¬†¬†¬†¬†//  character set
        properties.setProperty("kaptcha.textproducer.char.string", "0123456789");
¬†¬†¬†¬†¬†¬†¬†¬†//  Character length
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        Config config = new Config(properties);
¬†¬†¬†¬†¬†¬†¬†¬†//  Use the default graphic verification code to implement, of course, you can also customize the implementation
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

}

3. Create an interface for generating verification code

After creating the Producer object above, create an interface to generate the verification code, which is responsible for generating the verification code picture and storing the verification code in the session.

@Controller
public class CaptchaController {

    @Autowired
    private Producer captchaProducer;

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

}

4. Custom exception

Next, customize a runtime exception to handle the exception prompt when the verification code fails.

public class VerificationCodeException extends AuthenticationException {

    public VerificationCodeException() {
        super("Verification of graphic verification code failed");
    }

}

5. Customize WebAuthenticationDetails

I introduced WebAuthenticationDetails to you above. I know that this class can encapsulate the user's additional information, so here we customize a WebAuthenticationDetails class to encapsulate the authentication code information, and compare the authentication code passed by the user with the authentication code saved in the session.

/**
 * Add additional user authentication information
 */  
public class MyWebAuthenticationDetails extends WebAuthenticationDetails {

    private String imageCode;

    private String savedImageCode;

    public String getImageCode() {
        return imageCode;
    }

    public String getSavedImageCode() {
        return savedImageCode;
    }

    /**
     * Supplement the verification code submitted by the user and the verification code saved by the session
     */  
    public MyWebAuthenticationDetails(HttpServletRequest request) {
        super(request);
        this.imageCode = request.getParameter("captcha");
        //Get session object
        HttpSession session = request.getSession();
        
        this.savedImageCode = (String) session.getAttribute("captcha");
        if (!StringUtils.isEmpty(this.savedImageCode)) {
¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†//  Clear the verification code casually, whether it is failure or success, so the client should refresh the verification code when login fails
            session.removeAttribute("captcha");
        }
    }

}

6. Customize AuthenticationDetailsSource

AuthenticationDetailsSource is an interface with a buildDetails method. This method will be called when creating a new details object of authentication, and a request parameter can be passed to the details object here, as shown in the following figure:

Therefore, here we define an AuthenticationDetailsSource class, build the WebAuthenticationDetails object defined above through this class, and pass the HttpServletRequest object to WebAuthenticationDetails.

@Component
public class MyWebAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest,WebAuthenticationDetails> {

    /**
     * Create a WebAuthenticationDetails object
     */  
    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest request) {

        return new MyWebAuthenticationDetails(request);
    }

}

7. Customize DaoAuthenticationProvider

Next, by inheriting the DaoAuthenticationProvider parent class, the verification operation of the graphic verification code is introduced.

/**
 * On top of the conventional database authentication, the function of graphic verification code is added
 */
@Component
public class MyAuthenticationProvider extends DaoAuthenticationProvider {

    /**
     * The constructor injects UserDetailService and PasswordEncoder
     */
    public MyAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        this.setUserDetailsService(userDetailsService);
        this.setPasswordEncoder(passwordEncoder);
    }

    /**
     * Add additional graphic verification code function over conventional authentication
     */
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
        //Get the associated details object in the token token and convert it into our custom MyWebAuthenticationDetails
        MyWebAuthenticationDetails details = (MyWebAuthenticationDetails) usernamePasswordAuthenticationToken.getDetails();
        String imageCode = details.getImageCode();
        String savedImageCode = details.getSavedImageCode();
        
¬†¬†¬†¬†¬†¬†¬†¬†//  Verify graphic verification code
        if (StringUtils.isEmpty(imageCode) || StringUtils.isEmpty(savedImageCode) || !imageCode.equals(savedImageCode)) {
            throw new VerificationCodeException();
        }

        //Before the normal authentication check, add additional verification about the graphic verification code
        super.additionalAuthenticationChecks(userDetails, usernamePasswordAuthenticationToken);
    }

}

8. Add SecurityConfig

Then create the SecurityConfig class and configure the AuthenticationDetailsSource and AuthenticationProvider classes we wrote earlier.

@SuppressWarnings("all")
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> myWebAuthenticationDetailsSource;

    @Autowired
    private AuthenticationProvider authenticationProvider;

    @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()
             //The user-defined AuthenticationDetailsSource is configured here
                .authenticationDetailsSource(myWebAuthenticationDetailsSource)
                .failureHandler(new SecurityAuthenticationFailureHandler())
                .successHandler(new SecurityAuthenticationSuccessHandler())
                .loginPage("/myLogin.html")
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                .csrf()
                .disable();
    }

    //Associate our customized AuthenticationProvider here
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {

        return NoOpPasswordEncoder.getInstance();
    }

}

9. Write test page

Finally, write a custom login page and add a reference to the verification code interface here. I list the core code of html here.

<body>¬†¬†¬†¬†¬†¬†¬†¬†<div¬†class="login">¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<h2>Access¬†Form</h2>¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<div¬†class="login-top">¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<h1>validate logon</h1>¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<form¬†action="/login"¬†method="post">¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<input¬†type="text"¬†name="username"¬†placeholder="username"¬†/>¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<input¬†type="password"¬†name="password"¬†placeholder="password"¬†/>¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<div¬†style="display:¬†flex;">¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<!--¬†Input box of new graphic verification code¬†-->¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<input¬†type="text"¬†name="captcha"¬†placeholder="captcha"¬†/>¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<!--¬†Picture pointing to graphic verification code API¬†-->¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<img¬†src="/captcha.jpg"¬†alt="captcha"¬†height="50px"¬†width="150px"¬†style="margin-left:¬†20px;">¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†</div>¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<div¬†class="forgot">¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<a¬†href="#">Forget password</a>¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<input¬†type="submit"¬†value="Sign in"¬†>¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†</div>¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†</form>¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†</div>¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<div¬†class="login-bottom">¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†<h3>new user&nbsp;<a¬†href="#"> note & nbsp; volume</a></h3>             </ div>         </ div>     </ body>

10. Code structure

The main code structure of this case is shown in the figure below. You can create it for reference.

11. Start project testing

Next, we start the project, jump to the login page, and we can see that the verification code has been created.

At this point, we can see the generated digital verification code. After we enter the correct user name, password and verification code, we can successfully log in and access the web interface.

So far, we have implemented the graphical verification code function based on the user-defined authentication provider. This implementation method is more complex than the first implementation method. In fact, it can meet our development needs.

Welfare at the end of the article

Have you learned today's content?

 

ūüĎÜūüĎÜūüĎÜ

Don't forget to scan the code to get the information [HD Java learning roadmap]

And [full set of learning videos and supporting materials]

Tags: Java Spring Spring Boot

Posted on Fri, 15 Oct 2021 01:10:30 -0400 by james_holden