Recommended setup: treat the API as an OAuth2 resource server and let an identity provider (Keycloak, Auth0, Cognito, Okta) issue tokens.
Configuration (Spring Security 6):
- Add spring-boot-starter-oauth2-resource-server.
- Set spring.security.oauth2.resourceserver.jwt.issuer-uri so Spring fetches the signing keys (JWKS) and validates signature, expiry, issuer and audience.
- Define a SecurityFilterChain bean: stateless sessions, CSRF disabled for pure token APIs, authorizeHttpRequests rules per path, and oauth2ResourceServer(jwt()).
- Map token claims (scopes, roles) to authorities with a JwtAuthenticationConverter.
- Use method security (@PreAuthorize("hasAuthority('SCOPE_orders:write')")) for fine-grained checks, including ownership checks against the resource.
Good practices:
- Short-lived access tokens with refresh tokens handled by the client and identity provider.
- Asymmetric keys (RS256 or ES256), never a shared secret hard-coded in the service.
- Validate the audience so tokens meant for another API are rejected.
- Don't put sensitive data in the token; it is only encoded, not encrypted.
- Return 401 for missing or invalid tokens and 403 for insufficient permissions.
Rolling your own JWT filter is possible but rarely needed now.