This is the 8th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Writing in the front

Today we are going to learn how to use advanced themes to protect web applications. Advanced themes include filters, CSRF protection, CORS, and global methods. These are very useful features.

This time is divided into two parts. As a phased conclusion, today’s content will use these features to build a typical Authentication mechanism in the security field, namely multi-factor Authentication (MFA) mechanism.

Let’s share some cases below

Case design and initialization

In today’s case, our approach to building multifactor authentication was not to adopt a mature third-party solution, but to design and implement a simple and complete authentication mechanism based on Spring Security features.

Multi-factor authentication: Multi-factor authentication is a method of secure access control. The basic design concept is that users need to pass at least two authentication mechanisms to access the final resource.

So how do we implement multiple authentication mechanisms? A common approach is to take two steps. The first step is to obtain an Authentication Code based on the user name and password, and the second step is to secure access based on the user name and the Authentication Code. The basic execution process based on this multi-factor authentication is shown below:

System initialization

In order to realize multi-factor authentication, we need to build an independent authentication Service, auth-service, which provides authentication based on user name + password and user name + authentication code. Of course, the premise of authentication is to build the User system, so we need to provide the User entity class as follows:

@Entity
public class User {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    private String username;
    private String password;
}
Copy the code

As you can see, the User object contains definitions for the Username Username and Password Password. Similarly, the AuthCode object representing the authentication Code as shown below contains the definition of the Username and the specific authentication Code field:

@Entity
public class AuthCode {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    private String username;
    private String code;   
}
Copy the code

Based on the User and AuthCode entity objects, we also give the corresponding SQL definition for creating a database table, as follows:

CREATE TABLE IF NOT EXISTS `spring_security_demo`.`user` (
    `id` INT NOT NULL AUTO_INCREMENT,
    `username` VARCHAR(45) NULL,
    `password` TEXT NULL,
    PRIMARY KEY (`id`));
 
CREATE TABLE IF NOT EXISTS `spring_security_demo`.`auth_code` (
    `id` INT NOT NULL AUTO_INCREMENT,
    `username` VARCHAR(45) NOT NULL,
    `code` VARCHAR(45) NULL,
    PRIMARY KEY (`id`));)Copy the code

With authentication services, we then need to build a Business Service, business-Service, which integrates authentication services to perform specific authentication operations and return access tokens to the client system. Therefore, in terms of dependencies, business-service calls auth-service, as shown in the following figure:

Next, we start with these two services respectively to realize the multi-factor authentication mechanism.

Realize multi-factor authentication mechanism

For the multi-factor authentication mechanism, the implementation of authentication service is the foundation, but the difficulty is not great, let’s see.

Implementing authentication Services

Formally, the authentication service is also a Web service, so you need to internally expose HTTP endpoints by building Controller layer components. To do this, we built the following AuthController:

@RestController
public class AuthController {
 
    @Autowired
    private UserService userService;
 
    / / add User
    @PostMapping("/user/add")
    public void addUser(@RequestBody User user) {
        userService.addUser(user);
    }
 
    // The user is authenticated for the first time using the user name and password
    @PostMapping("/user/auth")
    public void auth(@RequestBody User user) {
        userService.auth(user);
    }
 
    // The user name + authentication code is used for secondary authentication
    @PostMapping("/authcode/check")
    public void check(@RequestBody AuthCode authCode, HttpServletResponse response) {
        if (userService.check(authCode)) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else{ response.setStatus(HttpServletResponse.SC_FORBIDDEN); }}}Copy the code

As you can see, in addition to an HTTP endpoint for adding user information, we implemented the “/user/ Auth “endpoint for first authentication of a user using a user name + password, and the “/ AuthCode/Check” endpoint for second authentication using a user name + authentication code.

The implementation logic behind the two core endpoints is in UserService. Let’s start with the auth() method:

public void auth(User user) {
        Optional<User> o =
                userRepository.findUserByUsername(user.getUsername());
 
        if(o.isPresent()) {
            User u = o.get();
            if (passwordEncoder.matches(user.getPassword(), u.getPassword())) {
                 // Generates or refreshes the authentication code
                generateOrRenewAutoCode(u);
            } else {
                throw new BadCredentialsException("Bad credentials."); }}else {
            throw new BadCredentialsException("Bad credentials."); }}Copy the code

GenerateOrRenewAutoCode () the generateOrRenewAutoCode() method is responsible for the key flow in the above code, which is the refresh of the authentication code after the user password is matched.

private void generateOrRenewAutoCode (User u) {
        String generatedCode = GenerateCodeUtil.generateCode();
 
        Optional<AuthCode> autoCode = autoCodeRepository.findAuthCodeByUsername(u.getUsername());
        if (autoCode.isPresent()) {// Refresh the authentication code if it exists
            AuthCode code = autoCode.get();
            code.setCode(generatedCode);
        } else {// If no authentication code is found, a new authentication code is generated and saved
            AuthCode code = newAuthCode(); code.setUsername(u.getUsername()); code.setCode(generatedCode); autoCodeRepository.save(code); }}Copy the code

The process of the above method is also clear: first generate an authentication code by calling the generateCode() method of the utility class GenerateCodeUtil, and then decide whether to refresh the existing authentication code based on the current state in the database, or simply generate a new authentication code and save it. Therefore, each call to UserService’s auth() method is a dynamic reset of the user’s authentication code.

Once a user obtains an authentication code and accesses the system through the authentication code, the authentication service verifies the authentication code to determine whether it is valid. The authentication code is verified as follows:

public boolean check(AuthCode authCodeToValidate) {
        Optional<AuthCode> authCode = autoCodeRepository.findAuthCodeByUsername(authCodeToValidate.getUsername());
        if (authCode.isPresent()) {
            AuthCode authCodeInStore = authCode.get();
            if (authCodeToValidate.getCode().equals(authCodeInStore.getCode())) {
                return true; }}return false;
}
Copy the code

The logic here is simple: the authentication code obtained from the database is compared to the authentication code passed in by the user.

At this point, the core functions of the authentication service have been built

Next time we’ll look at the implementation of business services. We still have to learn it little by little so we can digest it.

See you next time!!

overtones

Thank you for reading, and if you feel like you’ve learned something, you can like it and follow it. Also welcome to have a question we comment below exchange

Come on! See you next time!

To share with you a few I wrote in front of a few SAO operation

Copy object, this operation is a little SAO!

Dry! SpringBoot uses listening events to implement asynchronous operations