Can spring security dock multiple user tables at the same time?

Time:2021-1-14

@[toc]
This is also a question from a small partner:

Can spring security dock multiple user tables at the same time?

In fact, several of my friends have asked me this question, but this demand is relatively unpopular, and I haven’t written an article.

In fact, as long as you understand the previous article of brother song, this demand can be made. Because one of the core points is provider manager. If you understand this, the rest will be easy.

Today, SongGe takes a moment to analyze the core of this problem with you. At the same time, he uses a small case to demonstrate how to connect multiple data sources at the same time.

1. Principle

1.1 Authentication

As everyone who has played with spring security knows, there is a very important object called authentication in spring security. We can inject authentication anywhere to get the current login user information. Authentication itself is an interface, which is actually useful for users java.security.Principal For further encapsulation, let’s take a look at authentication Definition of

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

As you can see, there are not many methods in the interface. Let me give a general explanation

  1. The getauthorities method is used to obtain the user’s permissions.
  2. The getcredentials method is used to obtain user credentials, which are generally passwords.
  3. The getdetails method is used to get the details carried by the user, which may be the current request or something.
  4. The getprincipal method is used to obtain the current user, which may be a user name or a user object.
  5. Isauthenticated whether the current user is authenticated successfully.

Authentication, as an interface, defines some basic behaviors of users or principals. It has many implementation classes

Can spring security dock multiple user tables at the same time?

Among these implementation classes, the most commonly used one is the usernamepasswordauthenticationtoken, and each authentication has its own authenticationprovider to handle the verification. For example, the authenticationprovider that handles the usernamepasswordauthenticationtoken is daoauthenticationprovider.

1.2 AuthenticationManager

In spring security, the class used to handle authentication is AuthenticationManager, which is also called authentication manager.

Authentication manager specifies how spring security filters perform authentication, and returns an authenticated authentication object after successful authentication. Authentication manager is an interface, we can customize its implementation, but usually we use more of the provider manager provided by the system.

1.3 ProviderManager

Providemanager is the most commonly used authentication manager implementation class.

The provider manager manages a list of authentication providers. Each authentication provider is an authenticator. Different authentication providers are used to process the authentication of different authentication objects. A complete authentication process may go through multiple authentication providers.

Provider manager is equivalent to proxy multiple authentication providers, and their relationship is as follows:

Can spring security dock multiple user tables at the same time?

1.4 AuthenticationProvider

Authentication provider defines the verification logic in spring security. Let’s take a look at the definition of authentication provider

public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
    boolean supports(Class<?> authentication);
}

As you can see, there are only two methods in the authenticationprovider:

  • The authenticate method is used to verify the user’s identity.
  • Supports is used to determine whether the current authentication provider supports the corresponding authentication.

In a complete authentication, there may be multiple authentication providers, which are managed by the provider manager. For details, please refer to SongGe’s previous article: SongGe takes you through the spring security login process by hand.

Here, let’s focus on daoauthenticationprovider, because it is the most commonly used one. When we log in with a user name / password, we use it. The parent class of daoauthenticationprovider is abstractuserdetailsauthenticationprovider. Let’s start from its parent class:

public abstract class AbstractUserDetailsAuthenticationProvider implements
        AuthenticationProvider, InitializingBean, MessageSourceAware {
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                : authentication.getName();
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;
            try {
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException notFound) {
                logger.debug("User '" + username + "' not found");

                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials",
                            "Bad credentials"));
                }
                else {
                    throw notFound;
                }
            }
        }

        try {
            preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user,
                    (UsernamePasswordAuthenticationToken) authentication);
        }
        catch (AuthenticationException exception) {
            if (cacheWasUsed) {
                cacheWasUsed = false;
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
                preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            else {
                throw exception;
            }
        }

        postAuthenticationChecks.check(user);

        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;

        if (forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return createSuccessAuthentication(principalToReturn, authentication, user);
    }
    public boolean supports(Class<?> authentication) {
        return (UsernamePasswordAuthenticationToken.class
                .isAssignableFrom(authentication));
    }
}

The code of abstract user details authentication provider is quite long. Here we focus on two methods: authenticate and supports.

The authentication method is used for authentication. Let’s take a brief look at the method flow

  1. First, the login user name is extracted from authentication.
  2. Then call the retrieveuser method with the user name to get the current user object. In this step, we will call the loaduserbyuser method we wrote when logging in, so the user returned here is actually your login object. Please refer to the org / javaboy / VHR / service of microhr/ HrService.java#L34 You can also refer to the previous article in this series: spring The combination of security and spring data JPA makes security management easier!.
  3. Next, call pre AuthenticationChecks.check Method to check whether each account status attribute in user is normal, such as whether the account is disabled, locked, expired, etc.
  4. The additionalauthenticationchecks method is used for password comparison. Many friends are curious about how to compare the encrypted passwords of spring security. You can see it here. Because the logic of comparison is very simple, I won’t post the code here. However, note that the additionalauthenticationchecks method is an abstract method, which is implemented in a subclass of abstractuserdetailsauthenticationprovider, that is, daoauthenticationprovider. This is easy to understand, because abstractuserdetailsauthenticationprovider, as a general parent class, deals with some general behaviors. When we log in, some login methods do not need a password, so the additionalauthenticationchecks method is generally implemented by its subclass, which is in daoauthenticationprovider Class, the additionalauthenticationchecks method is used for password comparison. In other authentication providers, the role of additionalauthenticationchecks method is not necessarily.
  5. Finally, in the post AuthenticationChecks.check Method to check whether the password has expired.
  6. Next, there is a forceprincipalasstring property. This is whether to force the principal property in authentication to be set as a string. This property is actually set as a string in the usernamepasswordauthenticationfilter class at the beginning (that is, the user name). But by default, when the user logs in successfully, the, The value of this property becomes the object of the current user. The reason for this is that forceprincipalasstring is false by default, but in fact, it doesn’t need to be changed. Instead, it uses false. In this way, it is much more convenient to obtain the current user’s information in the later stage.
  7. Finally, a new usernamepasswordauthenticationtoken is constructed through the method of createsucessauthentication.

