• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Spring Security整体架构

武飞扬头像
目目沐沐
帮助1

Spring Security整体架构

1.认证

Spring Security的架构设计中,认证AuthenticationAuthorization是分开的,无论使用什么样的认证方式,都不会影响授权,这时两个独立的存在。这种独立的存在带来的好处之一,就是Spring Security可以非常方便地整合一些外部的认证方案。

Spring Security中,用户的认证信息主要由Authentication的实现类来保存,其中Authentication接口定义如下:

public interface Authentication extends Principal, Serializable {
    
    Collection<? extends GrantedAuthority> getAuthorities();

    Object getCredentials();

    Object getDetails();

    Object getPrincipal();

    boolean isAuthenticated();

    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
    
}

Authentication接口中定义的方法如下:

  • getAuthorities():获取用户的权限。
  • getCredentials():获取用户凭证,也就是密码。
  • getDetails():获取用户携带的详细信息。
  • getPrincipal():获取当前用户,例如一个用户名或者一个用户对象。
  • isAuthenticated():当前用户是否认证成功。

当用户使用用户名/密码登录或使用RememberMe登录时,都会对应一个不同的Authentication实例。

Spring Security中的认证工作主要由AuthenticationManager接口负责,AuthenticationManager接口定义如下:

public interface AuthenticationManager {
    
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
    
}

AuthenticationManager接口中只有一个authenticate方法可以用来做认证,该方法有三个不同的返回值:

  • 返回Authentication,表示认证成功。
  • 抛出AuthenticationException异常,表示用户输入了无效的凭证。
  • 返回null,表示不能断定。

AuthenticationManager接口最主要的实现类是ProviderManagerProviderManager类管理了众多的AuthenticationProvider实例。AuthenticationProvider类有点类似于AuthenticationManager,但是它多了一个supports方法用来判断是否支持给定的Authentication类型。AuthenticationProvider接口定义如下:

public interface AuthenticationProvider {
    
    Authentication authenticate(Authentication authentication) throws AuthenticationException;

    boolean supports(Class<?> clazz);
    
}

由于Authentication接口拥有众多不同的实现类,这些不同的实现类又由不同的AuthenticationProvider来处理,所以AuthenticationProvider会有一个supports方法,用来判断当前的AuthenticationProvider是否支持对应的Authentication

在一次完整的认证流程中,可能会同时存在多个AuthenticationProvider,例如项目同时支持form表单登录和短信验证码登录。多个AuthenticationProvider统一由ProviderManager来管理。同时,ProviderManager具有一个可选的parent,如果所有的AuthenticationProvider都认证失败,那就会调用parent进行认证。parent相当于一个具备认证方式,但当各个AuthenticationProvider都无法处理认证问题的时候,就由parent出面收拾残局。


2.授权

当完成认证后,接下来就是授权了。在Spring Security的授权体系中,有两个关键的接口:

  • AccessDecisionManager
  • AccessDecisionVoter

AccessDecisionVoter是一个投票器,投票器会检查用户是否具备应有的校色,进而投出赞成、反对或者弃权票。AccessDecisionManager则是一个决策器,来决定此次访问是否被允许。AccessDecisionManagerAccessDecisionVoter都有众多的实现类。在AccessDecisionManager中会挨个遍历AccessDecisionVoter,进而决定是否允许用户访问,因此AccessDecisionVoterAccessDecisionManager两者的关系类似于AuthenticationProviderProviderManager的关系。

Spring Security中,用户请求一个资源,这个资源通常是一个网络接口或者一个Java方法所需要的角色会被封装成一个ConfigAttribute对象,在ConfigAttribute中只有一个getAttribute方法,该方法返回一个String字符串,就是角色的名称。一般来说,一个角色名称都带有一个ROLE_前缀,投票器AccessDecisionVoter所作的事情,其实就是比较用户所具备的角色和请求某个资源所需的ConfigAttribute之间的关系。ConfigAttribute接口定义如下:

public interface ConfigAttribute extends Serializable {
    
    String getAttribute();
    
}

3.Web安全

Spring Security中,认证、授权等功能都是基于过滤器来完成的。下列为Spring Security中常见的过滤器。

