Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架
SpringSecurity配置示例
@Configuration @EnableWebSecurity public class SecurityConfig {
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .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") .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); } }
@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"
roles
和authorities
不能同时使用,只能用一个
原理
@WithMockUser
是通过SecurityContextHolder
注入一个UsernamePasswordAuthenticationToken
到当前测试线程上下文中
- 在
MockMvc
执行测试时,Spring Security会自动从SecurityContextHolder
中获取已设置的用户认证信息
执行流程(表单登录为例)
- 用户访问受保护资源 → 被过滤器拦截
- 若未登录,跳转到登录页面
- 用户提交用户名和密码
UsernamePasswordAuthenticationFilter
拦截登录请求
- 交给
AuthenticationManager
进行身份验证
UserDetailsService
加载用户信息并校验密码
- 验证成功,将认证信息存入
SecurityContextHolder
- 之后所有请求都带上用户身份,可以访问授权资源
常见问题
过滤器和拦截器
项目 |
Filter(过滤器) |
Interceptor(拦截器) |
所属规范 |
Servlet 规范(JavaEE) |
Spring MVC 组件 |
作用范围 |
所有请求(包括静态资源、DispatcherServlet前) |
Spring MVC 控制器处理的请求 |
执行位置 |
早于DispatcherServlet |
DispatcherServlet之后,只拦截Controller请求 |
应用场景 |
安全认证、请求日志、编码处理、权限校验(全局) |
登录拦截、权限校验、业务预处理(Controller层面) |
配置方式 |
@WebFilter 或FilterRegistrationBean |
实现HandlerInterceptor 接口 |
执行顺序控制 |
通过@Order 或FilterChain 顺序控制 |
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(加载
SecurityContext
到SecurityContextHolder
)
- CorsFilter(处理跨域请求)
- UsernamePasswordAuthenticationFilter(登录处理)
- LogoutFilter(处理登出逻辑)
- BearerTokenAuthenticationFilter(处理Bearer Token认证)
- OAuth2LoginAuthenticationFilter(处理OAuth2登录)
- SessionManagementFilter(管理 Session)
- FilterSecurityInterceptor(授权过滤器)