1. Spring Security 简介
在 Spring 生态系统中,为他的项目增加安全性,你可以借助 Spring Security 库来做到这一点。
那什么是 Spring Security?
从本质上讲,Spring Security 实际上只是一堆 servlet 过滤器,可帮助您向Web应用程序添加身份验证和授权。还与 Spring Web MVC(或 Spring Boot)之类的框架以及 OAuth2 或SAML 之类的标准很好地集成。并且它会自动生成登录/注销页面,并防御 CSRF 等常见漏洞。
学习 Spring Security 前需要了解三个重要概念:
-
Authentication(认证)
-
Authorization(授权)
-
Servlet Filters(一系列 Servlet 过滤器)
1)认证
首先,如果您正在运行典型的(网络)应用程序,则需要你的用户进行身份验证。这意味着您的应用需求,以验证用户是否是谁,他声称自己是,通常与一个用户名和密码检查来完成。
Authentication 主要构件:
-
SecurityContextHolder:Spring Security在此处存储经过身份验证的人员的详细信息。
-
SecurityContext:从 SecurityContextHolder 中获取
,
并包含 Authentication 当前经过身份验证的用户的。 -
Authentication:可以是 AuthenticationManager 的输入,以提供用户提供的用于身份验证的凭据或来自 SecurityContext 的当前用户。
-
GrantedAuthority:在身份验证的基础上授予委托人的权限(即角色,作用域等)
-
AuthenticationManager:定义 Spring Security 的过滤器如何执行身份验证的 API。
-
ProviderManager:最常见的实现 AuthenticationManager。
-
AuthenticationProvider:由 ProviderManager 用于执行特定类型的身份验证。
-
使用 AuthenticationEntryPoint 的请求凭据:用于从客户端请求凭据(即,重定向到登录页面,发送 WWW-Authenticate 响应等)
-
AbstractAuthenticationProcessingFilter: 用于认证的基础 Filter 。这也为高级别的身份验证流程以及各个部分如何协同工作提供了一个好主意。
认证机制:
-
用户名和密码:如何使用用户名/密码进行身份验证
-
OAuth 2.0登录:使用 OpenID Connect 和非标准 OAuth 2.0 登录(即 GitHub)登录的 OAuth 2.0
-
SAML 2.0登录:SAML 2.0 登录
-
中央身份验证服务器(CAS):中央身份验证服务器(CAS)支持
-
Remember Me:记住用户过期的会话的功能
-
JAAS认证:使用 JAAS 进行认证
-
OpenID:OpenID 身份验证(请勿与 OpenID Connect 混淆)
-
预先身份验证方案(Pre-Authentication Scenarios):使用诸如 SiteMinder 或 Java EE 安全性之类的外部机制进行身份验证,但仍使用 Spring Security 进行授权和防范常见漏洞利用。
-
X509验证:X509 验证
2. Authentication 主要构件介绍
① SecurityContextHolder
Spring Security 身份验证模型的核心是 SecurityContextHolder。它包含 SecurityContext。
SecurityContextHolder 用于存储通过身份验证的人员的详细信息。
如下代码所示,指示用户已通过身份验证的最简单方法是直接设置 SecurityContextHolder :
1 SecurityContext context = SecurityContextHolder.createEmptyContext(); 2 Authentication authentication = 3 new TestingAuthenticationToken("username", "password", "ROLE_USER"); 4 context.setAuthentication(authentication); 5 6 SecurityContextHolder.setContext(context);
如果你希望获取有关已认证主体的信息,可以通过以下方式访问 SecurityContextHolder 来获得。
1 SecurityContext context = SecurityContextHolder.getContext(); 2 Authentication authentication = context.getAuthentication(); 3 String username = authentication.getName(); 4 Object principal = authentication.getPrincipal(); 5 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
② SecurityContext 安全上下文
在 SecurityContextHolder 中所得 SecurityContext 中。该 SecurityContext 包含认证对象。
③ Authentication
它们 Authentication 在 Spring Security 中有两个主要目的:
-
AuthenticationManager 用于提供用户已提供身份验证的凭据的输入。在这种情况下使用时, isAuthenticated() 返回 false。
-
代表当前经过身份验证的用户。当前 Authentication 可以从 SecurityContext 获得。
该 Authentication 包含:
-
principal:识别用户。使用用户名/密码进行身份验证时,这通常是的实例 UserDetails 。
-
credentials:通常是密码。在许多情况下,将在验证用户身份后清除此内容,以确保它不会泄漏。
-
authorities:在 GrantedAuthority 是授予用户的高级别权限。比如说角色或范围。
④ GrantedAuthority 授予的权限
GrantedAuthority 是用户将被授予的高级别权限。比如说角色或范围。
GrantedAuthority 可以从该 Authentication.getAuthorities() 方法获得。此方法提供了一个 GrantedAuthority 集合对象。GrantedAuthority 是授予某个主体的权限。此类权限通常是“角色”,例如 ROLE_ADMINISTRATOR 或 ROLE_HR_SUPERVISOR 。稍后将这些角色配置为 Web授权,方法授权和域对象授权。Spring Security 的其他部分能够解释这些权限,并希望它们存在。使用基于用户名/密码的身份验证时GrantedAuthority
,通常会由加载 UserDetailsService 。
通常,GrantedAuthority 对象是应用程序范围的权限。它们不特定于给定的域对象。因此,您不可能 GrantedAuthority 代表 Employee 54号对象的权限,因为如果有成千上万个这样的权限,这将很快用完内存(或者至少导致应用程序花费很长时间来完成验证用户身份)。当然,Spring Security是专门为满足这一通用要求而设计的,但你可以为此目的使用项目的域对象安全性功能。
⑤ AuthenticationManager 认证管理器
AuthenticationManager 定义 Spring Security 的过滤器是如何执行身份验证的 API 。然后再由调用 AuthenticationManager 的控制器(即 Spring Security 的 Filters)在SecurityContextHolder 上设置返回的Authentication。如果您不与Spring Security的过滤器集成,则可以直接设置 SecurityContextHolder,并且不需要使用 AuthenticationManager。
尽管的实现 AuthenticationManager 可以是任何对象,但最常见的实现是 ProviderManager。
⑥ ProviderManager
ProviderManager 是 AuthenticationManager 的最常见实现。ProviderManager 委托给 AuthenticationProvider 列表。 每个 AuthenticationProvider 都有机会指示认证应该成功,失败,或者表明它不能做出决定并允许下游 AuthenticationProvider 进行决定。 如果没有配置的 AuthenticationProviders 可以进行身份验证,则身份验证将失败,并显示ProviderNotFoundException,这是一个特殊的 AuthenticationException,它指示未配置 ProviderManager 支持传递给它的身份验证类型。
实际上,每个 AuthenticationProvider 都知道如何执行特定类型的身份验证。 例如,一个 AuthenticationProvider 可能能够验证用户名/密码,而另一个可能能够验证 SAML 断言。 这允许每个 AuthenticationProvider 进行非常特定类型的身份验证,同时支持多种类型的身份验证,并且仅公开一个单例 AuthenticationManager bean。
ProviderManager 还允许配置可选的父类 AuthenticationManager,如果没有 AuthenticationProvider 可以执行身份验证,请咨询该父对象。 父级可以是任何类型的AuthenticationManager,但通常是 ProviderManager的实例。
实际上,多个 ProviderManager 实例可能共享同一个父类 AuthenticationManager。 在存在多个具有相同身份验证(共享的父类 AuthenticationManager)但又具有不同身份验证机制(不同 ProviderManager 实例)的多个 SecurityFilterChain 实例的情况下,这种情况有些常见。
默认情况下,ProviderManager 会尝试清除身份验证对象中所有敏感的凭据信息,这些信息将由成功的身份验证请求返回。 这样可以防止密码之类的信息在 HttpSession 中的保留时间超过所需的时间。
例如,在使用用户对象的缓存来提高无状态应用程序的性能时,这可能会导致问题。 如果身份验证包含对缓存中某个对象(例如 UserDetails 实例)的引用,并且已删除其凭据,则将无法再对缓存的值进行身份验证。 如果使用缓存,则需要考虑到这一点。 一个明显的解决方案是首先在缓存实现中或在创建返回的 Authentication 对象的 AuthenticationProvider 中创建对象的副本。 或者,你可以在 ProviderManager 上禁用 deleteCredentialsAfterAuthentication 属性。
⑦ AuthenticationProvider
可以将多个 AuthenticationProvider 注入 ProviderManager。 每个 AuthenticationProvider 执行特定类型的身份验证。 例如,DaoAuthenticationProvider 支持基于用户名/密码的身份验证,而 JwtAuthenticationProvider 支持对JWT令牌的身份验证。AuthenticationEntryPoint
⑧ 使用 AuthenticationEntryPoint 的请求凭据
AuthenticationEntryPoint 用于发送请求凭据响应,以回应客户端 HTTP 认证。有时,客户端会主动包含凭据(例如用户名/密码)以请求资源。 在这些情况下,Spring Security 不需要提供 HTTP 响应来从客户端请求凭据,因为它们已经包含在内。
在其他情况下,客户端将对未经授权访问的资源发出未经身份验证的请求。 在这种情况下,AuthenticationEntryPoint 的实现用于从客户端请求凭据。 AuthenticationEntryPoint 实现可能会执行重定向到登录页面,使用 WWW-Authenticate 标头进行响应等。
⑨ AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter 用作验证用户凭据的基本过滤器。 在对凭证进行身份验证之前,Spring Security 通常使用 AuthenticationEntryPoint 请求凭证。
接下来,AbstractAuthenticationProcessingFilter 可以对提交给它的任何身份验证请求进行身份验证。
1. 当用户提交其凭据时,AbstractAuthenticationProcessingFilter 从要验证的 HttpServletRequest 创建一个 Authentication。创建的身份验证类型取决于 AbstractAuthenticationProcessingFilter 的子类。例如,UsernamePasswordAuthenticationFilter 根据在 HttpServletRequest 中提交的用户名和密码来创建UsernamePasswordAuthenticationToken。
2. 接下来,将身份验证传递到AuthenticationManager进行身份验证。
3. 如果身份验证失败,则失败事件有:
已清除 SecurityContextHolder。
RememberMeServices.loginFail 被调用。如果 RememberMe 未配置,则为空。
AuthenticationFailureHandler 被调用。
4. 如果身份验证成功,则成功事件有:
SessionAuthenticationStrategy 会通知有新的登录。
身份验证是在SecurityContextHolder上设置的。 之后,SecurityContextPersistenceFilter 将 SecurityContext 保存到 HttpSession中。
RememberMeServices.loginSuccess 被调用。 如果 RememberMe 未配置,则为空。
ApplicationEventPublisher 发布一个 InteractiveAuthenticationSuccessEvent。
⑩ Username/Password Authentication
验证用户身份的最常见方法之一是验证用户名和密码。这样,Spring Security 为使用用户名和密码进行身份验证提供了全面的支持。
Spring Security 提供了以下内置机制,用于从 HttpServletRequest 中读取用户名和密码:
-
Form Login(表单登入)
-
Basic Authentication(基本认证)
-
Digest Authentication(摘要式身份验证)
用于读取用户名和密码的每种受支持的机制都可以利用任何受支持的存储机制:
- 具有内存内认证的简单存储(In-Memory Authentication)
- 具有 JDBC 身份验证的关系数据库(JDBC Authentication)
- 使用 UserDetailsService 的自定义数据存储(use UserDetails)
- 具有 LDAP 认证的 LDAP 存储(LDAP Authentication)
⑪ Session Management
与 HTTP 会话相关的功能由 SessionManagementFilter 和 SessionAuthenticationStrategy 接口的组合来处理,过滤器委托该接口。 典型的用法包括防止会话固定保护攻击,检测会话超时以及限制已认证用户可以同时打开多少个会话。其功能有:
-
检测超时
-
并发会话控制
-
会话固定攻击防护
-
SessionManagementFilter
-
SessionAuthenticationStrategy
-
并发控制
⑫ Remember-Me 认证
“记住我”或“永久登录”身份验证是指能够记住会话之间的主体身份的网站。 通常,这是通过向浏览器发送一个 cookie 来实现的,该 cookie 在以后的会话中被检测到并导致自动登录。 Spring Security 提供了进行这些操作所需的钩子,并具有两个具体的“记住我”实现。 一种使用散列来保留基于 cookie 的令牌的安全性,另一种使用数据库或其他持久性存储机制来存储生成的令牌。这两种实现都需要 UserDetailsService。
⑬ 注销处理
使用 WebSecurityConfigurerAdapter 时,将自动应用注销功能。 默认是访问 URL / logout 将通过以下方式注销用户:
-
使 HTTP 会话无效
-
清理配置的所有 RememberMe 身份验证
-
清除 SecurityContextHolder
-
重定向到 /login?logout
⑭ 认证事件
对于成功或失败的每个身份验证,分别触发 AuthenticationSuccessEvent 或 AuthenticationFailureEvent。
若要侦听这些事件,必须首先发布 AuthenticationEventPublisher。 Spring Security 的 DefaultAuthenticationEventPublisher 可能会很好:
1 @Bean 2 public AuthenticationEventPublisher authenticationEventPublisher 3 (ApplicationEventPublisher applicationEventPublisher) { 4 return new DefaultAuthenticationEventPublisher(applicationEventPublisher); 5 }
然后,你可以使用 Spring 的 @EventListener 支持:
1 @Component 2 public class AuthenticationEvents { 3 @EventListener 4 public void onSuccess(AuthenticationSuccessEvent success) { 5 // ... 6 } 7 8 @EventListener 9 public void onFailure(AuthenticationFailureEvent failures) { 10 // ... 11 } 12 }
尽管与 AuthenticationSuccessHandler 和 AuthenticationFailureHandler 相似,但它们的优点是可以独立于Servlet API使用。
(etc.)
主要译自:Spring Security官方文档