一 Spring Security简介
Spring Security是Spring生态系统中的一个安全框架,主要用于处理认证(Authentication)和授权(Authorization)。它提供了一套完整的安全解决方案,可以轻松集成到Spring应用中。
二 核心概念
1. 认证(Authentication)
验证用户的身份,确认"你是谁"。例如:用户登录过程。
2. 授权(Authorization)
验证用户是否有权限执行某个操作,确认"你能做什么"。例如:检查用户是否有权访问某个API。
3. 主要组件
- SecurityContextHolder:存储安全上下文信息
- Authentication:存储当前用户的认证信息
- UserDetails:用户信息的核心接口
- UserDetailsService:加载用户信息的核心接口
- AuthenticationProvider:认证的具体实现者
三 实战案例:基于JWT的认证授权系统
一、核心架构
1. 核心组件关系图
请求 → SecurityFilterChain → (多个Security Filter) → 目标资源↓SecurityContextHolder↓SecurityContext↓Authentication/ \Principal GrantedAuthority
2. 核心组件说明
2.1 SecurityContextHolder
- 作用:存储当前线程的安全上下文信息
- 实现:使用ThreadLocal存储SecurityContext
- 访问方式:
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
2.2 SecurityContext
- 作用:持有Authentication对象和其他安全相关信息
- 生命周期:请求开始到结束
- 存储位置:ThreadLocal或Session中
2.3 Authentication
- 作用:存储用户认证信息
- 主要属性:
- principal:用户身份信息
- credentials:凭证信息(如密码)
- authorities:用户权限集合
- authenticated:是否已认证
- 常用实现:UsernamePasswordAuthenticationToken
二、认证流程详解
1. 完整认证流程图
用户请求登录↓
UsernamePasswordAuthenticationFilter↓
AuthenticationManager↓
AuthenticationProvider↓
UserDetailsService↓
UserDetails↓
Authentication对象↓
SecurityContext
2. 详细流程说明
2.1 认证入口(以登录为例)
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {// 1. 创建未认证的AuthenticationAuthentication authentication = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),loginRequest.getPassword());// 2. 执行认证Authentication authenticated = authenticationManager.authenticate(authentication);// 3. 认证成功,生成JWTSecurityContextHolder.getContext().setAuthentication(authenticated);String jwt = tokenProvider.generateToken(authenticated);return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}
2.2 认证管理器(AuthenticationManager)
public class ProviderManager implements AuthenticationManager {private List<AuthenticationProvider> providers;@Overridepublic Authentication authenticate(Authentication authentication) {// 遍历所有Provider尝试认证for (AuthenticationProvider provider : providers) {if (!provider.supports(authentication.getClass())) {continue;}try {return provider.authenticate(authentication);} catch (AuthenticationException e) {// 处理认证异常}}throw new AuthenticationException("无法认证");}
}
2.3 自定义认证提供者
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {private final CustomUserDetailsService userDetailsService;private final PasswordEncoder passwordEncoder;@Overridepublic Authentication authenticate(Authentication authentication) {// 1. 获取认证信息String username = authentication.getName();String password = authentication.getCredentials().toString();// 2. 加载用户信息UserDetails userDetails = userDetailsService.loadUserByUsername(username);// 3. 验证密码if (!passwordEncoder.matches(password, userDetails.getPassword())) {throw new BadCredentialsException("密码错误");}// 4. 创建已认证的Authenticationreturn new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());}
}
三、授权流程详解
1. 授权流程图
请求 → FilterSecurityInterceptor↓SecurityContextHolder获取Authentication↓AccessDecisionManager↓AccessDecisionVoter↓权限判断结果
2. 详细授权步骤
2.1 配置安全规则
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) {http.authorizeRequests()// 1. URL级别的权限控制.antMatchers("/api/public/**").permitAll().antMatchers("/api/admin/**").hasRole("ADMIN")// 2. 自定义权限判断.anyRequest().access("@customSecurityService.hasPermission(request,authentication)");}
}
2.2 方法级别权限控制
@Service
public class UserService {// 使用Spring EL表达式进行权限控制@PreAuthorize("hasRole('ADMIN') or #username == authentication.name")public UserDetails getUser(String username) {// 方法实现}
}
四、JWT集成原理
1. JWT认证流程
请求 → JwtAuthenticationFilter↓提取JWT令牌↓验证JWT有效性↓解析用户信息↓创建Authentication↓存入SecurityContext
2. JWT过滤器实现
public class JwtAuthenticationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) {try {// 1. 从请求中提取JWTString jwt = getJwtFromRequest(request);if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {// 2. 从JWT中获取用户信息String username = tokenProvider.getUsernameFromJWT(jwt);String roles = tokenProvider.getRolesFromJWT(jwt);// 3. 创建AuthenticationList<GrantedAuthority> authorities = Arrays.stream(roles.split(",")).map(SimpleGrantedAuthority::new).collect(Collectors.toList());UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(username, null, authorities);// 4. 设置认证信息SecurityContextHolder.getContext().setAuthentication(authentication);}} catch (Exception ex) {logger.error("无法设置用户认证", ex);}filterChain.doFilter(request, response);}
}
五、数据校验流程
1. 请求数据校验
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {// 1. @Valid触发数据校验// 2. 校验失败抛出MethodArgumentNotValidException
}public class LoginRequest {@NotBlank(message = "用户名不能为空")private String username;@NotBlank(message = "密码不能为空")private String password;
}
2. 认证数据校验
public class CustomUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) {// 1. 检查用户是否存在UserPrincipal user = userMap.get(username);if (user == null) {throw new UsernameNotFoundException("用户不存在");}// 2. 检查用户状态if (!user.isEnabled()) {throw new DisabledException("用户已禁用");}// 3. 检查账户是否过期if (!user.isAccountNonExpired()) {throw new AccountExpiredException("账户已过期");}// 4. 检查账户是否锁定if (!user.isAccountNonLocked()) {throw new LockedException("账户已锁定");}return user;}
}
3. JWT数据校验
public class JwtTokenProvider {public boolean validateToken(String authToken) {try {// 1. 验证签名Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);// 2. 验证是否过期Claims claims = getClaimsFromJWT(authToken);return !claims.getExpiration().before(new Date());} catch (SignatureException ex) {logger.error("无效的JWT签名");} catch (MalformedJwtException ex) {logger.error("无效的JWT令牌");} catch (ExpiredJwtException ex) {logger.error("JWT令牌已过期");} catch (UnsupportedJwtException ex) {logger.error("不支持的JWT令牌");} catch (IllegalArgumentException ex) {logger.error("JWT声明为空");}return false;}
}
六、异常处理流程
1. 认证异常处理
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request,HttpServletResponse response,AuthenticationException e) throws IOException {// 1. 未认证异常处理response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.setContentType(MediaType.APPLICATION_JSON_VALUE);String message = "请先进行认证";if (e instanceof BadCredentialsException) {message = "用户名或密码错误";} else if (e instanceof JwtExpiredTokenException) {message = "token已过期";}response.getWriter().write(new ObjectMapper().writeValueAsString(new ApiResponse(false, message)));}
}
2. 授权异常处理
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request,HttpServletResponse response,AccessDeniedException e) throws IOException {// 1. 权限不足异常处理response.setStatus(HttpServletResponse.SC_FORBIDDEN);response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.getWriter().write(new ObjectMapper().writeValueAsString(new ApiResponse(false, "没有足够的权限")));}
}
七、安全上下文传递
1. 异步方法中的安全上下文
@Async
public CompletableFuture<String> asyncMethod() {// 1. 获取当前安全上下文SecurityContext context = SecurityContextHolder.getContext();return CompletableFuture.supplyAsync(() -> {try {// 2. 设置安全上下文到新线程SecurityContextHolder.setContext(context);// 3. 执行业务逻辑return "success";} finally {// 4. 清理安全上下文SecurityContextHolder.clearContext();}});
}