本文基于 Spring Security 5.3版本
官方文档
一、简介
Spring Security是一个提供身份验证,授权和保护以防止常见攻击的框架。 凭借对命令式和响应式应用程序的一流支持,它是用于保护基于Spring的应用程序的实际标准。
Spring Security通过标准的Filter与servlet容器进行集成,所以,只要是符合servlet规范的容器都可以用Spring Servlet进行保护。
Spring Security需要Java 8或更高版本的运行时环境。
二、引入Spring Security
文档中介绍了使用springboot和不使用springboot集成的方法,这里只介绍使用springboot的方式(采用maven)
新建springboot项目(版本2.2.4)引入maven坐标
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
三、Spring Security模块划分
在Spring Security 3.0中,代码库被细分为单独的jar,这些jar更清楚地区分了不同的功能区域和第三方依赖项。
1.Core — spring-security-core.jar
该模块包含核心身份验证和访问控制类和接口,远程支持和基本配置API。 使用Spring Security的任何应用程序都需要它。 它支持独立的应用程序,远程客户端,方法(服务层)安全性和JDBC用户配置。 它包含以下顶级程序包
- org.springframework.security.core
- org.springframework.security.access
- org.springframework.security.authentication
- org.springframework.security.provisioning
2.Remoting — spring-security-remoting.jar
该模块提供了与Spring Remoting的集成。 除非您要编写使用Spring Remoting的远程客户端,否则您不需要关注。
3.Web — spring-security-web.jar
该模块包含过滤器和相关的Web安全基础结构代码。 它包含任何与Servlet API相关的内容。 如果需要Spring Security Web认证服务和基于URL的访问控制,则需要它。 主要软件包是org.springframework.security.web。
4.Config — spring-security-config.jar
该模块包含安全名称空间解析代码和Java配置代码。 如果您使用Spring Security XML名称空间进行配置或Spring Security的Java配置支持,则需要它
5.LDAP — spring-security-ldap.jar
该模块提供LDAP身份验证和供应代码。 如果您需要使用LDAP认证或管理LDAP用户条目,则为必填项
6. OAuth 2.0 Core — spring-security-oauth2-core.jar
spring-security-oauth2-core.jar包含核心类和接口,这些类和接口提供对OAuth 2.0授权框架和OpenID Connect Core 1.0的支持。 使用OAuth 2.0或OpenID Connect Core 1.0的应用程序(例如客户端,资源服务器和授权服务器)需要它
7.OAuth 2.0 Client — spring-security-oauth2-client.jar
包含Spring Security对OAuth 2.0授权框架和OpenID Connect Core 1.0的客户端支持。 使用OAuth 2.0登录或OAuth客户端支持的应用程序需要使用它
8.OAuth 2.0 JOSE — spring-security-oauth2-jose.jar
包含Spring Security对JOSE(Javascript对象签名和加密)框架的支持。 JOSE框架旨在提供一种在各方之间安全地转移索赔的方法。 它是根据一系列规范构建的:
- JSON Web Token (JWT)
- JSON Web Signature (JWS)
- JSON Web Encryption (JWE)
- JSON Web Key (JWK)
9. OAuth 2.0 Resource Server — spring-security-oauth2-resource-server.jar
包含Spring Security对OAuth 2.0资源服务器的支持。 它用于通过OAuth 2.0承载令牌保护API
10.ACL — spring-security-acl.jar
该模块包含专门的域对象ACL实现。 它用于将安全性应用于应用程序中的特定域对象实例
11.CAS — spring-security-cas.jar
该模块包含Spring Security的CAS客户端集成。 如果要对CAS单点登录服务器使用Spring Security Web认证,则应该使用它。
12.OpenID — spring-security-openid.jar
该模块包含OpenID Web身份验证支持。 它用于根据外部OpenID服务器对用户进行身份验证
13.Test — spring-security-test.jar
该模块包含对使用Spring Security进行测试的支持
四、Spring Boot 关于Spring Security的自动配置
1. springSecurityFilterChain
自动配置会在servlet容器中创建一个名为springSecurityFilterChain的过滤器,负责应用程序内的所有安全性(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)
2. UserDetailsService
自动配置会创建一个UserDetailsService实例,其中包含用户名user和一个随机生成的密码,该密码将记录到控制台
3. springSecurityFilterChain会作为标准的Filter注册到Servlet容器中,每个请求都会经过它被处理
4. 其他自动完成的配置
- 要求经过身份验证的用户才能与应用程序进行任何交互
- 生成一个默认的登录表单
- 使用用户名user和打印到控制台的密码登录
- 使用BCrypt保护密码存储
- 用户注销
- CSRF攻击预防
- 会话固定保护
- 安全请求头信息集成
- HTTP严格传输安全性用于安全请求
- X-Content-Type-Options集成
- 缓存控制(以后可以由您的应用程序覆盖,以允许缓存您的静态资源)
- X-XSS-Protection集成
- X-Frame-Options集成有助于防止Clickjacking
- 集成servletAPI
五、Spring Security整体架构设计
1.FilterChain
首先是spring security的FilterChain里面包含很多spring security内部的过滤器,并且过滤器的顺序是固定的
2.DelegatingFilterProxy
Spring提供了一个名为DelegatingFilterProxy的Filter实现,可以在Servlet容器的生命周期和Spring的ApplicationContext之间进行桥接。 Servlet容器允许使用其自己的标准注册Filters,但它不了解Spring定义的Bean。 DelegatingFilterProxy可以通过标准的Servlet容器机制进行注册,但是可以将所有工作委托给实现Filter的Spring Bean。
DelegatingFilterProxy的作用可以用下面的伪代码表示
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);
}
DelegatingFilterProxy的另一个好处是它允许延迟查找Filter bean实例。 这很重要,因为容器需要在容器启动之前注册Filter实例。 但是,Spring通常使用ContextLoaderListener加载Spring Bean,直到需要注册Filter实例之后才可以加载。
3.FilterChainProxy
Spring Security的Servlet支持包含在FilterChainProxy中。 FilterChainProxy是Spring Security提供的特殊过滤器,允许通过SecurityFilterChain委派许多过滤器实例。 由于FilterChainProxy是Bean,因此通常将其包装在DelegatingFilterProxy中。
4.SecurityFilterChain
FilterChainProxy使用SecurityFilterChain确定应对此请求调用哪些Spring Security过滤器。
5. Security Filters
SecurityFilterChain 中包含很多个Filter,这些Filter装载到SecurityFilterChain中, SecurityFilterChain再装载到FilterChainProxy中,FilterChainProxy再被装载到连接Servlet容器和spring容器的DelegatingFilterProxy中。
包含以下这些过滤器:
ChannelProcessingFilter
ConcurrentSessionFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
CsrfFilter
LogoutFilter
OAuth2AuthorizationRequestRedirectFilter
Saml2WebSsoAuthenticationRequestFilter
X509AuthenticationFilter
AbstractPreAuthenticatedProcessingFilter
CasAuthenticationFilter
OAuth2LoginAuthenticationFilter
Saml2WebSsoAuthenticationFilter
ConcurrentSessionFilter
OpenIDAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
BearerTokenAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
JaasApiIntegrationFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
OAuth2AuthorizationCodeGrantFilter
SessionManagementFilter
SwitchUserFilter
6.Handling Security Exceptions
ExceptionTranslationFilter允许将AccessDeniedException和AuthenticationException转换为HTTP响应。它也是上面介绍的过滤器链中的一个
①开始处理
②如果用户未通过身份验证或抛出AuthenticationException,则启动身份验证。
③如果抛出AccessDeniedException,则拒绝访问。 调用AccessDeniedHandler以处理拒绝的访问
六、认证方式
1. 认证模块的组件
-
SecurityContextHolder:Spring Security在其中存储通过身份验证的人员的详细信息
SecurityContext:从SecurityContextHolder获得,并包含当前经过身份验证的用户的身份验证
Authentication
两个主要用途:
1.作为AuthenticationManager的输入,用于提供用户提供的用于身份验证的凭据
2.代表当前经过身份验证的用户。 可以从SecurityContext获得当前的身份验证GrantedAuthority:授予身份验证主体的权限(即角色,作用域等)
AuthenticationManager:AuthenticationManager是用于定义Spring Security的过滤器如何执行身份验证的API。 然后,由调用AuthenticationManager的控制器(即Spring Security的Filters)在SecurityContextHolder上设置返回的身份验证。 如果您没有与Spring Security的过滤器集成,则可以直接设置
SecurityContextHolder,并且不需要使用AuthenticationManager。虽然AuthenticationManager的实现可以是任何东西,但最常见的实现是ProviderManager。-
ProviderManager:AuthenticationManager的最常见实现,ProviderManager将认证工作委托给AuthenticationProviders列表, 每个AuthenticationProvider都有机会指示认证应该成功,失败,或者表明它不能做出决定并允许下一个AuthenticationProvider进行决定。 如果没有一个已配置的AuthenticationProviders可以进行身份验证,则身份验证将失败,并显示ProviderNotFoundException,这是一个特殊的AuthenticationException,它指示未配置ProviderManager并支持传递给它的Authentication类型。
七、 Username/Password Authentication(用户名密码认证方式)
由于这个认证组件部分内容笔记多,单独拉出来说明
Spring Security提供了以下内置机制,用于从HttpServletRequest中读取用户名和密码
1.Form Login
2.Basic Authentication
3.Digest Authentication
1.表单登录方式
该图基于我们的SecurityFilterChain图。
①用户对未经授权的资源/ private进行未经身份验证的请求。
②Spring Security的FilterSecurityInterceptor表示通过抛出AccessDeniedException拒绝了未经身份验证的请求。
③由于用户未通过身份验证,因此ExceptionTranslationFilter会启动“开始身份验证”,并使用配置的AuthenticationEntryPoint将重定向发送到登录页面。 在大多数情况下,AuthenticationEntryPoint是LoginUrlAuthenticationEntryPoint的实例。
④然后,浏览器将请求将其重定向到的登录页面。
⑤应用程序中的某些内容必须呈现登录页面。
提交用户名和密码后,UsernamePasswordAuthenticationFilter会对用户名和密码进行身份验证
①当用户提交其用户名和密码时,UsernamePasswordAuthenticationFilter通过从HttpServletRequest中提取用户名和密码来创建UsernamePasswordAuthenticationToken,这是一种身份验证类型。
②接下来,将UsernamePasswordAuthenticationToken传递到AuthenticationManager进行身份验证。 AuthenticationManager外观的详细信息取决于用户信息的存储方式。
③如果身份验证失败,则失败
- 清除SecurityContextHolder。
- RememberMeServices.loginFail被调用。如果记住我没有配置,这是一个禁忌。
- AuthenticationFailureHandler被调用。
④如果身份验证成功,则为成功。
- 新的登录通知SessionAuthenticationStrategy。
- 身份验证是在SecurityContextHolder上设置的。
- RememberMeServices.loginSuccess被调用。如果记住我没有配置,这是一个禁忌。
- ApplicationEventPublisher发布一个
- InteractiveAuthenticationSuccessEvent。
- AuthenticationSuccessHandler被调用。通常,这是一个SimpleUrlAuthenticationSuccessHandler,当我们重定向到登录页面时,它将重定向到ExceptionTranslationFilter保存的请求。
- AuthenticationProvider:
可以将多个AuthenticationProviders注入ProviderManager。 每个AuthenticationProvider执行特定的身份验证类型。
例如,DaoAuthenticationProvider支持基于用户名/密码的身份验证,而JwtAuthenticationProvider支持对JWT令牌的身份验证。 - 带AuthenticationEntryPoint的请求凭据:用于从客户端请求凭据(即,重定向到登录页面,发送WWW身份验证响应等)
-
AbstractAuthenticationProcessingFilter:用于身份验证的基本过滤器
2.Basic Authentication
该图基于我们的SecurityFilterChain图。
①用户对未经授权的资源发起请求。
②Spring Security的FilterSecurityInterceptor表示通过抛出AccessDeniedException拒绝了未经身份验证的请求。
③由于用户未通过身份验证,因此ExceptionTranslationFilter会启动“开始身份验证”。 配置的AuthenticationEntryPoint是BasicAuthenticationEntryPoint的实例,该实例响应WWW-Authenticate头。 RequestCache通常是一个NullRequestCache,它不保存请求,因为客户端能够重播它最初请求的请求。
当客户端收到WWW-Authenticate响应头时,它知道应该使用用户名和密码重试。 以下是正在处理的用户名和密码的流程。
①当用户提交其用户名和密码时,BasicAuthenticationFilter通过从HttpServletRequest中提取用户名和密码来创建UsernamePasswordAuthenticationToken,这是一种身份验证类型。
②接下来,将UsernamePasswordAuthenticationToken传递到AuthenticationManager进行身份验证。
③如果身份验证失败,则失败,清除SecurityContextHolder存储,RememberMeServices.loginFail被调用。
调用AuthenticationEntryPoint触发WWW-Authenticate重新发送。
④如果身份验证成功,则为成功,向SecurityContextHolder存储登录成功信息
RememberMeServices.loginSuccess被调用
BasicAuthenticationFilter调用FilterChain.doFilter(request,response)继续进行其余的应用程序逻辑。
Spring Security的HTTP基本身份验证支持默认为启用。 但是,一旦提供了任何基于servlet的配置,则必须显式提供HTTP Basic
如下
protected void configure(HttpSecurity http) {
http
// ...
.httpBasic(withDefaults());
}
3.Digest Authentication
略
4.In-Memory Authentication
springsecurity支持在内存中临时设置用户信息完成认证
可以按实例进行设置,定义两个用户并设置权限
@Bean
public UserDetailsService users() {
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
5. JDBC Authentication
支持从数据库查询用户信息完成认证