流程源码

核心组件

1、SecurityContextHolder:提供对 SecurityContext 的访问

2、SecurityContext:持有 Authentication 对象和其他可能需要的信息

3、AuthenticationManager:其中可以包含多个 AuthenticationProvider

4、ProviderManager:为 AuthenticationManager 接口的实现类

5、AuthenticationProvider:主要用来进行认证操作的类,调用其中的 authenticate() 方法去进行认证操作

6、Authentication:Spring Security 方式的认证主体,即认证信息封装类

7、GranteAuthority:对认证主体的应用层面的授权,含当前用户的权限信息,通常使用角色表示

8、UserDetails:构建 Authentication 对象的必须信息,可以自定义,可能需要访问数据库得到

9、UserDetailsService:通过 username 构建 UserDetails 对象,根据 username,通过 loadUserByUsername 获取 UserDetails 对象,可以基于自身业务进行自定义的实现,如:通过数据库,xml,缓存获取等

10、WebSecurityConfigurerAdapter(已弃用):自定义 Spring Security 安全框架的配置类,需要继承 WebSecurityConfigurerAdapter,重写其中的方法 configure

 

认证流程

1、第一次请求:UsernamePasswordAuthenticationFilter,Authentication(未认证)

2、AuthenticationManager(接口实现类:ProviderManager)管理多个 AuthenticationProvider,并委托对应的 AuthenticationProvider(接口实现类:DaoAuthenticationProvider),关联 UserDetailsService

3、查询数据库,返回 UserDetails

4、认证通过后,封装查询数据到 Authentication(已认证)

5、Authentication(已认证)封装到 SecurityContext(接口实现类:SecurityContextImpl)

6、SecurityContext 存入到 SecurityContextHolder,使用 SecurityContextPersistenceFilter(已弃用,使用 SecurityContextHolderFilter 代替)返回响应

 

UsernamePasswordAuthenticationFilter

1、父类:AbstractAuthenticationProcessingFilter

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter

2、调用父类 doFilter 方法

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    //判断是否为POST请求
    if (!requiresAuthentication(request, response)) {
        chain.doFilter(request, response);
        return;
    }
    try {
        //调用子类UsernamePasswordAuthenticationFilter的attemptAuthentication方法进行认证(即数据库查询用户密码),认证成功后,把认证信息封装到Authentication对象中
        Authentication authenticationResult = attemptAuthentication(request, response);
        if (authenticationResult == null) {
            // return immediately as subclass has indicated that it hasn't completed
            return;
        }
        //策略处理,配置最大并发数
        this.sessionStrategy.onAuthentication(authenticationResult, request, response);
        //认证成功,continueChainBeforeSuccessfulAuthentication == true,默认为false
        if (this.continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
        //认证成功,执行successfulAuthentication
        successfulAuthentication(request, response, chain, authenticationResult);
    }
    //捕获异常,由异常处理器处理失败,认证失败,执行unsuccessfulAuthentication
    catch (InternalAuthenticationServiceException failed) {
        this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
        unsuccessfulAuthentication(request, response, failed);
    }
    catch (AuthenticationException ex) {
        // Authentication failed
        unsuccessfulAuthentication(request, response, ex);
    }
}

3、调用 attemptAuthentication 方法,进行认证

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    throws AuthenticationException {
    //判断是否为POST请求,若是,继续执行,否则抛出异常
    if (this.postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }
    //获取表单数据
    String username = obtainUsername(request);
    username = (username != null) ? username.trim() : "";
    String password = obtainPassword(request);
    password = (password != null) ? password : "";
    //创建用户名密码认证令牌,标记为未认证状态
    UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
                                                                                                          password);
    //允许子类将请求中的details属性设置给对象
    setDetails(request, authRequest);
    //调用authenticate方法进行认证
    return this.getAuthenticationManager().authenticate(authRequest);
}

4、UsernamePasswordAuthenticationToken 的两个构造器,通过 this.setAuthenticated() 设置认证状态,true 表示认证成功,false 表示没认证

//这个构造函数可以被任何希望创建UsernamePasswordAuthenticationToken的代码安全使用,因为isAuthenticated()将返回false
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
    super(null);
    this.principal = principal;
    this.credentials = credentials;
    setAuthenticated(false);
}
//这个构造函数只能由AuthenticationManager或AuthenticationProvider实现使用,它们满足于产生一个可信的(即isAuthenticated() = true)认证令牌
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
                                           Collection<? extends GrantedAuthority> authorities) {
    super(authorities);
    this.principal = principal;
    this.credentials = credentials;
    super.setAuthenticated(true); // must use super, as we override
}

5、ProviderManager

(1)内部维护一个列表,存放多种认证方式(委托者模式),每种认证方式对应一个 AuthenticationProvider

