Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架

SpringSecurity配置示例

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // 关闭CSRF
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/register").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login") // 自定义登录页
.defaultSuccessUrl("/home", true)
.permitAll()
)
.logout(LogoutConfigurer::permitAll);

return http.build();
}

@Bean
public UserDetailsService userDetailsService() {
return username -> {
// 模拟用户信息(可换为从数据库中读取)
if ("admin".equals(username)) {
return User.withUsername("admin")
.password("{noop}123456") // {noop}表示不加密
.roles("ADMIN")
.build();
}
throw new UsernameNotFoundException("User not found");
};
}
}

核心功能

功能 说明
认证 验证“你是谁”,例如用户名密码登录
授权 验证“你是否有权限做某件事”,如访问某个接口或资源
攻击防护 防止常见攻击:CSRF、Session Fixation、点击劫持、密码暴力破解等
会话管理 登录用户的session管控,限制登录次数,踢出旧会话等
Remember-Me 实现“记住我”功能
与第三方认证集成 支持OAuth2 / JWT / OpenID Connect 认证
安全拦截链 通过过滤器链实现请求安全控制

核心组件

组件 说明
SecurityFilterChain 安全过滤器链,是SpringSecurity的核心机制
AuthenticationManager 认证管理器,处理用户身份认证
UserDetailsService 用户数据获取接口,通常配合数据库实现
UserDetails 表示用户信息的接口(用户名、密码、权限)
GrantedAuthority 权限标识对象,通常是一个字符串
SecurityContextHolder 存储当前用户的认证信息(线程本地变量)

SecurityFilterChain

SpringSecurity的请求处理核心机制,是多个Filter的组合,每个请求都会通过这些过滤器进行处理,支持自定义过滤器链:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.formLogin(); // 支持表单登录
return http.build();
}

AuthenticationManager

接收Authentication对象,负责认证过程,默认使用 ProviderManager 实现,它委托多个AuthenticationProvider去完成具体认证,AuthenticationProvider用于执行真正的认证逻辑,内置的DaoAuthenticationProvider使用用户名 + 密码来做认证

AuthenticationManager authManager = new ProviderManager(List.of(customProvider));

UserDetailsService + UserDetails

UserDetailsService提供用户数据的服务,定义了如何根据用户名加载用户信息

UserDetails是你返回的用户对象,必须包含用户名、密码、权限等字段

@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 假设从数据库查到了用户
return User.builder()
.username("admin")
.password(passwordEncoder().encode("123456"))
.roles("ADMIN")
.build();
}
}

GrantedAuthority

表示当前用户所拥有的权限/角色,SpringSecurity默认会将角色前缀加上ROLE_

GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN");

SecurityContextHolder

用于保存当前登录用户的认证信息(线程安全的ThreadLocal),可在任何地方获取当前用户

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();

注解权限控制

需要启用@EnableMethodSecurity,常用注解:

注解 作用
@PreAuthorize("hasRole('ADMIN')") 方法执行前权限检查
@PostAuthorize 方法执行后权限检查
@Secured("ROLE_USER") 基于角色
@RolesAllowed("ADMIN") JSR-250标准注解

Session会话管理

.sessionManagement(session -> session
.maximumSessions(1) // 只允许一个登录会话
.maxSessionsPreventsLogin(true)) // 后一个登录失败

与JWT结合

jwt登录过滤器,用户登录时认证:

public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;

public JwtLoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
setFilterProcessesUrl("/login"); // 登录路径
}

@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
LoginRequest creds = new ObjectMapper().readValue(req.getInputStream(), LoginRequest.class);
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(creds.getUsername(), creds.getPassword());

return authenticationManager.authenticate(token);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

// 登录成功后生成 JWT
@Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException {
String token = JwtUtils.generateToken(auth.getName());
res.addHeader("Authorization", "Bearer " + token);
}
}

jwt校验过滤器,解析请求头中 token:

public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String header = request.getHeader("Authorization");

if (header != null && header.startsWith("Bearer ")) {
String token = header.replace("Bearer ", "");

if (JwtUtils.validateToken(token)) {
String username = JwtUtils.getUsernameFromToken(token);
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());

SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}

SpringSecurity测试

@WithMockUser是SpringSecurity提供的一个测试注解,用于在单元测试或集成测试中模拟一个已认证的用户,使得测试环境下无需实际登录即可进行权限校验。

使用背景

在用Spring Boot+Spring Security开发应用时,我们常需要测试某些受保护的接口,比如:

@GetMapping("/admin")
@PreAuthorize("hasRole('ADMIN')")
public String adminPage() {
return "admin";
}

如果我们不设置用户上下文,直接访问这个接口的测试方法会返回 403 Forbidden。这时候 @WithMockUser 就能派上用场

示例

