Spring Security 介绍

简介

Spring Security是为基于Spring的应用程序提供声明式安全保护的安全性框架,它能够在web请求级别和方法调用级别处理身份认证和授权。由于是基于Spring框架,它充分地利用了依赖注入和面向切面技术

身份认证和授权(Authentication and Authorization)

应用程序安全性差不多可以归结为两个独立的问题:
身份认证(Authentication)(你是谁)
授权(Authorization)(你可以做什么?)。
有时人们会说“访问控制”而不是“授权”,这可能会造成困惑,但是以这种方式思考可能会有所帮助,因为“授权”在其他地方又有其他含义。 Spring Security的体系结构旨在将身份认证与授权分开,并且具有许多策略和扩展点

源代码

版本: 5.3.x
下载地址 https://github.com/spring-projects/spring-security.git

Servlet Security

Servlet Filters

Spring Security 对于Servlet的支持是基于Servlet Filters。
下图描述的是典型的Servlet Filter 对于HTTP Request的处理
在这里插入图片描述
当客户端的请求到达, Servlet 会按照请求的URI,依序将请求交由FilterChain的Filter进行处理
在Spring MVC中的Servlet 是 DispatcherServlet

DelegatingFilterProxy

Servlet 容器支持自定义的Filter,但是不知道Bean的定义, 所有Spring 提供了一个 Filter DelegatingFilterProxy, 它的主要作用是把容器的生命周期和Spring的ApplicationContext
DelegatingFilterProxy 是基于Servlet 容器的机制注册的,然后把具体的工作代理给继承了Filter 的Spring Bean
在这里插入图片描述
DelegatingFilterProxy Pseudo Code

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // Lazily get Filter that was registered as a Spring Bean
    // For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
    Filter delegate = getFilterBean(someBeanName);
    // delegate work to the Spring Bean
    delegate.doFilter(request, response);
}
FilterChainProxy
  • Spring Security 对于Servlet的支持是在 FIlterChainProxy 中实现。
  • FIlterChainProxy 是一个特殊的Filter,封装在 DelegatingFilterProxy 里面
  • FIlterChainProxy 决定了调用 SecurityFilterChain 里面的哪一个 Security Filter
    在这里插入图片描述
SecurityFilterChain

SecurityFilterChain 里面注册是SecurityFilter,他们是通过FilterChainProxy注册,而不是DelegatingFilterProxy

Multiple SecurityFilterChain

通过自定义的FilterChainProxy,可以定义多个SecurityFilterChain
在这里插入图片描述

Security Filters

下面是默认的Spring Security Filters 及其顺序

  1. ChannelProcessingFilter
  2. ConcurrentSessionFilter
  3. WebAsyncManagerIntegrationFilter
  4. SecurityContextPersistenceFilter
  5. HeaderWriterFilter
  6. CorsFilter
  7. CsrfFilter
  8. LogoutFilter
  9. OAuth2AuthorizationRequestRedirectFilter
  10. Saml2WebSsoAuthenticationRequestFilter
  11. X509AuthenticationFilter
  12. AbstractPreAuthenticatedProcessingFilter
  13. CasAuthenticationFilter
  14. OAuth2LoginAuthenticationFilter
  15. Saml2WebSsoAuthenticationFilter
  16. UsernamePasswordAuthenticationFilter
  17. ConcurrentSessionFilter
  18. OpenIDAuthenticationFilter
  19. DefaultLoginPageGeneratingFilter
  20. DefaultLogoutPageGeneratingFilter
  21. DigestAuthenticationFilter
  22. BearerTokenAuthenticationFilter
  23. BasicAuthenticationFilter
  24. RequestCacheAwareFilter
  25. SecurityContextHolderAwareRequestFilter
  26. JaasApiIntegrationFilter
  27. RememberMeAuthenticationFilter
  28. AnonymousAuthenticationFilter
  29. OAuth2AuthorizationCodeGrantFilter
  30. SessionManagementFilter
  31. ExceptionTranslationFilter
  32. FilterSecurityInterceptor
  33. SwitchUserFilter
Security Exceptions

