Spring Security 快速入门

Spring Security 快速入门

快速开始

参考:Securing a Web Application

核心 API

设计原理

Spring Security 对于 Servlet 的支持基于过滤链(FilterChain)实现。

Spring 提供了一个名为 DelegatingFilterProxyFilter 实现,该实现允许在 Servlet 容器的生命周期和 Spring 的 ApplicationContext 之间进行桥接。 Servlet 容器允许使用其自己的标准注册 Filters,但它不了解 Spring 定义的 Bean。 DelegatingFilterProxy 可以通过标准的 Servlet 容器机制进行注册,但是可以将所有工作委托给实现 Filter 的 Spring Bean。

1
2
3
4
5
6
7
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

ExceptionTranslationFilter 可以将 AccessDeniedException 和 AuthenticationException 转换为 HTTP 响应。

exceptiontranslationfilter

核心源码:

1
2
3
4
5
6
7
8
9
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException e) {
if (!authenticated || e instanceof AuthenticationException) {
startAuthentication();
} else {
accessDenied();
}
}

认证

数据模型

Spring Security 框架中的认证数据模型如下:

img

  • Authentication - 认证信息实体。
    • principal - 用户标识。如:用户名、账户名等。通常是 UserDetails 的实例(后面详细讲解)。
    • credentials - 认证凭证。如:密码等。
    • authorities - 授权信息。如:用户的角色、权限等信息。
  • SecurityContext - 安全上下文。包含一个 Authentication 对象。
  • SecurityContextHolder - 安全上下文持有者。用于存储认证信息。

【示例】注册认证信息

1
2
3
4
5
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);

【示例】访问认证信息

认证基本流程

AbstractAuthenticationProcessingFilter 用作验证用户凭据的基本过滤器。 在对凭证进行身份验证之前,Spring Security 通常使用 AuthenticationEntryPoint 请求凭证。

abstractauthenticationprocessingfilter

  • (1)当用户提交其凭据时,AbstractAuthenticationProcessingFilter 从要验证的 HttpServletRequest 创建一个 Authentication。创建的身份验证类型取决于 AbstractAuthenticationProcessingFilter 的子类。例如,UsernamePasswordAuthenticationFilter 根据在 HttpServletRequest 中提交的用户名和密码来创建 UsernamePasswordAuthenticationToken
  • (2)接下来,将身份验证传递到 AuthenticationManager 进行身份验证。
  • (3)如果身份验证失败,则认证失败
    • 清除 SecurityContextHolder
    • 调用 RememberMeServices.loginFail。如果没有配置 remember me,则为空。
    • 调用 AuthenticationFailureHandler
  • (4)如果身份验证成功,则认证成功。
    • 如果是新的登录,则通知 SessionAuthenticationStrategy
    • 身份验证是在 SecurityContextHolder 上设置的。之后,SecurityContextPersistenceFilterSecurityContext 保存到 HttpSession 中。
    • 调用 RememberMeServices.loginSuccess。如果没有配置 remember me,则为空。
    • ApplicationEventPublisher 发布一个 InteractiveAuthenticationSuccessEvent

用户名/密码认证

读取用户名和密码的方式:

  • 表单
  • 基本认证
  • 数字认证

存储机制

表单认证

spring security 支持通过从 html 表单获取登录时提交的用户名、密码。

loginurlauthenticationentrypoint

一旦,登录信息被提交,UsernamePasswordAuthenticationFilter 就会验证用户名和密码。

usernamepasswordauthenticationfilter

基本认证

1
2
3
4
5
protected void configure(HttpSecurity http) {
http
// ...
.httpBasic(withDefaults());
}

内存认证

InMemoryUserDetailsManager 实现了 UserDetailsService ,提供了基本的用户名、密码认证,其认证数据存储在内存中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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 ,提供了基本的用户名、密码认证,其认证数据存储在关系型数据库中,通过 JDBC 方式访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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:

1
2
3
4
5
6
7
8
9
10
11
12
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

UserDetailsUserDetailsService 返回。 DaoAuthenticationProvider 验证 UserDetails,然后返回身份验证,该身份验证的主体是已配置的 UserDetailsService 返回的 UserDetails

DaoAuthenticationProvider 使用 UserDetailsService 检索用户名,密码和其他用于使用用户名和密码进行身份验证的属性。 Spring Security 提供 UserDetailsService 的内存中和 JDBC 实现。

您可以通过将自定义 UserDetailsService 公开为 bean 来定义自定义身份验证。

PasswordEncoder

Spring Security 的 servlet 支持通过与 PasswordEncoder 集成来安全地存储密码。 可以通过公开一个 PasswordEncoder Bean 来定制 Spring Security 使用的 PasswordEncoder 实现。

daoauthenticationprovider

Remember-Me

Spring Boot 集成

@EnableWebSecurity@Configuration 注解一起使用, 注解 WebSecurityConfigurer 类型的类。

或者利用@EnableWebSecurity注解继承 WebSecurityConfigurerAdapter 的类,这样就构成了 Spring Security 的配置。

  • configure(WebSecurity):通过重载该方法,可配置 Spring Security 的 Filter 链。
  • configure(HttpSecurity):通过重载该方法,可配置如何通过拦截器保护请求。

参考资料