The supports method is relatively simple. It is mainly used to determine whether the current authentication is a usernamepasswordauthenticationtoken.

Because abstractuserdetailsauthenticationprovider has implemented the authenticate and supports methods, in daoauthenticationprovider, we mainly focus on the additionalauthenticationchecks method

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    @SuppressWarnings("deprecation")
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }
        String presentedPassword = authentication.getCredentials().toString();
        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }
    }
}

As you can see, the additionalauthenticationchecks method is mainly used for password comparison, and the logic is relatively simple. That is to call the matches method of passwordencoder to do the comparison. If the password is not correct, you can directly throw an exception.

Under normal circumstances, we log in with a user name / password, and eventually we will come to this step.

The authenticationprovider is called through the providermanager # authenticate method. Since we may have multiple authenticationproviders in one authentication, we will traverse the authenticationproviders one by one in the provideranager? Authenticate method, and call their authenticate method for authentication. Let’s take a look at the provideranager? Authenticate method

public Authentication authenticate(Authentication authentication)
        throws AuthenticationException {
    for (AuthenticationProvider provider : getProviders()) {
        result = provider.authenticate(authentication);
        if (result != null) {
            copyDetails(authentication, result);
            break;
        }
    }
    ...
    ...
}

As you can see, in this method, it will traverse all the authenticationproviders and call its authenticate method for authentication.

Well, after the general certification process is finished, I believe you have understood where we are going to start.

2. Cases

To access multiple data sources, we only need to provide multiple user-defined authentication providers, which are managed by the provider manager. Each authentication provider corresponds to a different data source.

First, we create a spring boot project to introduce security and web dependency

<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>

Then create a test controller as follows:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
    @GetMapping("/admin")
    public String admin() {
        return "admin";
    }
}

Finally, configure securityconfig:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Primary
    UserDetailsService us1() {
        return new InMemoryUserDetailsManager(User.builder().username("javaboy").password("{noop}123").roles("admin").build());
    }
    @Bean
    UserDetailsService us2() {
        return new InMemoryUserDetailsManager(User.builder().username("sang").password("{noop}123").roles("user").build());
    }
    @Override
    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        DaoAuthenticationProvider dao1 = new DaoAuthenticationProvider();
        dao1.setUserDetailsService(us1());

        DaoAuthenticationProvider dao2 = new DaoAuthenticationProvider();
        dao2.setUserDetailsService(us2());

        ProviderManager manager = new ProviderManager(dao1, dao2);
        return manager;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/hello").hasRole("user")
                .antMatchers("/admin").hasRole("admin")
                .and()
                .formLogin()
                .loginProcessingUrl("/doLogin")
                .permitAll()
                .and()
                .csrf().disable();
    }
}
  1. First of all, two examples of userdetailsservice are provided. Here, for the convenience of demonstration, I use inmemory userdetailsmanager to build the userdetailsservice. In the actual development, you can define the userdetailsservice by yourself. You can refer to (spring Security + spring data JPA join hands, security management is only simpler!) One article.
  2. Next, customize the authentication manager. The instance of authentication manager is actually provider manager. First, two daoauthenticationprovider instances are constructed, each passing in a different userdetailsservice instance, which means that each daoauthenticationprovider represents a userdetailsservice instance.
  3. Finally, configure httpsecurity, which has been mentioned many times in the previous articles in this series, so I won’t repeat it here.

According to the principle in the first section, during user authentication, the two daoauthenticationproviders will be executed in turn, so that the two data sources we configured will take effect.

After the configuration is complete, start the project.

Testing in postman, we can use javaboy to log in. After successful login, the user has the role of admin, so he can access http://localhost : 8080 / Admin, you can also use sang to log in. The user who logs in has the user role and can access http://localhost :8080/hello。

3. Summary

Well, this article shares with my friends how to access multiple data sources in spring security at the same time. If you are interested, you can try it

WeChat official account [background]multiusersYou can get the download address of this case

If you feel that you have something to gain, remember to watch and encourage brother song

Recommended Today

General method of Tkinter (21) components

method explain after(delay_ms, callback=None, *args) At least delay_ Ms after calling callback, no callback, equivalent time.sleep (); returns an ID to cancel after_ The cancel () method uses after_cancel(id) Cancel the callback of after method call after_idle(func, *args) Similar to the after method, but called when there is no event idle bell() A beep bind(sequence=None, […]