# Java Security

> When to activate: Java security, Spring Security, JWT, OAuth2, SecurityFilterChain, method security, CSRF, CORS, password encoding

- Skill: `mattakushi432/java-security` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/java-security`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/java-security/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/java-security

---

# Java Security Patterns

## Spring Security 6 Configuration

```java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(AbstractHttpConfigurer::disable)          // stateless API
            .sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**", "/actuator/health").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
            .exceptionHandling(ex -> ex
                .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
                .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
            )
            .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12);
    }
}
```

## JWT with Nimbus (Spring Security)

```java
@Configuration
public class JwtConfig {

    @Value("${app.jwt.secret}")
    private String jwtSecret;

    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withSecretKey(secretKey()).build();
    }

    @Bean
    public JwtEncoder jwtEncoder() {
        return new NimbusJwtEncoder(new ImmutableSecret<>(secretKey()));
    }

    private SecretKey secretKey() {
        return new SecretKeySpec(jwtSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
    }
}

@Service
@RequiredArgsConstructor
public class TokenService {
    private final JwtEncoder encoder;

    public String generateToken(UserDetails user) {
        Instant now = Instant.now();
        JwtClaimsSet claims = JwtClaimsSet.builder()
            .issuer("my-service")
            .issuedAt(now)
            .expiresAt(now.plus(1, ChronoUnit.HOURS))
            .subject(user.getUsername())
            .claim("roles", user.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority).toList())
            .build();
        return encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
    }
}
```

## Method-Level Security

```java
@Service
public class DocumentService {

    @PreAuthorize("hasRole('ADMIN') or #userId == authentication.name")
    public Document getDocument(Long id, String userId) { ... }

    @PostAuthorize("returnObject.ownerId == authentication.name")
    public Document findById(Long id) { ... }

    @PreAuthorize("hasAuthority('DOCUMENT_WRITE')")
    public Document create(CreateDocumentRequest request) { ... }

    @Secured("ROLE_ADMIN")
    public void deleteDocument(Long id) { ... }
}
```

## CORS Configuration

```java
@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(List.of("https://app.example.com"));
    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
    config.setAllowCredentials(true);
    config.setMaxAge(3600L);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/api/**", config);
    return source;
}
```

## Input Validation & SQL Injection Prevention

```java
// Always use parameterized queries
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);

// Never concatenate user input
// BAD:  "SELECT * FROM users WHERE name = '" + name + "'"
// GOOD: jdbcTemplate.queryForObject("SELECT * FROM users WHERE name = ?", User.class, name)

// Validate at boundary
@PostMapping("/users")
public UserResponse create(@Valid @RequestBody CreateUserRequest request) { ... }

public record CreateUserRequest(
    @NotBlank @Size(max = 100) String name,
    @NotBlank @Email String email,
    @Pattern(regexp = "^(?=.*[A-Z])(?=.*\\d).{8,}$") String password
) {}
```

## Secrets Management

```java
// Never hardcode secrets — read from environment
@Value("${app.jwt.secret:#{null}}")
private String jwtSecret;

@PostConstruct
public void validateSecrets() {
    if (jwtSecret == null || jwtSecret.length() < 32) {
        throw new IllegalStateException("JWT secret must be at least 32 characters");
    }
}

// Use Spring Cloud Vault or AWS Secrets Manager in production
@Configuration
@EnableVaultConfiguration
public class VaultConfig extends AbstractVaultConfiguration {
    @Override
    public VaultEndpoint vaultEndpoint() {
        return VaultEndpoint.from(URI.create(System.getenv("VAULT_ADDR")));
    }
}
```

## Key Rules
- `BCryptPasswordEncoder(12)` — strength 12 is the minimum for production; never store plain or MD5 passwords
- Disable CSRF only for stateless REST APIs; always enable for session-based apps
- `@PreAuthorize` with SpEL is preferred over role checks in business logic
- Never log passwords, tokens, or PII — use structured logging with explicit field exclusions
- Validate all user input at the controller boundary with `@Valid` — never trust raw request data