过滤器 过滤器作用 是否默认加载
ChannelProcessingFilter 过滤请求协议,如HTTPHTTPS NO
WebAsyncManagerIntegrationFilter WebAsyncManagerSpring Security上下文进行集成 YES
SecurityContextPersistenceFilter 在处理请求之前,将安全信息加载到SecurityContextHolder中以便后续使用。请求结束后再擦除SecurityContextHolder中的信息 YES
HeaderWriterFilter 头信息加入到响应中 YES
CorsFilter 处理跨域请求问题 NO
CsrfFilter 处理CSRF攻击 YES
LogoutFilter 处理注销登录 YES
OAuth2AuthorizationRequestRedirectFilter 处理OAuth2认证重定向 NO
Saml2WebSsoAuthenticationRequestFilter 处理SAML认证 NO
X509AuthenticationFilter 处理X509认证 NO
AbstractPreAuthenticatedProcessingFilter 处理预认证问题 NO
CasAuthenticationFilter 处理CAS单点登录 NO
OAuth2LoginAuthenticationFilter 处理OAuth2认证 NO
Saml2WebSsoAuthenticationFilter 处理SAML认证 NO
UsernamePasswordAuthenticationFilter 处理表单登录 YES
OpenIDAuthenticationFilter 处理OpenID认证 NO
DefaultLoginPageGeneratingFilter 配置默认登录页面 YES
DefaultLogoutPageGeneratingFilter 配置默认注销页面 YES
ConcurrentSessionFilter 处理session有效期 NO
DigestAuthenticationFilter 处理HTTP摘要认证 NO
BearerTokenAuthenticationFilter 处理OAuth2认证时的Access Token NO
BasicAuthenticationFilter 处理HttpBasic登录 YES
RequestCacheAwareFilter 处理请求缓存 YES
SecurityContextHolderAwareRequestFilter 包装原始请求 YES
JaasApiIntegrationFilter 处理JAAS认证 NO
RememberMeAuthenticationFilter 处理RememberMe登录 NO
AnonymousAuthenticationFilter 配置匿名认证 YES
OAuth2AuthorizationCodeGrantFilter 处理OAuth2认证中的授权码 NO
SessionManagementFilter 处理Session并发问题 YES
ExceptionTranslationFilter 处理异常/授权中的情况 YES
FilterSecurityInterceptor 处理授权 YES
SwitchUserFilter 处理账号切换 NO

以上过滤器是否默认加载是指引入Spring Security依赖之后,开发者不做任何配置时,会自动加载的过滤器。

开发者所见到的Spring Security提供的功能,都是通过这些过滤器实现的,这些过滤器按照既定的优先级排列,最终形成一个过滤器链。开发者也可以自定义过滤器,并通过@Order注解去调整自定义过滤器在过滤器链中的位置。

需要注意的是,默认过滤器并不是直接放在Web项目的原生过滤器中,而是通过一个FilterChainProxy来统一管理。Spring Security中的过滤链通过FilterChainProxy嵌入到Web项目的原生过滤器链中。

学新通

Spring Security中,这样的过滤器链不仅仅只有一个,可能会有多个。当存在多个过滤器链时,多个过滤器链之间要指定优先级,当请求到达后,会从FilterChainProxy进行分发,先和哪个过滤器链匹配上,就用哪个过滤器链进行处理。当系统中存在多个不同认证体系时,使用多个过滤器链就非常有效。

学新通

FilterChainProxy作为一个顶层管理者,将统一管理Security FilterFilterChainProxy本身将通过Spring框架提供的DelegatingFilterProxy整合到原生过滤器链中,所以上图还可以做进一步优化:

学新通


4.登录数据保存

如果不使用Spring Security这一类安全管理框架,大部分的开发者可能会将登录用户数据保存在session中。事实上,Spring Security也是这么做的。但是,为了使用方便,Spring Security在此基础上还做了一些改进,其中最重要的一个变化就是线程绑定。

当用户登陆成功后,Spring Security会将登陆成功的用户信息保存到SecurityContextHolder中。SecurityContextHolder中的数据保存默认是通过ThreadLocal来实现的。使用ThreadLocal创建的变量只能被当前线程访问,不能被其它线程访问和修改,也就是将用户数据与请求线程绑定在一起。当请求处理完毕后,Spring Security会将SecurityContextHolder中的数据拿出来保存到Session中,同时将SecurityContextHolder中的数据清空。每当有请求到来时,Spring Security会先从Session中取出用户登录数据,保存到SecurityContextHolder中,方便在该请求的后续处理过程中使用。同时在请求结束时将SecurityContextHolder中的数据拿出来保存到Session中,然后将SecurityContextHolder中的用户数据清空。

这一策略非常方便用户在Controller或者Service层获取当前登录用户数据,但是带来的另外一个问题就是,在字线程中想要获取用户登录数据就比较麻烦。Spring Security对此也提供了相应的解决方案,如果开发者使用@Async注解来开启异步任务的话,那么只需要添加如下配置,使用Spring Security提供的异步任务代理,就可以在异步任务中从SecurityContextHolder中获取当前登录用户的信息:

@Configuration
public class ApplicationAsyncConfiguration extends AsyncConfigurerSupport {

    @Override
    public Executor getAsyncExecutor() {
        return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5));
    }
    
}

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgffbff
系列文章
更多 icon
同类精品
更多 icon
继续加载