Spring安全
Mattakushi4322026-10-26查看 1696复制 1955
何时激活:Spring Security、身份验证、授权、OAuth2 资源服务器、自定义过滤器、UserDetailsService、安全上下文
📘如何使用这个技能
- 1复制技能内容·点击上方「Copy SKILL.md」复制全部内容,或 Download 下载到本地
- 2打开你的 AI 客户端·支持 Claude Code、Trae、Cursor、ChatGPT、DeepSeek、Kimi 等任意 AI 助手
- 3粘贴为系统提示/知识文件·Claude Code:放入 .claude/skills/ 目录;普通对话:作为首条消息发送并说「请严格按这份技能文档执行」
- 4直接提出你的需求·AI 会按技能定义的工作流、格式和约束来处理,无需再解释背景
💡 提示:技能生效后,每次对话只需一句需求,AI 会按 SKILL.md 的流程执行。
SKILL.md5964 chars
---
name: spring-security
description: "When to activate: Spring Security, authentication, authorization, OAuth2 resource server, custom filters, UserDetailsService, security context"
origin: zakr
metadata:
namespace: zakr/devtools-pack
version: 1.0.0
category: engineering
language: java-kotlin
platforms: [claude-code, codex, gemini-cli, cursor, aider, windsurf]
---
# Spring Security Patterns
## Custom UserDetailsService
```java
@Service
@RequiredArgsConstructor
public class AppUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return userRepository.findByEmail(email)
.map(this::toUserDetails)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + email));
}
private UserDetails toUserDetails(User user) {
List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.name()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(
user.getEmail(), user.getPasswordHash(), user.isActive(), true, true, true, authorities);
}
}
```
## OAuth2 Resource Server (JWT)
```java
@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthConverter()))
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin")
.anyRequest().hasAuthority("SCOPE_api")
)
.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthConverter() {
var grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
var converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return converter;
}
}
```
## Custom Authentication Filter
```java
public class ApiKeyAuthFilter extends OncePerRequestFilter {
private static final String API_KEY_HEADER = "X-API-Key";
private final ApiKeyService apiKeyService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String apiKey = request.getHeader(API_KEY_HEADER);
if (apiKey != null) {
apiKeyService.validateKey(apiKey).ifPresent(principal -> {
var auth = new UsernamePasswordAuthenticationToken(
principal, null, principal.getAuthorities());
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(auth);
});
}
chain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return new AntPathMatcher().match("/public/**", request.getServletPath());
}
}
// Register in SecurityFilterChain
http.addFilterBefore(apiKeyAuthFilter(), UsernamePasswordAuthenticationFilter.class);
```
## Security Context Access
```java
// In any Spring bean
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
boolean isAdmin = auth.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
// In controller via @AuthenticationPrincipal
@GetMapping("/me")
public UserResponse getCurrentUser(@AuthenticationPrincipal JwtAuthenticationToken token) {
String userId = token.getToken().getSubject();
return userService.findBySubject(userId);
}
// With custom principal
@GetMapping("/profile")
public Profile getProfile(@AuthenticationPrincipal AppUserPrincipal principal) {
return profileService.findByUserId(principal.getUserId());
}
```
## Request-Level Authorization with SpEL
```java
.authorizeHttpRequests(auth -> auth
// Only users accessing their own data
.requestMatchers("/api/users/{id}/**").access(
new WebExpressionAuthorizationManager("#id == authentication.name or hasRole('ADMIN')")
)
// IP allowlist
.requestMatchers("/actuator/**").access(
new WebExpressionAuthorizationManager("hasIpAddress('10.0.0.0/8')")
)
)
```
## Security Event Listening
```java
@Component
public class AuthenticationEventListener {
@EventListener
public void onSuccess(AuthenticationSuccessEvent event) {
log.info("Login success: {}", event.getAuthentication().getName());
}
@EventListener
public void onFailure(AbstractAuthenticationFailureEvent event) {
log.warn("Login failure for {}: {}", event.getAuthentication().getName(),
event.getException().getMessage());
auditService.recordFailedLogin(event.getAuthentication().getName());
}
}
```
## Key Rules
- Clear `SecurityContextHolder` after async processing — it's `ThreadLocal` and leaks across thread boundaries
- Never expose internal user IDs in JWT subjects; use opaque UUIDs or email
- Use `@AuthenticationPrincipal` in controllers instead of calling `SecurityContextHolder` directly
- Rate-limit authentication endpoints independently — Spring Security has no built-in rate limiting
- `hasAuthority('ROLE_X')` and `hasRole('X')` are equivalent — `hasRole` auto-prefixes `ROLE_`; be consistent