private List<AuthenticationProvider> providers = Collections.emptyList();

(2)authenticate 认证方法

@Override
//传入未认证的Authentication对象
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    //通过反射,获取传入Authentication类型,即UsernamePasswordAuthenticationToken
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    AuthenticationException parentException = null;
    Authentication result = null;
    Authentication parentResult = null;
    int currentPosition = 0;
    int size = this.providers.size();
    //遍历List<AuthenticationProvider> providers
    for (AuthenticationProvider provider : getProviders()) {
        //判断当前AuthenticationProvider是否支持UsernamePasswordAuthenticationToken类型
        if (!provider.supports(toTest)) {
            continue;
        }
        if (logger.isTraceEnabled()) {
            logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
                                           provider.getClass().getSimpleName(), ++currentPosition, size));
        }
        //成功找到适配当前认证方式的AuthenticationProvider,此处为DaoAuthenticationProvider
        try {
            //调用AuthenticationProvider的authenticate进行认证
            result = provider.authenticate(authentication);
            //认证成功,返回的result为已标记认证Authentication对象
            if (result != null) {
                //认证成功后,将传入的Authentication对象中的details信息,拷贝到已认证的Authentication对象
                copyDetails(authentication, result);
                break;
            }
        }
        catch (AccountStatusException | InternalAuthenticationServiceException ex) {
            prepareException(ex, authentication);
            //SEC-546:如果认证失败是由于无效的账户状态,应避免轮询其他provider
            throw ex;
        }
        catch (AuthenticationException ex) {
            lastException = ex;
        }
    }
    if (result == null && this.parent != null) {
        //认证失败,使用父类AuthenticationManager进行验证
        try {
            parentResult = this.parent.authenticate(authentication);
            result = parentResult;
        }
        catch (ProviderNotFoundException ex) {
            // ignore as we will throw below if no other exception occurred prior to
            // calling parent and the parent
            // may throw ProviderNotFound even though a provider in the child already
            // handled the request
        }
        catch (AuthenticationException ex) {
            parentException = ex;
            lastException = ex;
        }
    }
    if (result != null) {
        //认证完成,从reslut中删除凭证和其他秘密数据,要求相关类实现CredentialsContainer接口
        if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
            //CredentialsContainer接口的移除方法eraseCredentials
            ((CredentialsContainer) result).eraseCredentials();
        }
        //发布认证成功的事件
        if (parentResult == null) {
            this.eventPublisher.publishAuthenticationSuccess(result);
        }

        return result;
    }

    //认证失败后,抛出的失败异常信息
    if (lastException == null) {
        lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
                                                                               new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
    }

    if (parentException == null) {
        prepareException(lastException, authentication);
    }
    throw lastException;
}

6、认证成功:调用父类 successfulAuthentication 方法

/*
成功认证的默认行为
1、在SecurityContextHolder上设置成功的Authentication对象
2、向配置的RememberMeServices通报成功登录的情况
3、通过配置的ApplicationEventPublisher发射一个InteractiveAuthenticationSuccessEvent,将其他行为委托给AuthenticationSuccessHandler
子类可以重写此方法,以便在成功认证后继续使用FilterChain
形参:
request
response
chain
authResult - 从 attemptAuthentication 方法返回的对象
抛出:
IOException - ServletException
*/
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                        Authentication authResult) throws IOException, ServletException {
    SecurityContext context = SecurityContextHolder.createEmptyContext();
    //对认证成功的用户对象,封装到SecurityContext
    context.setAuthentication(authResult);
    //SecurityContext封装到SecurityContextHolder
    SecurityContextHolder.setContext(context);
    this.securityContextRepository.saveContext(context, request, response);
    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);
}

7、认证失败:调用父类 unsuccessfulAuthentication 方法

/*
对认证失败的默认行为
1、清除SecurityContextHolder
2、将异常存储在会话中(如果它存在或allowSesssionCreation被设置为true)
3、通知配置的RememberMeServices登录失败的情况
4、将其他行为委托给AuthenticationFailureHandler
*/
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);
}

 

权限访问流程

1、ExceptionTranslationFilter

(1)处理异常的过滤器

(2)doFilter 方法

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    try {
        //对前端异常,直接放行
        chain.doFilter(request, response);
    }
    //对后端异常,进行相应的处理
    catch (IOException ex) {
        throw ex;
    }
    catch (Exception ex) {
        // Try to extract a SpringSecurityException from the stacktrace
        Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
        RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
            .getFirstThrowableOfType(AuthenticationException.class, causeChain);
        if (securityException == null) {
            securityException = (AccessDeniedException) this.throwableAnalyzer
                .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
        }
        if (securityException == null) {
            rethrow(ex);
        }
        if (response.isCommitted()) {
            throw new ServletException("Unable to handle the Spring Security Exception "
                                       + "because the response is already committed.", ex);
        }
        handleSpringSecurityException(request, response, chain, securityException);
    }
}

