SpringSecurity登录验证步骤和源码浅析

该博客可能需要结合https://blog.csdn.net/XlxfyzsFdblj/article/details/8208344来看。
下面主要浅析spring security登录校验的过程和源码。
主要说说一些重要的filter
       一、点击登录后,发送请求首先被UsernamePasswordAuthenticationFilter拦截,该拦截器继承AbstractAuthenticationProcessingFilter类。UsernamePasswordAuthenticationFilter类有什么用呢?下面是该类的源代码(省略了一部分)。可以看到主要就是获取前端传过来的账号密码,如果想要做更多的事情,例如不只是获取账号密码,还想处理验证码,或者Jwt的校验,想要更自由的控制业务,则可以继承UsernamePasswordAuthenticationFilter或者直接继承UsernamePasswordAuthenticationFilter的父类AbstractAuthenticationProcessingFilter替代它。做了这些变动需要在WebSecurityConfiguration注册配置下。

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    /**
    * 登录属性的默认值,如果想要自定义在loginProcessingUrl后面添
    * 加.usernameParameter("xxx").passwordParameter("xxx")即可。
    */
    private String usernameParameter = "username";
    private String passwordParameter = "password";
    private boolean postOnly = true;

    public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }

    /**
    * 获取前端传过来的参数username, password
    */
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            // 前往下一个主要类
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(this.passwordParameter);
    }

    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(this.usernameParameter);
    }
}

       二、当UsernamePasswordAuthenticationFilter类中attemptAuthentication方法执行到 return this.getAuthenticationManager().authenticate(authRequest);就到下一个较为重要的类DaoAuthenticationProvider,该类继承了AbstractUserDetailsAuthenticationProvider
下面是它的源代码(一部分),这个类主要就是进行密码匹对的。

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
    private PasswordEncoder passwordEncoder;
    private volatile String userNotFoundEncodedPassword;
    private UserDetailsService userDetailsService;

    public DaoAuthenticationProvider() {
        this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
    }

    /**
    * 进行密码的匹配
    */
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            this.logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
            String presentedPassword = authentication.getCredentials().toString();
            // 根据你前面WebSecurityConfiguration自定义的加密算法来对前端的密码进行加密码校验。
            if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
                this.logger.debug("Authentication failed: password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
        }
    }

    protected void doAfterPropertiesSet() throws Exception {
        Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
    }

    /**
    * 获取前面自定义的MyuUserDetailsService类, 调用loadUserByUsername方法获取数据库数据。
    */
    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        this.prepareTimingAttackProtection();

        try {
            // 根据用户名,获取数据库数据。
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
            } else {
                return loadedUser;
            }
        } catch (UsernameNotFoundException var4) {
            this.mitigateAgainstTimingAttack(authentication);
            throw var4;
        } catch (InternalAuthenticationServiceException var5) {
            throw var5;
        } catch (Exception var6) {
            throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
        }
    }

    private void prepareTimingAttackProtection() {
        if (this.userNotFoundEncodedPassword == null) {
            this.userNotFoundEncodedPassword = this.passwordEncoder.encode("userNotFoundPassword");
        }

    }

该类有两个重要的方法additionalAuthenticationChecks和retrieveUser,执行顺序为retrieveUser –> additionalAuthenticationChecks
先看retrieveUser方法,方法中有一句

UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

大家是不是感觉很眼熟,对就是这里调用了我面前面自定义配置的MyUserDetailsService类中的loadUserByUsername方法获取数据库的账号密码信息。
retrieveUser方法执行完后就到additionalAuthenticationChecks方法,该方法中有块代码

if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
    this.logger.debug("Authentication failed: password does not match stored value");
    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}

该代码块就是进行前端传过来的代码和数据库的代码进行匹对,this.passwordEncoder就是根据你前面在WebSecurityConfiguration类中自定义的加密算法进行对密码的处理(它们都实现了PasswordEncoder接口)。

public interface PasswordEncoder {
    String encode(CharSequence var1);

    boolean matches(CharSequence var1, String var2);
}

       对于DaoAuthenticationProvider这个类,也可以进行继承它。这样就可以在不同的业务场景下灵活运用了,例如可以进行第三方或者短信登录验证等。

       三、最后如果验证成功就会到我们前面自定义的验证成功类MyAuthenticationSuccessHandler,这样子我们可以很方便的对前端进行Json数据返回等操作。

@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        // 允许跨域
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        // 允许自定义请求头token(允许head跨域)
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
        httpServletResponse.getWriter().write(JsonUtil.objectToJson(JsonData.success()));
    }
}

假如没有自定义返回,则就会跳转到默认SimpleUrlAuthenticationSuccessHandler类中

public class SimpleUrlAuthenticationSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements AuthenticationSuccessHandler {
    public SimpleUrlAuthenticationSuccessHandler() {
    }

    public SimpleUrlAuthenticationSuccessHandler(String defaultTargetUrl) {
        this.setDefaultTargetUrl(defaultTargetUrl);
    }

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        this.handle(request, response, authentication);
        this.clearAuthenticationAttributes(request);
    }

    protected final void clearAuthenticationAttributes(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            session.removeAttribute("SPRING_SECURITY_LAST_EXCEPTION");
        }
    }
}

最后综上主要的类之间的跳转过程为
UsernamePasswordAuthenticationFilter –> DaoAuthenticationProvider –> UserDetails –> PasswordEncoder
再到最后的AuthenticationSuccessHandler或AuthenticationFailureHandler

猜你喜欢

转载自blog.csdn.net/XlxfyzsFdblj/article/details/82084183
今日推荐