agentsclimarketplace

Spring security

Skill Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack/plugins/devtools-pack/skills/spring-security

When to activate: Spring Security, authentication, authorization, OAuth2 resource server, custom filters, UserDetailsService, security contextFrom its SKILL.md

Install
npx -y skills add Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack --skill spring-security

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

5.8 KB, ~1.0k tokens by cl100k_base, as published. Nobody here has run it

Spring Security Patterns

Custom UserDetailsService

@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)

@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

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

// 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

.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

@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

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.