1
0

Add Spring Authorization Server project: OAuth2/OIDC provider, client and resource server

Three modules on Spring Boot 4.1.1 with Spring Authorization Server 7.1.1: the provider
itself, a relying party, and an API that trusts its tokens. Client registration, PKCE,
a custom consent page and token customisation, with profiles that make each failure
reproducible.

Every claim is backed by captured output in docs/output/as-*.txt, regenerated by
authorization-server/scripts/run-all.sh. Notable findings, verified against the jars:

  - OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(HttpSecurity) was deleted
    in 7.0, and both configuration classes moved into spring-security-config
  - ClientSettings.requireProofKey flipped from false to true, on the authorization server
    (1.5.8 -> 7.1.1) and on the OAuth2 client (6.5.1 -> 7.1.1)
  - requireProofKey(false) does not make PKCE optional for a public client; the code
    verifier is that client's only authentication at the token endpoint
  - MediaTypeRequestMatcher(TEXT_HTML) matches Accept: */*, so the token endpoint answers
    API callers with 302 -> /login unless setIgnoredMediaTypes(ALL) is called

Also renames the repository to spring-auth-demo and cross-links the new chapter set from
the existing documentation.
This commit is contained in:
2026-08-24 08:12:36 +05:30
parent 4dc45d5e00
commit 38c0a5f358
89 changed files with 5234 additions and 9 deletions

View File

@@ -0,0 +1,240 @@
------------------------------------------------------------------
== OpenID Connect discovery: GET /.well-known/openid-configuration
------------------------------------------------------------------
$ curl -s http://localhost:9000/.well-known/openid-configuration
{
"issuer": "http://localhost:9000",
"authorization_endpoint": "http://localhost:9000/oauth2/authorize",
"token_endpoint": "http://localhost:9000/oauth2/token",
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt",
"tls_client_auth",
"self_signed_tls_client_auth"
],
"jwks_uri": "http://localhost:9000/oauth2/jwks",
"userinfo_endpoint": "http://localhost:9000/userinfo",
"end_session_endpoint": "http://localhost:9000/connect/logout",
"response_types_supported": [
"code"
],
"grant_types_supported": [
"authorization_code",
"client_credentials",
"refresh_token",
"urn:ietf:params:oauth:grant-type:token-exchange"
],
"revocation_endpoint": "http://localhost:9000/oauth2/revoke",
"revocation_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt",
"tls_client_auth",
"self_signed_tls_client_auth"
],
"introspection_endpoint": "http://localhost:9000/oauth2/introspect",
"introspection_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt",
"tls_client_auth",
"self_signed_tls_client_auth"
],
"code_challenge_methods_supported": [
"S256"
],
"tls_client_certificate_bound_access_tokens": true,
"dpop_signing_alg_values_supported": [
"RS256",
"RS384",
"RS512",
"PS256",
"PS384",
"PS512",
"ES256",
"ES384",
"ES512"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"scopes_supported": [
"openid"
]
}
------------------------------------------------------------------
== OAuth2 metadata: GET /.well-known/oauth-authorization-server
------------------------------------------------------------------
Present even with .oidc(...) switched off. The OIDC document above is the one
that additionally advertises userinfo_endpoint and id_token signing algorithms.
$ curl -s http://localhost:9000/.well-known/oauth-authorization-server
{
"issuer": "http://localhost:9000",
"authorization_endpoint": "http://localhost:9000/oauth2/authorize",
"token_endpoint": "http://localhost:9000/oauth2/token",
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt",
"tls_client_auth",
"self_signed_tls_client_auth"
],
"jwks_uri": "http://localhost:9000/oauth2/jwks",
"response_types_supported": [
"code"
],
"grant_types_supported": [
"authorization_code",
"client_credentials",
"refresh_token",
"urn:ietf:params:oauth:grant-type:token-exchange"
],
"revocation_endpoint": "http://localhost:9000/oauth2/revoke",
"revocation_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt",
"tls_client_auth",
"self_signed_tls_client_auth"
],
"introspection_endpoint": "http://localhost:9000/oauth2/introspect",
"introspection_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt",
"tls_client_auth",
"self_signed_tls_client_auth"
],
"code_challenge_methods_supported": [
"S256"
],
"tls_client_certificate_bound_access_tokens": true,
"dpop_signing_alg_values_supported": [
"RS256",
"RS384",
"RS512",
"PS256",
"PS384",
"PS512",
"ES256",
"ES384",
"ES512"
]
}
------------------------------------------------------------------
== JWK Set: GET /oauth2/jwks
------------------------------------------------------------------
Public keys only. No 'd' member - if you ever see one here, stop the server.
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"kid": "262bd550-3657-46c4-bafc-cce4c6f4e0cb",
"n": "pFCIstpVnGQm7Mp8bskE_-_Rz-oI6mPaiyQLiWMBuVip4fkKYwapZVbsZf9fmy1w1KXWIdtXOhe-fqa3-KqYzsrje-o2u6_D27rqR1Z0I9ezbDfw2A4Gsj5AlmnXzWMLnNMzSueSf8luRk04MHO4bGzXLqQ1gOltnqMkMAQzhCqWFZmKxNJeaB4FlXOtxqzcso0eeKsPzRjZvTamgU5TvGUZmQ4LTKTkoYzB3kjvCspVpZAbdVR01RlmzfTJB0tVIc0ioMk1YZHUx27TPN1W8Nw1AaAYmV9URaf2fgz2Ja3y_Lj8hmXuQAcPOGBmoVWX0QkW4DTSAPW0xiPHqpjw9Q"
}
]
}
------------------------------------------------------------------
== Resolved endpoint settings, read back from AuthorizationServerSettings
------------------------------------------------------------------
{
"settings.authorization-server.client-registration-endpoint": "/oauth2/register",
"settings.authorization-server.authorization-endpoint": "/oauth2/authorize",
"settings.authorization-server.token-endpoint": "/oauth2/token",
"settings.authorization-server.device-verification-endpoint": "/oauth2/device_verification",
"settings.authorization-server.oidc-user-info-endpoint": "/userinfo",
"settings.authorization-server.pushed-authorization-request-endpoint": "/oauth2/par",
"settings.authorization-server.oidc-client-registration-endpoint": "/connect/register",
"settings.authorization-server.oidc-logout-endpoint": "/connect/logout",
"settings.authorization-server.issuer": "http://localhost:9000",
"settings.authorization-server.multiple-issuers-allowed": false,
"settings.authorization-server.device-authorization-endpoint": "/oauth2/device_authorization",
"settings.authorization-server.jwk-set-endpoint": "/oauth2/jwks",
"settings.authorization-server.token-revocation-endpoint": "/oauth2/revoke",
"settings.authorization-server.token-introspection-endpoint": "/oauth2/introspect"
}
------------------------------------------------------------------
== Registered clients, as the server actually holds them
------------------------------------------------------------------
[
{
"clientId": "demo-web",
"authenticationMethods": [
"client_secret_basic"
],
"grantTypes": [
"refresh_token",
"authorization_code"
],
"redirectUris": [
"http://127.0.0.1:8080/login/oauth2/code/demo-web"
],
"scopes": [
"orders.write",
"openid",
"profile",
"orders.read"
],
"requireProofKey": true,
"requireAuthorizationConsent": true,
"accessTokenFormat": "self-contained",
"accessTokenTtlSeconds": 300,
"reuseRefreshTokens": false
},
{
"clientId": "demo-spa",
"authenticationMethods": [
"none"
],
"grantTypes": [
"refresh_token",
"authorization_code"
],
"redirectUris": [
"http://127.0.0.1:8080/authorized"
],
"scopes": [
"openid",
"orders.read"
],
"requireProofKey": true,
"requireAuthorizationConsent": true,
"accessTokenFormat": "self-contained",
"accessTokenTtlSeconds": 300,
"reuseRefreshTokens": true
},
{
"clientId": "demo-service",
"authenticationMethods": [
"client_secret_basic"
],
"grantTypes": [
"client_credentials"
],
"redirectUris": [],
"scopes": [
"orders.read"
],
"requireProofKey": true,
"requireAuthorizationConsent": false,
"accessTokenFormat": "self-contained",
"accessTokenTtlSeconds": 600,
"reuseRefreshTokens": true
}
]