@SpringBootTest
@AutoConfigureMockMvc
public class AdminControllerTest {

@Autowired
private MockMvc mockMvc;

@Test
@WithMockUser(username = "testuser", roles = {"ADMIN"})
void adminPage_shouldReturnOk_forAdminUser() throws Exception {
mockMvc.perform(get("/admin"))
.andExpect(status().isOk());
}
}

注解参数

参数 说明
username 模拟用户的用户名,默认是 “user”
password 模拟用户的密码(仅用于显示,无实际用途)
roles 角色,最终会自动加前缀 "ROLE_"
authorities 权限,比 roles 更细粒度控制(不加前缀)
  • roles = {"ADMIN"}实际注入的是"ROLE_ADMIN"
  • rolesauthorities不能同时使用,只能用一个

原理

  • @WithMockUser是通过SecurityContextHolder注入一个UsernamePasswordAuthenticationToken到当前测试线程上下文中
  • MockMvc执行测试时,Spring Security会自动从SecurityContextHolder中获取已设置的用户认证信息

执行流程(表单登录为例)

  1. 用户访问受保护资源 → 被过滤器拦截
  2. 若未登录,跳转到登录页面
  3. 用户提交用户名和密码
  4. UsernamePasswordAuthenticationFilter拦截登录请求
  5. 交给AuthenticationManager进行身份验证
  6. UserDetailsService加载用户信息并校验密码
  7. 验证成功,将认证信息存入SecurityContextHolder
  8. 之后所有请求都带上用户身份,可以访问授权资源

常见问题

过滤器和拦截器

项目 Filter(过滤器) Interceptor(拦截器)
所属规范 Servlet 规范(JavaEE) Spring MVC 组件
作用范围 所有请求(包括静态资源、DispatcherServlet前) Spring MVC 控制器处理的请求
执行位置 早于DispatcherServlet DispatcherServlet之后,只拦截Controller请求
应用场景 安全认证、请求日志、编码处理、权限校验(全局) 登录拦截、权限校验、业务预处理(Controller层面)
配置方式 @WebFilterFilterRegistrationBean 实现HandlerInterceptor接口
执行顺序控制 通过@OrderFilterChain顺序控制 WebMvcConfigurer.addInterceptors()顺序配置

执行顺序

    ┌────────────────────────────┐
│ 前端请求进入服务器 │
└────────────────────────────┘

[Filter 1] --> 前置逻辑

[Filter 2] --> 前置逻辑

┌──── DispatcherServlet (Spring MVC) ────┐
│ ↓ │
[Interceptor 1] preHandle() │
│ ↓ │
[Interceptor 2] preHandle() │
│ ↓ │
│ Controller 方法 │
│ ↑ │
[Interceptor 2] postHandle() │
[Interceptor 1] postHandle() │
└───────────────↑────────────────────────┘

[Filter 2] --> 后置逻辑

[Filter 1] --> 后置逻辑

响应返回到客户端

实现机制

比较维度 Filter Interceptor
基于机制 Servlet API Spring MVC 拦截器链
是否依赖 Spring 无需依赖 Spring,可用于非 Spring 项目 只适用于 Spring 环境
本质实现 继承 javax.servlet.Filter 接口 实现 HandlerInterceptor 接口
是否可拦截静态资源 是,能拦截所有HTTP请求 不能拦截静态资源
是否能拿到Controller方法信息 拿不到 拿得到(HandlerMethod
适用粒度 适合系统层面拦截(如 XSS、防盗链、日志) 适合业务层面拦截(如用户登录、角色权限)

SpringSecurity是基于什么实现的

Spring Security是基于Servlet的Filter实现的

  • Spring Security 的设计目标是“请求级安全防护”,要在DispatcherServlet之前就拦截请求,包括静态资源、API、文件上传、非法访问等
  • 若等到拦截器阶段再处理,控制粒度太晚,系统容易被绕过

springsecurity有几种权限控制机制

  • 基于URL的权限控制,根据请求的路径来控制访问权限
  • 基于方法的权限控制(注解方式):@PreAuthorize、@PostAuthorize、@Secured、@RolesAllowed等
  • 基于注解表达式的权限控制(SpEL表达式)
  • 基于权限标签的控制(结合前端Thymeleaf)

springsecurity有什么过滤器

Spring Security的核心是一条由多个过滤器组成的过滤器链,常用的有

  • WebAsyncManagerIntegrationFilter(将Security上下文与异步线程绑定)
  • SecurityContextPersistenceFilter(加载SecurityContextSecurityContextHolder
  • CorsFilter(处理跨域请求)
  • UsernamePasswordAuthenticationFilter(登录处理)
  • LogoutFilter(处理登出逻辑)
  • BearerTokenAuthenticationFilter(处理Bearer Token认证)
  • OAuth2LoginAuthenticationFilter(处理OAuth2登录)
  • SessionManagementFilter(管理 Session)
  • FilterSecurityInterceptor(授权过滤器)