agentsclimarketplace

Spring security

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

A curated pack of custom Claude Code skills for developers — installable as a Claude Code plugin marketplace.

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.

What its author says it does

Copied from the file, not written here

When to activate: Spring Security, authentication, authorization, OAuth2 resource server, custom filters, UserDetailsService, security context

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

Keep looking

Skills are one crate of 328,083. 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.