--- title: Spring Security 快速入门 date: 2022-02-17 22:34:30 order: 02 categories: - Java - 框架 - 安全 tags: - Java - 框架 - 安全 - SpringSecurity permalink: /pages/eea88ac8/ --- # Spring Security 快速入门 ## 快速开始 参考:[Securing a Web Application](https://spring.io/guides/gs/securing-web/) ## 核心 API ## 设计原理 Spring Security 对于 Servlet 的支持基于过滤链(`FilterChain`)实现。 Spring 提供了一个名为 `DelegatingFilterProxy` 的 `Filter` 实现,该实现允许在 Servlet 容器的生命周期和 Spring 的 `ApplicationContext` 之间进行桥接。 Servlet 容器允许使用其自己的标准注册 Filters,但它不了解 Spring 定义的 Bean。 `DelegatingFilterProxy` 可以通过标准的 Servlet 容器机制进行注册,但是可以将所有工作委托给实现 Filter 的 Spring Bean。 ```java 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` 使用 `SecurityFilterChain` 确定应对此请求调用哪些 Spring Security 过滤器。 `SecurityFilterChain` 中的安全过滤器通常是 Bean,但它们是使用 `FilterChainProxy` 而不是 `DelegatingFilterProxy` 注册的。 实际上,`FilterChainProxy` 可用于确定应使用哪个 `SecurityFilterChain`。如果您的应用程序可以为不同的模块提供完全独立的配置。 ![multi securityfilterchain](https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/images/servlet/architecture/multi-securityfilterchain.png) ExceptionTranslationFilter 可以将 AccessDeniedException 和 AuthenticationException 转换为 HTTP 响应。 ![exceptiontranslationfilter](https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/images/servlet/architecture/exceptiontranslationfilter.png) 核心源码: ```java try { filterChain.doFilter(request, response); } catch (AccessDeniedException | AuthenticationException e) { if (!authenticated || e instanceof AuthenticationException) { startAuthentication(); } else { accessDenied(); } } ``` ## 认证 ### 数据模型 Spring Security 框架中的认证数据模型如下: ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200331115710.png) - `Authentication` - 认证信息实体。 - `principal` - 用户标识。如:用户名、账户名等。通常是 `UserDetails` 的实例(后面详细讲解)。 - `credentials` - 认证凭证。如:密码等。 - `authorities` - 授权信息。如:用户的角色、权限等信息。 - `SecurityContext` - 安全上下文。包含一个 `Authentication` 对象。 - `SecurityContextHolder` - 安全上下文持有者。用于存储认证信息。 【示例】注册认证信息 ```java SecurityContext context = SecurityContextHolder.createEmptyContext(); Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER"); context.setAuthentication(authentication); SecurityContextHolder.setContext(context); ``` 【示例】访问认证信息 ### 认证基本流程 AbstractAuthenticationProcessingFilter 用作验证用户凭据的基本过滤器。 在对凭证进行身份验证之前,Spring Security 通常使用 AuthenticationEntryPoint 请求凭证。 ![abstractauthenticationprocessingfilter](https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/images/servlet/authentication/architecture/abstractauthenticationprocessingfilter.png) - (1)当用户提交其凭据时,`AbstractAuthenticationProcessingFilter` 从要验证的 `HttpServletRequest` 创建一个 `Authentication`。创建的身份验证类型取决于 `AbstractAuthenticationProcessingFilter` 的子类。例如,`UsernamePasswordAuthenticationFilter` 根据在 `HttpServletRequest` 中提交的用户名和密码来创建 `UsernamePasswordAuthenticationToken`。 - (2)接下来,将身份验证传递到 `AuthenticationManager` 进行身份验证。 - (3)如果身份验证失败,则认证失败 - 清除 `SecurityContextHolder`。 - 调用 `RememberMeServices.loginFail`。如果没有配置 remember me,则为空。 - 调用 `AuthenticationFailureHandler`。 - (4)如果身份验证成功,则认证成功。 - 如果是新的登录,则通知 `SessionAuthenticationStrategy`。 - 身份验证是在 `SecurityContextHolder` 上设置的。之后,`SecurityContextPersistenceFilter` 将 `SecurityContext` 保存到 `HttpSession` 中。 - 调用 `RememberMeServices.loginSuccess`。如果没有配置 remember me,则为空。 - `ApplicationEventPublisher` 发布一个 `InteractiveAuthenticationSuccessEvent`。 ### 用户名/密码认证 读取用户名和密码的方式: - 表单 - 基本认证 - 数字认证 存储机制 - 内存 - JDBC - [UserDetailsService](https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/#servlet-authentication-userdetailsservice) - LDAP #### 表单认证 spring security 支持通过从 html 表单获取登录时提交的用户名、密码。 ![loginurlauthenticationentrypoint](https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/images/servlet/authentication/unpwd/loginurlauthenticationentrypoint.png) 一旦,登录信息被提交,`UsernamePasswordAuthenticationFilter` 就会验证用户名和密码。 ![usernamepasswordauthenticationfilter](https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/images/servlet/authentication/unpwd/usernamepasswordauthenticationfilter.png) #### 基本认证 ```java protected void configure(HttpSecurity http) { http // ... .httpBasic(withDefaults()); } ``` #### 内存认证 `InMemoryUserDetailsManager` 实现了 [UserDetailsService](https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/#servlet-authentication-userdetailsservice) ,提供了基本的用户名、密码认证,其认证数据存储在内存中。 ```java @Bean public UserDetailsService users() { // The builder will ensure the passwords are encoded before saving in memory UserBuilder users = User.withDefaultPasswordEncoder(); UserDetails user = users .username("user") .password("password") .roles("USER") .build(); UserDetails user = users .username("admin") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } ``` #### JDBC 认证 JdbcUserDetailsManager 实现了 [UserDetailsService](https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/#servlet-authentication-userdetailsservice) ,提供了基本的用户名、密码认证,其认证数据存储在关系型数据库中,通过 JDBC 方式访问。 ``` @Bean UserDetailsManager users(DataSource dataSource) { 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(); JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource); users.createUser() } ``` 基本的 scheam: ```sql create table users( username varchar_ignorecase(50) not null primary key, password varchar_ignorecase(50) not null, enabled boolean not null ); create table authorities ( username varchar_ignorecase(50) not null, authority varchar_ignorecase(50) not null, constraint fk_authorities_users foreign key(username) references users(username) ); create unique index ix_auth_username on authorities (username,authority); ``` #### UserDetailsService `UserDetails` 由 `UserDetailsService` 返回。 `DaoAuthenticationProvider` 验证 `UserDetails`,然后返回身份验证,该身份验证的主体是已配置的 `UserDetailsService` 返回的 `UserDetails`。 `DaoAuthenticationProvider` 使用 `UserDetailsService` 检索用户名,密码和其他用于使用用户名和密码进行身份验证的属性。 Spring Security 提供 `UserDetailsService` 的内存中和 JDBC 实现。 您可以通过将自定义 `UserDetailsService` 公开为 bean 来定义自定义身份验证。 #### PasswordEncoder Spring Security 的 servlet 支持通过与 `PasswordEncoder` 集成来安全地存储密码。 可以通过公开一个 PasswordEncoder Bean 来定制 Spring Security 使用的 PasswordEncoder 实现。 ![daoauthenticationprovider](https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/images/servlet/authentication/unpwd/daoauthenticationprovider.png) ### Remember-Me ## Spring Boot 集成 `@EnableWebSecurity` 和 `@Configuration` 注解一起使用, 注解 `WebSecurityConfigurer` 类型的类。 或者利用`@EnableWebSecurity`注解继承 `WebSecurityConfigurerAdapter` 的类,这样就构成了 _Spring Security_ 的配置。 - configure(WebSecurity):通过重载该方法,可配置 Spring Security 的 Filter 链。 - configure(HttpSecurity):通过重载该方法,可配置如何通过拦截器保护请求。 ## 参考资料 - [Spring Security Architecture](https://spring.io/guides/topicals/spring-security-architecture) - [Securing a Web Application](https://spring.io/guides/gs/securing-web/)