2、FilterSecurityInterceptor

(1)过滤链中的最后一个过滤器,根据资源权限,判断当前请求是否有权限访问资源

(2)doFilter 方法

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    invoke(new FilterInvocation(request, response, chain));
}

(3)invoke 方法

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
    if (isApplied(filterInvocation) && this.observeOncePerRequest) {
        //过滤器已经应用于这个请求,并且用户希望观察每一次请求的处理,所以不需要重新进行安全检查
        filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
        return;
    }
    //第一次调用这个请求,所以要进行安全检查
    if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
        filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
    }
    //根据资源权限,判断当前请求是否有权限访问资源
    InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
    try {
        filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
    }
    finally {
        super.finallyInvocation(token);
    }
    super.afterInvocation(token, null);
}

 

请求间认证信息共享

1、成功认证方法

/*
成功认证的默认行为
1、在SecurityContextHolder上设置成功的Authentication对象
2、向配置的RememberMeServices通报成功登录的情况
3、通过配置的ApplicationEventPublisher发射一个InteractiveAuthenticationSuccessEvent,将其他行为委托给AuthenticationSuccessHandler
子类可以重写此方法,以便在成功认证后继续使用FilterChain
形参:
request
response
chain
authResult - 从 attemptAuthentication 方法返回的对象
抛出:
IOException - ServletException
*/
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                        Authentication authResult) throws IOException, ServletException {
    SecurityContext context = SecurityContextHolder.createEmptyContext();
    //SecurityContext封装认证成功的Authentication对象
    context.setAuthentication(authResult);
    //SecurityContext存入SecurityContextHolder
    SecurityContextHolder.setContext(context);
    this.securityContextRepository.saveContext(context, request, response);
    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);
}

2、SecurityContextHolder

(1)使用 ThreadLocal 进行操作,与当前线程做绑定

public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
private static void initializeStrategy() {
    if (MODE_PRE_INITIALIZED.equals(strategyName)) {
        Assert.state(strategy != null, "When using " + MODE_PRE_INITIALIZED
                     + ", setContextHolderStrategy must be called with the fully constructed strategy");
        return;
    }
    if (!StringUtils.hasText(strategyName)) {
        //设置默认策略
        strategyName = MODE_THREADLOCAL;
    }
    if (strategyName.equals(MODE_THREADLOCAL)) {
        strategy = new ThreadLocalSecurityContextHolderStrategy();
        return;
    }
    if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
        strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
        return;
    }
    if (strategyName.equals(MODE_GLOBAL)) {
        strategy = new GlobalSecurityContextHolderStrategy();
        return;
    }
    // Try to load a custom strategy
    try {
        Class<?> clazz = Class.forName(strategyName);
        Constructor<?> customStrategy = clazz.getConstructor();
        strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
    }
    catch (Exception ex) {
        ReflectionUtils.handleReflectionException(ex);
    }
}

(2)getContext 方法:若当前线程存在 Context,则返回;若不存在,则创建

3、SecurityContextPermissionFilter

(1)最开始的过滤器,已弃用,代替:SecurityContextHolderFilter

(2)doFilter 方法:将 Authentication 和 Session 进行绑定

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    //确保每个请求只应用一次过滤器
    if (request.getAttribute(FILTER_APPLIED) != null) {
        chain.doFilter(request, response);
        return;
    }
    request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
    if (this.forceEagerSessionCreation) {
        HttpSession session = request.getSession();
        if (this.logger.isDebugEnabled() && session.isNew()) {
            this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
        }
    }
    HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
    //判断Session是否存在认证信息,如果有认证信息,则取出SecurityContext对象,如果没有,则创建SecurityContext对象
    SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
    try {
        SecurityContextHolder.setContext(contextBeforeChainExecution);
        if (contextBeforeChainExecution.getAuthentication() == null) {
            logger.debug("Set SecurityContextHolder to empty SecurityContext");
        }
        else {
            if (this.logger.isDebugEnabled()) {
                this.logger
                    .debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
            }
        }
        //放行执行其他过滤器
        chain.doFilter(holder.getRequest(), holder.getResponse());
    }
    finally {
        //获取SecurityContextHolder的SecurityContext
        SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
        //在执行其他操作之前,必须先删除SecurityContextHolder中的SecurityContext
        SecurityContextHolder.clearContext();
        //先前获取的SecurityContext再放入Session
        this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
        request.removeAttribute(FILTER_APPLIED);
        this.logger.debug("Cleared SecurityContextHolder to complete request");
    }
}
posted @   半条咸鱼  阅读(177)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示