关于Spring Security的认证流程心得

        最近用spring security框架来做认证授权工作,搞得头有点大了,借此机会,小结学习一番
        spring security是一个安全访问控制框架,其工作是基于一系列的过滤器来实现的,这些过滤器是框架的核心,但是他们不直接处理用户的认证和授权,而是通过AuthenticationManager(认证管理器)和AccessDecisionManager(授权管理器)来进行处理

认证工作流程图:
在这里插入图片描述
        当初始化spring security的时候,会创建一个名字SpringSecurityFilterChain的过滤器,其类型为FilterChainProxy,这个过滤器实现了javax.servlet.Filter,所以可以拦截到外部的请求。FilterChainProxy是一个代理,真正起作用的是SecurityFilterChain这个,我们找到SecurityFilterChain的实现类:DefaultSecurityFilterChain,如下代码所示,可以看到其内部维护了一个Filter集合

public final class DefaultSecurityFilterChain implements SecurityFilterChain {
    private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
    private final RequestMatcher requestMatcher;
    private final List filters;
    public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
        this(requestMatcher, Arrays.asList(filters));
    }
    public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List filters) {
        logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));
        this.requestMatcher = requestMatcher;
        this.filters = new ArrayList(filters);
    }
    public RequestMatcher getRequestMatcher() {
        return this.requestMatcher;
    }
    public List getFilters() {
        return this.filters;
    }
    public boolean matches(HttpServletRequest request) {
        return this.requestMatcher.matches(request);
    }
    public String toString() {
        return this.getClass().getSimpleName() + " [RequestMatcher=" + this.requestMatcher + ", Filters=" + this.filters + "]";
    }
}

在这个过滤器集合处中打上断点,debug运行,如下图所示:
在这里插入图片描述
下面列举几个比较主要的过滤器:
SecurityContextPersistenceFilter:

//整个拦截链的入口和出口
//请求开始的时候:
//1、从SecurityContextRepository获取SecurityContext对象
//2、把SecurityContext对像设置到SecurityContextHolder中
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
SecurityContextHolder.setContext(contextBeforeChainExecution);
//请求结束的时候
//1、从SecurityContextHolder中取出SecurityContext对象,并清理掉SecurityContextHolder中的
//2、将SecurityContext设置回SecurityContextRepository中
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());

LogoutFilter:

//拦截器主要拦截logout请求,也就是退出请求
//并调用一下方法
this.handler.logout(request, response, auth);
//logoutSuccessHandler这个可以通过自定义
this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);

AbstractAuthenticationProcessingFilter(UsernamePasswordAuthenticationFilter的父类):

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
    if (!this.requiresAuthentication(request, response)) {
        chain.doFilter(request, response);
    } else {
        try {
//attemptAuthentication这个在子类UsernamePasswordAuthenticationFilter中实现
//具体的可以看下面的代码   
//子类返回的认证结果         
            Authentication authenticationResult = this.attemptAuthentication(request, response);
            if (authenticationResult == null) {
                return;
            }
            this.sessionStrategy.onAuthentication(authenticationResult, request, response);
            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
            this.successfulAuthentication(request, response, chain, authenticationResult);
        } catch (InternalAuthenticationServiceException var5) {
            this.logger.error("An internal error occurred while trying to authenticate the user.", var5);
            this.unsuccessfulAuthentication(request, response, var5);
        } catch (AuthenticationException var6) {
            this.unsuccessfulAuthentication(request, response, var6);
        }
    }
}
//认证成功处理
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    //将我们的用户信息保存在全局中,后面就可以方便取到
    SecurityContextHolder.getContext().setAuthentication(authResult);
    if (this.logger.isDebugEnabled()) {
        this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
    }
    this.rememberMeServices.loginSuccess(request, response, authResult);
    if (this.eventPublisher != null) {
        this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
    }
    this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
//认证失败处理
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
    SecurityContextHolder.clearContext();
    this.logger.trace("Failed to process authentication request", failed);
    this.logger.trace("Cleared SecurityContextHolder");
    this.logger.trace("Handling authentication failure");
    this.rememberMeServices.loginFail(request, response);
    this.failureHandler.onAuthenticationFailure(request, response, failed);
}

UsernamePasswordAuthenticationFilter:

//从这一句也可以看出这个过滤器是拦截请求路径为login的POST请求
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST");
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);
        username = username != null ? username : "";
        username = username.trim();
        String password = this.obtainPassword(request);
        password = password != null ? password : "";
        //获取我们请求中的密码和用户名,并封装成UsernamePasswordAuthenticationToken对象
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        this.setDetails(request, authRequest);
        //AuthenticationManager(认证管理器)调用authenticate方法,对UsernamePasswordAuthenticationToken进行认证
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

ExceptionTranslationFilter:
异常过滤器,用来处理在认证过程中抛出的异常

看完几个主要的过滤器,接下来还是把重心放在UsernamePasswordAuthenticationFilter中this.getAuthenticationManager().authenticate(authRequest);认证的过程上,我们通过debug来调通这个过程:
1、在UsernamePasswordAuthenticationFilter中加入断点
在这里插入图片描述
2、从上述断点进去,进到ProviderManager类的authenticate方法
在这里插入图片描述

从上述断点的地方进来,则到这个类AbstractUserDetailsAuthenticationProvider的authenticate方法
在这里插入图片描述
首先看看系统用户信息的获取过程,从上述第一个断点进去,进到DaoAuthenticationProvider类中
在这里插入图片描述
接下来看看密码的匹配过程,则到DaoAuthenticationProvider的additionalAuthenticationChecks方法
在这里插入图片描述
密码匹配成功则继续运行,失败则抛出BadCredentialsException异常

流程大概总结:
1、UsernamePasswordAuthenticationFilter会将我们输入的用户名账号信息,封装成UsernamePasswordAuthenticationToken,并调用认证管理器AuthenticationManager对UsernamePasswordAuthenticationToken进行认证。2、AuthenticationManager的认证方法会调用DaoAuthenticationProvider的retrieveUser(根据用户名)来获取系统的用户信息(通过UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);),然后再将获取的系统对象的密码和我们传入的UsernamePasswordAuthenticationToken密码进行比对,如果比对符合,则认证成功。

补充:Authentication(认证信息)的结构(UsernamePasswordAuthenticationToken就是它的子接口)

public interface Authentication extends Principal, Serializable {
    Collection getAuthorities();获取一些权限信息

    Object getCredentials();//凭证信息,用户输入的密码,一般在认证后就会删除掉

    Object getDetails();//获取一些细节信息,比如用户ip或者session等

    Object getPrincipal();//身份信息,一般返回的就是UserDetails的实现类,代表着用户的详细信息

    boolean isAuthenticated();

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

如有错漏,还望大佬指出

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值