Spring Security custom login authentication management and throw custom information

The default filter for authentication processing by security is UsernamePasswordAuthenticationFilter . By looking at the source code, you can see that the method for authentication processing is attemptAuthentication . The main function of this method is to encapsulate the account and password entered by the user into a UsernamePasswordAuthenticationToken object, and then use the setDetails method to This object is stored, and then call this.getAuthenticationManager().authenticate(authRequest) method to return an Authentication object. Many other classes called by this process this.getAuthenticationManager().authenticate(authRequest) are as follows:

UsernamePasswordAuthenticationFilter-->ProviderManager-->AbstractUserDetailsAuthenticationProvider-->DaoAuthenticationProvider-->JdbcDaoImpl

I found a picture from the Internet and posted it:

 

1: After entering the user name and password, click login to reach the attemptAuthentication method of UsernamePasswordAuthenticationFilter , which is the entrance to login

2: Call ProviderManager The authenticate method, and ProviderManager entrusted to AbstractUserDetailsAuthenticationProvider of authenticate to do, then AbstractUserDetailsAuthenticationProvider turn calls DaoAuthenticationProvider in retrieveUser , in DaoAuthenticationProvider class retrieveUser approach, because to get a user name entered UserDetails , so the call The loadUserByUsername method in JdbcDaoImpl , this method returns a queried user ( UserDetails ) to its caller , and finally a UserDetails will be obtained in the authenticate method of AbstractUserDetailsAuthenticationProviderObject user ,

3: Then execute preAuthenticationChecks.check(user) and additionalAuthenticationChecks (user, (UsernamePasswordAuthenticationToken) authentication) in DaoAuthenticationProvider ; the former method is to judge whether the queried user is available or locked, etc., and the latter is to judge the queried user Whether the password of the object is the same as the password of authentication (this object is actually to store the user name and password entered by the user), if the same, it means that the login is successful, if it is wrong, then throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails); The message Bad credentials is the information after the login failed

I think the above picture is a bit wrong. I can see from the debugger that the additionalAuthenticationChecks method should be in the called DaoAuthenticationProvider instead of the AbstractUserDetailsAuthenticationProvider .

===================== Dividing line ========================== ===============================

It can be seen from the above that if we need to modify the authentication rules or customize the password authentication failure exception, we can override the additionalAuthenticationChecks method by inheriting DaoAuthenticationProvider , and then inject our own AuthenticationProvider class.

The first step: inherit DaoAuthenticationProvider and override the additionalAuthenticationChecks method

I moved the method of DaoAuthenticationProvide and changed it to my own needs. The password error exception is currently thrown in it. You can do password verification according to your needs. It should be noted that do not move the following line of code,

public DaoAuthenticationProvider() {

   setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());

}

Because PasswordEncoderFactories.createDelegatingPasswordEncoder() in DaoAuthenticationProvide calls BCryptPasswordEncoder, but in my own code, it calls DelegatingPasswordEncoder. The
specific reason is not known yet. This will cause the password verification to fail, so I created a new BCryptPasswordEncoder for calibration. Test

package com.XXX.XXXX.auth.filter;

import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * 类描述:
 *
 * @author :carry
 * @version: 1.0  CreatedDate in  2019年11月05日
 * <p>
 * 修订历史: 日期			修订者		修订描述
 */
@Component
public class SelfLoginAuthenticationProvider extends DaoAuthenticationProvider {

    private static PasswordEncoder passwordEncoder;

    public SelfLoginAuthenticationProvider(UserDetailsService userDetailsService) {
        // 这个地方一定要对userDetailsService赋值,不然userDetailsService是null
        setUserDetailsService(userDetailsService);
    }

    @SuppressWarnings("deprecation")
    protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                  UsernamePasswordAuthenticationToken authentication) {
        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }

        //使用BCryptPasswordEncoder不要使用代理DelegatingPasswordEncoder
        if (null == passwordEncoder) {
            passwordEncoder = new BCryptPasswordEncoder();
        }

        String presentedPassword = authentication.getCredentials().toString();

        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "密码错误!"));
        }

    }


}

Two: inject your own AuthenticationProvide

There are two ways, the first is to inject
@Override in the same way as the custom userDetailsService

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

    // auth.authenticationProvider(new SelfLoginAuthenticationProvider(userDetailsService));

    auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}

But this kind of problem is that there will be multiple AuthenticationProvide, and you will always be the first to call your own, and finally call your own, so it will overwrite your own

The second rewrite the AuthenticationManager authentication manager in WebSecurityConfigurerAdapter

 @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        ProviderManager authenticationManager = new ProviderManager(Arrays.asList(selfLoginAuthenticationProvider));
        authenticationManager.setEraseCredentialsAfterAuthentication(false);
        return authenticationManager;
    }
通过源码看出里面有2个方法,需要用第一个方法而不要用第二个,这样就会使用自己自定义的SelfLoginAuthenticationProvider

In this way, you can customize the login request processing.
The three main areas and steps are required.
1: Inherit DaoAuthenticationProvide and rewrite additionalAuthenticationChecks
2: Rewrite the AuthenticationManager authentication manager in WebSecurityConfigurerAdapter to ensure that only your own AuthenticationProvide is used
3: Create a BCryptPasswordEncoder yourself for password verification. Do not use proxy DelegatingPasswordEncoder

 

Guess you like

Origin blog.csdn.net/CarryBest/article/details/102929683