Spring Security实现用户认证一:简单示例
1 原理
Spring Security是一个Java框架,用于保护应用程序的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。它支持多种身份验证选项和授权策略,开发人员可以根据需要选择适合的方式。
从官方给的流程图可以看出,DelegatingFilterProxy
通常被配置为一个Servlet过滤器,注册到Servlet容器中,而DelegatingFilterProxy
会将过滤请求代理给FilterChainProxy
。
Spring Security中定义的过滤器链会被注册进入FilterChainProxy
,FilterChainProxy
持有一个或多个SecurityFilterChain
实例,每个实例包含一组过滤器和一个匹配规则(通常是一个请求模式)。
- 当一个HTTP请求到达应用时,Servlet容器会首先调用
DelegatingFilterProxy
。 DelegatingFilterProxy
会将请求委托给FilterChainProxy
。FilterChainProxy
根据请求URL
选择适当的过滤器链(SecurityFilterChain),并按顺序执行链中的过滤器。
1.1 用户认证怎么进行和保存的?
认证流程
从上面得知,Spring Security内部是由一个个过滤器组成的,那必然会经历一个认证过滤器。
-
负责认证的过滤器为
AbstractAuthenticationProcessingFilter
,其本质是一个抽象类型的过滤器,通过调用里面一个用于执行认证的抽象方法attemptAuthentication
。那必然需要一个该抽象类的具体继承类去实现抽象方法attemptAuthentication
,完成认证的功能。 -
Spring Security中为其写了一个默认的继承类为
UsernamePasswordAuthenticationFilter
,实现了attemptAuthentication
方法,将提交的username
和password
生成UsernamePasswordAuthenticationToken
。 -
UsernamePasswordAuthenticationFilter
只是负责过滤采用用户名和密码认证方式的请求,生成的UsernamePasswordAuthenticationToken
并没完成认证,而是用于后面选择合适的AuthenticationProvider
。 -
ProviderManager
类实现了AuthenticationManager
接口,ProviderManager
类管理一个List<AuthenticationProvider> providers
,这里面存着可用的AuthenticationProvider
。ProviderManager
类通过UsernamePasswordAuthenticationToken
选择合适的AuthenticationProvider
进行具体的校验,如DaoAuthenticationProvider
。
如果认证失败:
SecurityContextHolder
被清空。RememberMeServices.loginFail
被调用(需要配置)。AuthenticationFailureHandler
被调用。
认证成功:
SessionAuthenticationStrategy
被通知有新的登录。Authentication
是在SecurityContextHolder
上设置的。RememberMeServices.loginSuccess
被调用。ApplicationEventPublisher
发布一个InteractiveAuthenticationSuccessEvent
事件。AuthenticationSuccessHandler
被调用。
SecurityContext保存
在得到用户认证成功的UsernamePasswordAuthenticationToken
,这个token包含了用户的用户名、权限和是否认证等信息,并且会写入到SecurityContext
中。 默认情况下是保存在session中的,方便下次直接从session中获取(持久化)。
如上图所示,SecurityContext
包含了Authentication
,Authentication
即UsernamePasswordAuthenticationToken
。SecurityContext
被设置在SecurityContextHolder
中。
SecurityContextHolder
用于存储和检索 SecurityContext。它是整个 Spring Security 框架中访问认证信息的核心组件。
由于在进入SecurityFilterChain
后,肯定需要将 从Repository 获取已经认证的信息 放在 认证之前执行,才能实现一个持久化存储。否则每次都是从新认证。
判断用户是否认证的便是SecurityContext
,SecurityContextPersistenceFilter
从SecurityContextRepository
加载 SecurityContext
并将其设置在 SecurityContextHolder
上。没有获取到SecurityContext
则未认证。
Spring Security中SecurityContextRepository
的默认实现是HttpSessionSecurityContextRepository
,所以为什么会将session作为默认的上下文存储地点。这样做的坏处是需要维持大量的session,加重了服务器的负担。
2 创建简单的登录认证示例
创建一个Spring Boot 的项目。
2.1 pom.xml依赖添加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.2 application.yaml配置
spring:
application:
name: spring-security
server:
port: 4555
logging:
level:
web: debug
2.3 创建WebSecurityConfig配置类
package com.song.cloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity //开启SpringSecurity自动配置(springboot中可以省略)
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authorize ->
authorize
.anyRequest() //对任何请求执行认证
.authenticated()
)
// formLogin 的配置先于 httpBasic
.formLogin(withDefaults()) //内置的表单认证
.httpBasic(withDefaults()); //内置的基础认证
return http.build();
}
}
2.4 测试
这一步便是最基本的认证配置,网页打开localhost:4555
,将会自动跳转到登录页面进行认证。
默认的用户名为:user
默认的密码在控制台打印输出,如下。
登录成功的页面: