一 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
- 访问方式:
java
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 认证入口(以登录为例)
java
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
// 1. 创建未认证的Authentication
Authentication authentication = new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
);
// 2. 执行认证
Authentication authenticated = authenticationManager.authenticate(authentication);
// 3. 认证成功,生成JWT
SecurityContextHolder.getContext().setAuthentication(authenticated);
String jwt = tokenProvider.generateToken(authenticated);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}
2.2 认证管理器(AuthenticationManager)
java
public class ProviderManager implements AuthenticationManager {
private List<AuthenticationProvider> providers;
@Override
public 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 自定义认证提供者
java
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final CustomUserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
public 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. 创建已认证的Authentication
return new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
}
}
三、授权流程详解
1. 授权流程图
请求 → FilterSecurityInterceptor
↓
SecurityContextHolder获取Authentication
↓
AccessDecisionManager
↓
AccessDecisionVoter
↓
权限判断结果
2. 详细授权步骤
2.1 配置安全规则
java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected 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 方法级别权限控制
java
@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过滤器实现
java
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
try {
// 1. 从请求中提取JWT
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
// 2. 从JWT中获取用户信息
String username = tokenProvider.getUsernameFromJWT(jwt);
String roles = tokenProvider.getRolesFromJWT(jwt);
// 3. 创建Authentication
List<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. 请求数据校验
java
@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. 认证数据校验
java
public class CustomUserDetailsService implements UserDetailsService {
@Override
public 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数据校验
java
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. 认证异常处理
java
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public 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. 授权异常处理
java
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public 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. 异步方法中的安全上下文
java
@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();
}
});
}