Spring Security 源码浅析

1.核心组件

1.1.SecurityContextHolder

  SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保存在SecurityContextHolder中。SecurityContextHolder默认使用ThreadLocal 策略来存储认证信息。看到ThreadLocal 也就意味着,这是一种与线程绑定的策略。Spring Security在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息。

1.2.Authentication

public interface Authentication extends Principal, Serializable {
    Collection getAuthorities();
    bject getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
  • Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于java.security包中的。可以见得,Authentication在spring security中是最高级别的身份/认证的抽象。
  • 由这个顶级接口,我们可以得到用户拥有的权限信息列表,密码,用户细节信息,用户身份信息,认证信息。
    • getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。
    • getCredentials(),密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全
    • getDetails(),细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
    • getPrincipal(),最重要的身份信息,大部分情况下返回的是UserDetails接口的实现类,也是框架中的常用接口之一。UserDetails接口将会在下面的小节重点介绍。

1.3.AuthenticationManager

  AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中,我们可能会允许用户使用用户名+密码登录,同时允许用户使用邮箱+密码,手机号码+密码登录,甚至,可能允许用户使用指纹登录(还有这样的操作?没想到吧),所以说AuthenticationManager一般不直接认证,AuthenticationManager接口的常用实现类ProviderManager 内部会维护一个List列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate)。

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
        InitializingBean {
    // 维护一个AuthenticationProvider列表
    private List providers = Collections.emptyList();

    public Authentication authenticate(Authentication authentication)
          throws AuthenticationException {
       Classextends Authentication> toTest = authentication.getClass();
       AuthenticationException lastException = null;
       Authentication result = null;
       // 依次认证
       for (AuthenticationProvider provider : getProviders()) {
          if (!provider.supports(toTest)) {
             continue;
          }
          try {
             result = provider.authenticate(authentication);
             if (result != null) {
                copyDetails(authentication, result);
                break;
             }
          }
          ...
          catch (AuthenticationException e) {
             lastException = e;
          }
       }
       // 如果有Authentication信息,则直接返回
       if (result != null) {
            if (eraseCredentialsAfterAuthentication
                    && (result instanceof CredentialsContainer)) {
                 //移除密码
                ((CredentialsContainer) result).eraseCredentials();
            }
             //发布登录成功事件
            eventPublisher.publishAuthenticationSuccess(result);
            return result;
       }
       ...
       //执行到此,说明没有认证成功,包装异常信息
       if (lastException == null) {
          lastException = new ProviderNotFoundException(messages.getMessage(
                "ProviderManager.providerNotFound",
                new Object[] { toTest.getName() },
                "No AuthenticationProvider found for {0}"));
       }
       prepareException(lastException, authentication);
       throw lastException;
    }
}

1.4.AuthenticationProvider

  AuthenticationProvider 就是实际的认证接口。比如DaoAuthenticationProvider,用户提交的用户名和密码,被封装成了UsernamePasswordAuthenticationToken,而根据用户名加载用户的任务则是交给了UserDetailsService ,DaoAuthenticationProvider中,对应的方法便是retrieveUser,虽然有两个参数,但是retrieveUser只有第一个参数起主要作用,返回一个UserDetails。还需要完成UsernamePasswordAuthenticationToken和UserDetails密码的比对,这便是交给additionalAuthenticationChecks方法完成的,如果这个void方法没有抛异常,则认为比对成功。比对密码的过程,用到了PasswordEncoder和SaltSource。

1.5.UserDetails

public interface UserDetails extends Serializable {
    Collection getAuthorities();
    String getPassword();
    String getUsername();
    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
}

  它和Authentication接口很类似,比如它们都拥有username,authorities,区分他们也是本文的重点内容之一。Authentication的getCredentials()与UserDetails中的getPassword()需要被区分对待,前者是用户提交的密码凭证,后者是用户正确的密码,认证器其实就是对这两者的比对。Authentication中的getAuthorities()实际是由UserDetails的getAuthorities()传递而形成的。还记得Authentication接口中的getUserDetails()方法吗?其中的UserDetails用户详细信息便是经过了AuthenticationProvider之后被填充的。

1.6.UserDetailsService

  UserDetailsService只负责从特定的地方(通常是数据库)加载用户信息,仅此而已

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

1.7.AccessDecisionManager

  AccessDecisionManager会调用AccessDecisionVoter进行投票,并根据结果进行决策。

  • AffirmativeBased 一票通过,只要有一个投票器通过就允许访问
  • ConsensusBased 有一半以上投票器通过才允许访问资源
  • UnanimousBased 所有投票器都通过才允许访问

1.8.AccessDecisionVoter

public interface AccessDecisionVoter {
    boolean supports(ConfigAttribute attribute);
    boolean supports(Class clazz);
    int vote(Authentication authentication, S object,
            Collection attributes);
}

2.请求过程

Created with Raphaël 2.1.2 客户端 客户端 DelegatingFilterProxy DelegatingFilterProxy FilterChainProxy FilterChainProxy SecurityFilterChain SecurityFilterChain RequestMatcher RequestMatcher VirtualFilterChain VirtualFilterChain Filter Filter originalChain originalChain doFilter invokeDelegate doFilter doFilterInternal getFilters matches matches doFilter doFilter doFilter

主要的过滤器有:

  • LogoutFilter
  • BasicAuthenticationFilter Basic认证
  • UsernamePasswordAuthenticationFilter 用户认证,会调用AuthenticationManager进行认证
  • SmsCodeAuthenticationFilter 短信认证
  • OAuth2AuthenticationProcessingFilter OAuth2认证
  • AnonymousAuthenticationFilter
  • SessionManagementFilter
  • FilterSecurityInterceptor 会调用AccessDecisionManager进行授权,授权通过会调用SecurityContextHolder.setContext(token.getSecurityContext())

3.boot方式的初始化过程

3.1时序图

Created with Raphaël 2.1.2 SecurityFilterAutoConfiguration SecurityFilterAutoConfiguration DelegatingFilterProxyRegistrationBean DelegatingFilterProxyRegistrationBean WebSecurityConfiguration WebSecurityConfiguration WebSecurity WebSecurity SecurityConfigurer SecurityConfigurer securityFilterChainRegistration onStartup getFilter setFilterChainProxySecurityConfigurer apply springSecurityFilterChain build init init configure configure performBuild

3.2类图

Spring Security 源码浅析_第1张图片
AbstractConfiguredSecurityBuilder的doBuild方法:

protected final O doBuild() throws Exception {
        synchronized (configurers) {
            buildState = BuildState.INITIALIZING;

            beforeInit();
            init();

            buildState = BuildState.CONFIGURING;

            beforeConfigure();
            configure();

            buildState = BuildState.BUILDING;

            O result = performBuild();

            buildState = BuildState.BUILT;

            return result;
        }
    }

你可能感兴趣的:(spring)