Implementing OAuth2 Security with Swagger Annotations in Spring Boot
Define a global OAuth2 security scheme once, map it with @SecurityRequirement, and verify the spec and UI. This guide shows the exact annotations, a working config, and common pitfalls to avoid.
04 Aug 2025, 09:21 UTC

Why Use a Global OAuth2 Scheme?
When an API requires OAuth2, you normally need to annotate every @Operation or @ApiOperation with a security reference. That repetition is error‑prone and clutters the code. Springdoc‑OpenAPI lets you declare a single @SecurityScheme that the entire specification consumes. Once the scheme is defined, you can apply it at the controller or method level with @SecurityRequirement, and Swagger‑UI will render an "Authorize" button that drives the OAuth2 flow.
Defining the Security Scheme
Place the scheme declaration in a configuration class that is scanned by Springdoc. The annotation must appear before any operation annotations; otherwise the generated spec will omit the security section.
@Configuration
@SecurityScheme(
name = "oauth2",
type = SecuritySchemeType.OAUTH2,
flows = @OAuthFlows(
authorizationCode = @OAuthFlow(
authorizationUrl = "https://auth.example.com/oauth/authorize",
tokenUrl = "https://auth.example.com/oauth/token",
scopes = {
@OAuthScope(name = "read", description = "Read access"),
@OAuthScope(name = "write", description = "Write access")
}
)
)
)
public class OpenApiConfig {
// No additional beans are required for the spec itself
}
Key points:
nameis the identifier used in@SecurityRequirement.- Use
SecuritySchemeType.OAUTH2for OAuth2; other types (APIKEY, HTTP, etc.) are also supported. - The
authorizationUrlandtokenUrlmust be reachable from the browser that opens Swagger‑UI.
Mapping the Scheme to Endpoints
Apply @SecurityRequirement(name = "oauth2") to a controller class or individual methods. The annotation automatically injects a security array into the OpenAPI operation.
@RestController
@RequestMapping("/api/v1/items")
@SecurityRequirement(name = "oauth2")
public class ItemController {
@GetMapping
@Operation(summary = "List items")
public List<Item> list() { /* ... */ }
@PostMapping
@Operation(summary = "Create item")
@SecurityRequirement(name = "oauth2", scopes = {"write"})
public Item create(@RequestBody Item item) { /* ... */ }
}
Notice the method‑level override that restricts the write scope for the POST endpoint. If you omit scopes, the operation inherits all scopes defined in the scheme.
Example Controller
Below is a minimal controller that demonstrates both read and write operations with the OAuth2 scheme applied.
@RestController
@RequestMapping("/api/v1/users")
@SecurityRequirement(name = "oauth2")
public class UserController {
@GetMapping
@Operation(summary = "Get all users", description = "Requires read scope.")
public List<User> getAll() { return userService.findAll(); }
@PostMapping
@Operation(summary = "Create user", description = "Requires write scope.")
@SecurityRequirement(name = "oauth2", scopes = {"write"})
public User create(@RequestBody User user) { return userService.save(user); }
}
Verifying the Generated Spec
- Inspect the JSON: Open
/v3/api-docsin the browser. Search forcomponents.securitySchemes– it should contain anoauth2entry with the URLs and scopes you defined.- Each operation that has
@SecurityRequirementwill have asecurityarray referencing the scheme.
- Each operation that has
- Test Swagger‑UI: Navigate to
/swagger-ui.html(or/swagger-ui/index.htmldepending on your setup). The "Authorize" button should show an OAuth2 tab. Clicking it should open the authorization server’s consent screen.- After granting access, Swagger‑UI attaches the returned
access_tokento subsequent requests automatically.
- After granting access, Swagger‑UI attaches the returned
- Manual call: Use
curlor Postman. Example:
The request should succeed only if the token is present and has the required scope.curl -H "Authorization: Bearer <token>" http://localhost:8080/api/v1/users
Common Pitfalls and Limits
- Annotation Order: If
@SecuritySchemeappears after@Operationannotations, Springdoc ignores it. Always declare the scheme first. - Swagger‑2 vs OpenAPI‑3 Mix‑ups: Using legacy
@Apior@ApiOperationcan suppress thesecurityfield. Stick to OpenAPI 3 annotations fromio.swagger.v3.oas.annotations. - HTTPS with Self‑Signed Certs: Swagger‑UI runs in the browser; if the authorization server uses a self‑signed certificate, the browser may block the OAuth2 request. Either use a trusted cert or configure the browser to accept it.
- Class‑Level Overrides: Applying
@SecurityRequirementon a class overrides method‑level annotations. If a method is meant to be public, you must explicitly annotate it with@SecurityRequirement(name = "oauth2", scopes = {})or remove the class annotation. - Missing
tokenUrl: Without a token endpoint, the UI can’t exchange the code for a token, resulting in a 400 error. Verify the endpoint is reachable. - Scope Mis‑configuration: If the scopes in
@SecurityRequirementdon’t match those defined in the scheme, Swagger‑UI will still render but the authorization server will reject the request.
Conclusion
Defining a single OAuth2 @SecurityScheme and mapping it with @SecurityRequirement keeps your Spring Boot API documentation clean and functional. Verify the spec via /v3/api-docs and the UI, and watch out for the common ordering and configuration mistakes highlighted above. Once in place, Swagger‑UI becomes a powerful tool for developers to test secured endpoints directly from the browser.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.