1
0
Files
spring-auth-demo/authorization-server/scripts/client-flow.sh
Ankur Mhatre 38c0a5f358 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.
2026-08-24 08:12:36 +05:30

111 lines
4.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Drives the real Spring OAuth2 client through a real browser flow with curl, so the
# behaviour is the client's own and not this script's.
#
# ./scripts/client-flow.sh <output-name> <label>
set -u
cd "$(dirname "$0")/.."
. scripts/lib.sh
CLIENT=http://127.0.0.1:8080
LAST_AUTHZ=""
NAME="${1:-as-client-flow}"
LABEL="${2:-default}"
OUT="../docs/output/${NAME}.txt"
mkdir -p ../docs/output
JAR=$(mktemp); trap 'rm -f "$JAR" /tmp/cf.html' EXIT
follow() { # url -> prints status + location, follows same-host redirects up to 6 hops
local url="$1" hop=0
while [ $hop -lt 8 ]; do
local hdrs status loc
# A browser sends Accept: text/html. curl's default is */*, and with the entry-point
# matcher configured to ignore */* that difference decides whether /oauth2/authorize
# redirects you to the login page or answers 401.
hdrs=$(curl -s -D - -o /tmp/cf.html -b "$JAR" -c "$JAR" -H 'Accept: text/html' "$url" | tr -d '\r')
status=$(echo "$hdrs" | head -1 | awk '{print $2}')
loc=$(echo "$hdrs" | grep -i '^location:' | head -1 | sed 's/^[Ll]ocation: *//')
echo " $status $url"
case "$url" in *"/oauth2/authorize?"*) LAST_AUTHZ="$url" ;; esac
[ -z "$loc" ] && { LAST_URL="$url"; return 0; }
case "$loc" in http*) url="$loc" ;; *) url="$(echo "$url" | grep -oE '^https?://[^/]+')$loc" ;; esac
hop=$((hop+1))
done
LAST_URL="$url"
}
{
section "The relying party drives the flow [$LABEL]"
echo "GET $CLIENT/orders while unauthenticated. Every hop below is a real redirect."
echo
follow "$CLIENT/orders"
echo
echo "The authorization request the client built:"
echo "$LAST_AUTHZ" | tr '&?' '\n\n' | sed 's/^/ /'
case "$LAST_AUTHZ" in
*code_challenge*) echo " >>> code_challenge IS present" ;;
*) echo " >>> NO code_challenge - a client registered with"
echo " >>> requireProofKey(true) will reject this outright" ;;
esac
case "$LAST_URL" in
"$AS/login"*)
echo
echo "Landed on the authorization server's login page. Submitting credentials:"
CSRF=$(form_value /tmp/cf.html _csrf)
HDRS=$(curl -s -D - -o /dev/null -b "$JAR" -c "$JAR" -H 'Accept: text/html' \
-d username=alice -d password=password -d "_csrf=$CSRF" \
"$AS/login" | tr -d '\r')
echo " $(echo "$HDRS" | head -1 | awk '{print $2}') POST $AS/login"
NEXT=$(echo "$HDRS" | grep -i '^location:' | head -1 | sed 's/^[Ll]ocation: *//')
echo
echo "Resuming the authorization request:"
follow "$NEXT"
;;
esac
# The consent page, if we reached it.
if grep -q 'name="scope"' /tmp/cf.html 2>/dev/null; then
echo
echo "Consent page reached. Approving:"
CSRF=$(form_value /tmp/cf.html _csrf)
STATE=$(form_value /tmp/cf.html state)
CID=$(form_value /tmp/cf.html client_id)
ARGS=(-d "client_id=$CID" -d "state=$STATE" -d "_csrf=$CSRF")
for s in $(form_values /tmp/cf.html scope); do ARGS+=(-d "scope=$s"); done
HDRS=$(curl -s -D - -o /dev/null -b "$JAR" -c "$JAR" -H 'Accept: text/html' "${ARGS[@]}" "$AS/oauth2/authorize" | tr -d '\r')
echo " $(echo "$HDRS" | head -1 | awk '{print $2}') POST $AS/oauth2/authorize"
NEXT=$(echo "$HDRS" | grep -i '^location:' | head -1 | sed 's/^[Ll]ocation: *//')
echo
echo "Back to the client with the code:"
follow "$NEXT"
fi
case "$LAST_URL" in
*error*)
echo
echo "The flow ended at the CLIENT's error page, not the provider's. The provider"
echo "rejected the authorization request and redirected the failure back to the"
echo "registered redirect_uri, so nothing in the client's logs names the provider"
echo "as the cause. The reason is only in the query string above."
;;
esac
section "What the client rendered"
if grep -qi 'error' /tmp/cf.html && ! grep -q 'orders' /tmp/cf.html; then
echo "An error page. The provider rejected the authorization request:"
echo "$LAST_URL" | tr '&?' '\n\n' | sed 's/^/ /'
echo
python3 -c "
import html,re,sys
t = re.sub(r'<[^>]+>', ' ', open('/tmp/cf.html', encoding='utf-8', errors='replace').read())
print(' '.join(html.unescape(t).split())[:600])"
else
python3 -c "
import html,re
t = re.sub(r'<[^>]+>', '\n', open('/tmp/cf.html', encoding='utf-8', errors='replace').read())
print('\n'.join(l.strip() for l in html.unescape(t).splitlines() if l.strip())[:1400])"
fi
} > "$OUT" 2>&1
sed -i 's/[[:space:]]*$//' "$OUT"
echo "wrote $OUT"