ExceptionTranslationFilter 可以处理两种类型的异常

  • AccessDeniedException
  • AuthenticationException
    在这里插入图片描述
    ExceptionTranslationFilter pseudo code
 try {
 		filterChain.doFilter(request, response); // step1
    } catch (AccessDeniedException | AuthenticationException e{
    	if (!authenticated || e instanceof 	AuthenticationException) {
        startAuthentication(); //step2
    } else {
        accessDenied(); //step3
    }
}

Authentication Architecture Components

SecurityContextHolder

为当前线程创建了一个 ThreadLocal 用于存放当前的 SecurityContext,既是当前 Spring Security 的上下文,包含对象,参数等任何一切与 Spring Security 相关的内容;
在这里插入图片描述

SecurityContext

通过SecurityContextHolder获得当前认证user 的信息

Authentication

org.springframework.security.core.Authentication 是 java.security.Principal 的继承接口,提供了 Spring Security 额外需要的用户认证相关的接口信息,比如 Credentials,Details information,is Authenticated 等等信息;

GrantedAuthority

权限

AuthenticationManager

AuthenticationManager 提供了认证方法的入口,并且它只实现了一个用于认证的方法,接收 Authentication 对象作为验证的参数;

Authentication authenticate(Authentication authentication)
      throws AuthenticationException;
ProviderManager

它是 AuthenticationManager 的一个实现类,提供了基本的认证逻辑和方法;不过为了便于灵活扩展,它包含了一个 List 对象,通过 AuthenticationProvider 接口来扩展出不同的认证提供者;

AuthenticationProvider

该对象主要用来为 ProviderManager 扩展出不同的认证提供者既 Providers
在这里插入图片描述

Spring Security 验证逻辑

在这里插入图片描述
通过上面类图,
下半部黄色部分,提供了一系列的类和功能用来支撑对Authentication对象的验证
上半部分,介绍了Authentication的组成,该部分主要是将用户输入的用户名和密码进行封装,封装成Authentication对象,并提供了AuthenticationManager进行验证;验证完成后返回一个认证成功的Authentication对象

Provider Manager

ProviderManager通过实现AuthenticationManager接口方法 authenticate() 实现验证逻辑,主要流程包含三个方面,

  • 遍历所有的 Providers,然后依次执行该 Provider 的验证方法

    • 如果某一个 Provider 验证成功,则跳出循环不再执行后续的验证;
    • 如果验证成功,会将返回的 result 既 Authentication 对象进一步封装为 Authentication Token;比如 UsernamePasswordAuthenticationToken、RememberMeAuthenticationToken 等;这些 Authentication Token 也都继承自 Authentication 对象;
  • 如果 #1 没有任何一个 Provider 验证成功,则试图使用其 parent Authentication Manager 进行验证;

  • 是否需要擦除密码等敏感信息;

DaoAuthenticationProvider

DAO,英文全名 Data Access Object,数据访问对象
DaoAuthenticationProvider 是接口AuthticationProvider的一个实现类
DaoAuthenticationProvider 包含一个属性UserDetailService
DaoAuthenticationProvider继承自AbstractUserDetailsAuthenticationProvider主要做了三件事情,

  1. 对用户身份信息进行加密操作;主要是传入一个PasswordEncoder对象
private PasswordEncoder passwordEncoder;
  1. 实现了 AbstractUserDetailsAuthenticationProvider 预留的两个扩展点,
  • 获取用户信息的扩展点
private UserDetailsService userDetailsService;
...
protected final UserDetails retrieveUser(String username,
    UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException {
  UserDetails loadedUser;
  try {
    loadedUser = this.getUserDetailsService().loadUserByUsername(username);
  }
  ....
  if (loadedUser == null) {
    throw new InternalAuthenticationServiceException(
        "UserDetailsService returned null, which is an interface contract violation");
  }
  return loadedUser;
}

可见,主要是通过注入 UserDetailsService 接口对象,并调用其接口方法 loadUserByUsername(String username) 获取得到相关的用户信息;正如类图中我们所看到的那样,UserDetailsService接口非常重要,接入了 JdbcDaoImpl,InMemoryUserDetailsManager 等等用户来源接口实现类;

  • 实现相关的 additionalAuthenticationChecks 的额外验证方法;
    该抽象方法是 AbstractUserDetailsAuthenticationProvider 提供给子类的可扩展的核心入口方法,我们看看 DaoAuthenticationProvider 是怎么做的
protected void additionalAuthenticationChecks(UserDetails userDetails,
    UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException {
  Object salt = null;

  if (this.saltSource != null) {
    salt = this.saltSource.getSalt(userDetails);
  }
  if (authentication.getCredentials() == null) {
    logger.debug("Authentication failed: no credentials provided");
    throw new BadCredentialsException(messages.getMessage(
    "AbstractUserDetailsAuthenticationProvider.badCredentials",
        "Bad credentials"));
  }
  String presentedPassword = authentication.getCredentials().toString();
  if (!passwordEncoder.isPasswordValid(userDetails.getPassword(),
      presentedPassword, salt)) {
    logger.debug("Authentication failed: password does not match stored value");

    throw new BadCredentialsException(messages.getMessage(
        "AbstractUserDetailsAuthenticationProvider.badCredentials",
        "Bad credentials"));
  }
}
  1. AbstractUserDetailsAuthenticationProvider 为DaoAuthenticationProvider提供了基本的认证方法
public Authentication authenticate(Authentication authentication)
    throws AuthenticationException {
  
  // Determine username
  String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
      : authentication.getName();
  
  ...
  // 1. 获取用户
  user = retrieveUser(username,
          (UsernamePasswordAuthenticationToken) authentication);
  // 2.1 pre-authenticate  
  preAuthenticationChecks.check(user);
  // 2.2 additional-authenticate
  additionalAuthenticationChecks(user,
        (UsernamePasswordAuthenticationToken) authentication);
  ...
  // 2.3 post-authenticate      
  postAuthenticationChecks.check(user);

  ...

  Object principalToReturn = user;

  if (forcePrincipalAsString) {
    principalToReturn = user.getUsername();
  }

  // 3. 封装通过验证的用户信息
  return createSuccessAuthentication(principalToReturn, authentication, user);
}

通过省略后的代码,主要验证逻辑有以下几步

  1. 获取用户,通过用户名去获得用户的信息并作为UserDetails对象进行返回;
AbstractUserDetailsAuthenticationProvider定义了一个抽象的方法
protected abstract UserDetails retrieveUser(String username,
     UsernamePasswordAuthenticationToken authentication)
     throws AuthenticationException;
  1. 验证
  • preAuthenticationChecks
    提供了对用户基本信息的一些默认的前置验证逻辑,包括,用户账户是否被锁定,是否是 enabled 的状态以及用户账户是否过期等逻辑的验证;
  • additionalAuthenticationChecks
    这是一个扩展点,该方法是一个抽象方法,必须由子类进行实现,
  • postAuthenticationChecks
    提供了对用户基本信息的一些默认的后置验证逻辑,默认实现很简单,就是对用户的 Credential 既密码进行判断,判断其是否过期。
  1. 最后,将已通过验证的用户信息封装成 UsernamePasswordAuthenticationToken 对象并返回;该对象封装了用户的身份信息,以及相应的权限信息,相关源码如下,
protected Authentication createSuccessAuthentication(Object principal,
    Authentication authentication, UserDetails user) {
  // Ensure we return the original credentials the user supplied,
  // so subsequent attempts are successful even with encoded passwords.
  // Also ensure we return the original getDetails(), so that future
  // authentication events after cache expiry contain the details
  UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
      principal, authentication.getCredentials(),
      authoritiesMapper.mapAuthorities(user.getAuthorities()));
  result.setDetails(authentication.getDetails());

  return result;
}
总结:

从这里我们就可以清晰的看到,UserDetailsService接口作为桥梁,是DaoAuthenticationProvier与特定用户信息来源进行解耦的地方,UserDetailsService由UserDetails和UserDetailsManager所构成;UserDetails和UserDetailsManager各司其责,一个是对基本用户信息进行封装,一个是对基本用户信息进行管理;

特别注意,UserDetailsService、UserDetails以及UserDetailsManager都是可被用户自定义的扩展点,我们可以继承这些接口提供自己的读取用户来源和管理用户的方法,比如我们可以自己实现一个 与特定 ORM 框架,比如 Mybatis 或者 Hibernate,相关的UserDetailsService和UserDetailsManager;

时序图

在这里插入图片描述

Authorization

在方法或者Web 请求之前的, Spring Security提供了拦截器通过AccessDecisionManager来检查用户权限
在这里插入图片描述

决策管理器

Spring Security已经内置了几个基于投票的AccessDecisionManager,

  • AffirmativeBased
  • ConsensusBased
  • UnanimousBased
    当然如果需要你也可以实现自己的AccessDecisionManager

Spring Security内置了三个基于投票的AccessDecisionManager实现类,它们分别是AffirmativeBased、ConsensusBased和UnanimousBased。

AffirmativeBased的逻辑是这样的:

(1)只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;

(2)如果全部弃权也表示通过;

(3)如果没有一个人投赞成票,但是有人投反对票,则将抛出AccessDeniedException。

ConsensusBased的逻辑是这样的:

(1)如果赞成票多于反对票则表示通过。

(2)反过来,如果反对票多于赞成票则将抛出AccessDeniedException。

(3)如果赞成票与反对票相同且不等于0,并且属性allowIfEqualGrantedDeniedDecisions的值为true,则表示通过,否则将抛出异常AccessDeniedException。参数allowIfEqualGrantedDeniedDecisions的值默认为true。

(4)如果所有的AccessDecisionVoter都弃权了,则将视参数allowIfAllAbstainDecisions的值而定,如果该值为true则表示通过,否则将抛出异常AccessDeniedException。参数allowIfAllAbstainDecisions的值默认为false。

UnanimousBased的逻辑与另外两种实现有点不一样,另外两种会一次性把受保护对象的配置属性全部传递给AccessDecisionVoter进行投票,而UnanimousBased会一次只传递一个ConfigAttribute给AccessDecisionVoter进行投票。这也就意味着如果我们的AccessDecisionVoter的逻辑是只要传递进来的ConfigAttribute中有一个能够匹配则投赞成票,但是放到UnanimousBased中其投票结果就不一定是赞成了。UnanimousBased的逻辑具体来说是这样的:

  1. 在Authentication 之后,AuthenticationManager 会将用户的GrantedAuthority 插入到Authentication
    GrantedAuthority 是一个接口,只有一个方法
投票者

RoleVoter是Spring Security内置的一个AccessDecisionVoter,其会将ConfigAttribute简单的看作是一个角色名称,在投票的时如果拥有该角色即投赞成票。如果ConfigAttribute是以“ROLE_”开头的,则将使用RoleVoter进行投票。当用户拥有的权限中有一个或多个能匹配受保护对象配置的以“ROLE_”开头的ConfigAttribute时其将投赞成票;如果用户拥有的权限中没有一个能匹配受保护对象配置的以“ROLE_”开头的ConfigAttribute,则RoleVoter将投反对票;如果受保护对象配置的ConfigAttribute中没有以“ROLE_”开头的,则RoleVoter将弃权。

AuthenticatedVoter也是Spring Security内置的一个AccessDecisionVoter实现。其主要用来区分匿名用户、通过Remember-Me认证的用户和完全认证的用户。完全认证的用户是指由系统提供的登录入口进行成功登录认证的用户。

基于Web 的Authorize 流程

在这里插入图片描述

  1. 首先FilterSecurityInterceptor 从SecurityContextHolder中获得一个Authentication对象

  2. FilterSecurityInterceptor 创建从HttpServletRequest, HttpServletResponse, and FilterChain 中创建一个 FilterInvocation, 然后传给FilterSecurityInterceptor.

  3. 把FilterInvocation 传给SecurityMetadataSource 以获得ConfigAttributes.

  4. 把Authentication, FilterInvocation, 和 ConfigAttributes 传入AccessDecisionManager.

  5. 如果authorization is denied, 抛出 AccessDeniedException, 然后ExceptionTranslationFilter 处理这个AccessDeniedException.

  6. 如果访问授权,FilterSecurityInterceptor 继续FilterChain的处理

Spring Security是一个基于Spring框架的安全性解决方案,用于保护Java应用程序的安全性。它提供了一套全面的安全性功能,包括身份验证、授权、密码管理和会话管理等。 Spring Security的主要特点包括: 1. 身份验证(Authentication):Spring Security支持多种身份验证方式,包括基于表单、基于HTTP基本认证、基于LDAP等。它还支持自定义身份验证方式。 2. 授权(Authorization):Spring Security提供了细粒度的授权机制,可以通过注解或配置文件来定义访问控制规则。它支持基于角色(Role)和基于权限(Permission)的授权方式。 3. 密码管理(Password Management):Spring Security提供了安全的密码存储和验证机制,可以对密码进行加密和解密操作,以保护用户密码的安全性。 4. 会话管理(Session Management):Spring Security支持会话管理功能,可以管理用户的会话状态,包括会话超时、并发登录控制等。 5. CSRF防护(CSRF Protection):Spring Security提供了跨站请求伪造(CSRF)防护功能,可以防止恶意攻击者利用用户的身份进行非法操作。 6. 记住我(Remember Me):Spring Security支持“记住我”功能,可以在用户下次访问时自动登录,提高用户体验。 7. 安全事件监听(Security Event Listeners):Spring Security提供了安全事件监听机制,可以监听用户登录、注销等安全事件,并进行相应的处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值