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.
72 lines
2.9 KiB
HTML
72 lines
2.9 KiB
HTML
<!DOCTYPE html>
|
|
<html xmlns:th="http://www.thymeleaf.org">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Approve access</title>
|
|
<style>
|
|
body { font-family: system-ui, sans-serif; max-width: 34rem; margin: 4rem auto; color: #222; }
|
|
.card { border: 1px solid #e2e5ea; border-radius: 8px; padding: 1.5rem 1.75rem; }
|
|
.scope { display: block; margin: .4rem 0; }
|
|
code { background: #f4f5f7; padding: .1rem .3rem; border-radius: 3px; }
|
|
button { padding: .5rem 1rem; border-radius: 6px; border: 1px solid #b7bec9; cursor: pointer; }
|
|
.primary { background: #2f6fdb; color: #fff; border-color: #2f6fdb; }
|
|
.muted { color: #667; font-size: .9rem; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="card">
|
|
<h2>Approve access</h2>
|
|
<p>
|
|
Signed in as <b th:text="${principalName}">user</b>.
|
|
The application <code th:text="${clientId}">client</code> wants to act on your behalf.
|
|
</p>
|
|
|
|
<!--
|
|
The form POSTs to /oauth2/authorize, NOT to /oauth2/consent. The authorization
|
|
endpoint owns both halves of the exchange; the consent page is only a renderer.
|
|
-->
|
|
<form method="post" th:action="@{${requestURI}}">
|
|
<!-- Echoed back verbatim. Drop `state` and the endpoint cannot correlate this
|
|
approval with the pending authorization request, and you get a redirect loop. -->
|
|
<input type="hidden" name="client_id" th:value="${clientId}">
|
|
<input type="hidden" name="state" th:value="${state}">
|
|
<input type="hidden" name="user_code" th:value="${userCode}" th:if="${userCode}">
|
|
|
|
<p><b>This application will be able to:</b></p>
|
|
<label class="scope" th:each="s : ${scopes}">
|
|
<!-- One `scope` parameter per approved scope. A single space-joined value is
|
|
accepted by the parameter binder and then silently treated as one unknown
|
|
scope name, so consent appears to succeed and the token comes back short. -->
|
|
<input type="checkbox" name="scope" th:value="${s}" checked>
|
|
<code th:text="${s}">scope</code>
|
|
</label>
|
|
|
|
<div th:if="${!previouslyApprovedScopes.isEmpty()}">
|
|
<p class="muted">Already approved previously:</p>
|
|
<label class="scope" th:each="s : ${previouslyApprovedScopes}">
|
|
<input type="checkbox" disabled checked>
|
|
<code th:text="${s}">scope</code>
|
|
</label>
|
|
</div>
|
|
|
|
<p class="muted">
|
|
<code>openid</code> is requested implicitly and is not listed — the
|
|
authorization server never asks for consent on it.
|
|
</p>
|
|
|
|
<p>
|
|
<button class="primary" type="submit" id="approve">Approve</button>
|
|
</p>
|
|
</form>
|
|
|
|
<form method="post" th:action="@{${requestURI}}">
|
|
<input type="hidden" name="client_id" th:value="${clientId}">
|
|
<input type="hidden" name="state" th:value="${state}">
|
|
<!-- No `scope` parameters at all is how you say "denied". The endpoint then redirects
|
|
back to the client with error=access_denied. -->
|
|
<button type="submit" id="deny">Deny</button>
|
|
</form>
|
|
</div>
|
|
</body>
|
|
</html>
|