Spring安全

Mattakushi4322026-10-26查看 1696复制 1955

何时激活:Spring Security、身份验证、授权、OAuth2 资源服务器、自定义过滤器、UserDetailsS​​ervice、安全上下文

Download (.md)

📘如何使用这个技能

  1. 1
    复制技能内容·点击上方「Copy SKILL.md」复制全部内容,或 Download 下载到本地
  2. 2
    打开你的 AI 客户端·支持 Claude Code、Trae、Cursor、ChatGPT、DeepSeek、Kimi 等任意 AI 助手
  3. 3
    粘贴为系统提示/知识文件·Claude Code:放入 .claude/skills/ 目录;普通对话:作为首条消息发送并说「请严格按这份技能文档执行」
  4. 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
来源:skillsdirectory

评论0

登录后参与评论

还没有评论,来抢沙发吧