Spring Security 自定义登录认证管理以及抛出自定义信息

security默认的做认证处理的过滤器为UsernamePasswordAuthenticationFilter,通过查看源码知道,做认证处理的方法为attemptAuthentication,这个方法的主要作用就是将用户输入的账号和密码,封装成一个UsernamePasswordAuthenticationToken对象,然后通过setDetails方法将这个对象储存起来,然后调用this.getAuthenticationManager().authenticate(authRequest)方法返回一个Authentication对象。其中这个过程this.getAuthenticationManager().authenticate(authRequest)又调用的其他的许多类,调用的大体如下:

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

从网上找了一张图贴上:

 

1:当输入用户名和密码后,点击登陆到达UsernamePasswordAuthenticationFilterattemptAuthentication方法,这个方法是登陆的入口

2:调用ProviderManager中的authenticate方法,而ProviderManager委托给AbstractUserDetailsAuthenticationProviderauthenticate做,然后AbstractUserDetailsAuthenticationProvider又调用DaoAuthenticationProvider中的retrieveUser,在DaoAuthenticationProvider类的retrieveUser方法中,因为要通过输入的用户名获取到一个UserDetails,所以其调用JdbcDaoImpl中的loadUserByUsername方法,该方法给它的调用者返回一个查询到的用户(UserDetails),最终AbstractUserDetailsAuthenticationProviderauthenticate方法中会得到一个UserDetails对象user

3:接着执行preAuthenticationChecks.check(user)和DaoAuthenticationProvider里的additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);其中前面这个方法是判断,查询的用户是否可用或被锁等,后面的则是判断查询到的user对象的密码是否和authentication(这个对象其实就是存储用户输入的用户名和密码)的密码一样,若一样则表示登陆成功,若错误,则throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);Bad credentials这个消息就是登陆失败后的信息

上面的图我认为画的有点不对,通过debugger看出,additionalAuthenticationChecks方法应该是调用的DaoAuthenticationProvider里面的,而不是AbstractUserDetailsAuthenticationProvider里面的。

=====================分割线=========================================================

由上面就可知,我们如果需要修改验证的规则或者自定义密码验证失败异常,就可以通过继承DaoAuthenticationProvider重写additionalAuthenticationChecks方法,然后注入自己的AuthenticationProvider类。

第一步:继承DaoAuthenticationProvider重写additionalAuthenticationChecks方法

把DaoAuthenticationProvide的方法搬过来,改成自己的需求,这里面目前就抛出了密码错误异常,可以根据自己的需求来做密码验证。需要注意的是不要把下面行代码搬过来,

public DaoAuthenticationProvider() {

   setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());

}

因为在DaoAuthenticationProvide里PasswordEncoderFactories.createDelegatingPasswordEncoder()调用的是BCryptPasswordEncoder,而放在自己的代码里,调用的就是DelegatingPasswordEncoder
具体原因还不知道,这样会导致密码校验失败,所以我自己new了一个BCryptPasswordEncoder用于校验

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",
                    "密码错误!"));
        }

    }


}

二:注入自己的AuthenticationProvide

有2种方式,第一种通过和自定义的userDetailsService同样的方式注入
@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

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

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

但是这种有个问题就是会有多个AuthenticationProvide,而且始终第一个调用你自己的,最后调用自带的,所以会把你自己的覆盖了

第二种重写WebSecurityConfigurerAdapter里的AuthenticationManager认证管理器

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

这样就可以自定义登录请求处理了
需要主要的3个地方以及步骤。
1:继承DaoAuthenticationProvide重写additionalAuthenticationChecks
2:重写WebSecurityConfigurerAdapter里的AuthenticationManager认证管理器,保证只使用自己的AuthenticationProvide
3:自己创建一个BCryptPasswordEncoder进行密码校验不要使用代理DelegatingPasswordEncoder

猜你喜欢

转载自blog.csdn.net/CarryBest/article/details/102929683