Add the cors-csrf module
This commit is contained in:
25
cors-csrf/scripts/preflight.sh
Executable file
25
cors-csrf/scripts/preflight.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# Send one CORS preflight and print the status line and the headers that decide the outcome.
|
||||
#
|
||||
# ./scripts/preflight.sh https://spa.example.com POST /api/data
|
||||
#
|
||||
# A preflight is not a special kind of request. It is an OPTIONS carrying Origin and
|
||||
# Access-Control-Request-Method, and it is sent WITHOUT cookies or an Authorization header -
|
||||
# which is precisely why a chain that requires authentication rejects it.
|
||||
set -eu
|
||||
ORIGIN="${1:-https://spa.example.com}"
|
||||
METHOD="${2:-POST}"
|
||||
PATH_="${3:-/api/data}"
|
||||
|
||||
echo "\$ curl -s -i -X OPTIONS http://localhost:8080$PATH_ \\"
|
||||
echo " -H 'Origin: $ORIGIN' \\"
|
||||
echo " -H 'Access-Control-Request-Method: $METHOD' \\"
|
||||
echo " -H 'Access-Control-Request-Headers: content-type,x-xsrf-token'"
|
||||
echo
|
||||
curl -s -i -X OPTIONS "http://localhost:8080$PATH_" \
|
||||
-H "Origin: $ORIGIN" \
|
||||
-H "Access-Control-Request-Method: $METHOD" \
|
||||
-H "Access-Control-Request-Headers: content-type,x-xsrf-token" \
|
||||
| sed -n '1,/^\r$/p' \
|
||||
| grep -viE '^(date|keep-alive|connection|content-length|transfer-encoding):' \
|
||||
| sed 's/\r$//'
|
||||
355
cors-csrf/scripts/run-all.sh
Executable file
355
cors-csrf/scripts/run-all.sh
Executable file
@@ -0,0 +1,355 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerates every file under docs/output/ from a real run. Nothing in docs/output/ is
|
||||
# hand-written; if a number in the article disagrees with a file here, the file is right.
|
||||
#
|
||||
# ./scripts/run-all.sh
|
||||
#
|
||||
# Takes a few minutes: the application restarts once per scenario, because the scenarios are
|
||||
# Spring profiles and profiles are fixed at context startup.
|
||||
set -eu
|
||||
cd "$(dirname "$0")/.."
|
||||
OUT=docs/output
|
||||
mkdir -p "$OUT"
|
||||
|
||||
hdr() { printf '%s\n%s\n%s\n\n' "$(printf '=%.0s' $(seq 1 78))" "$1" "$(printf '=%.0s' $(seq 1 78))"; }
|
||||
|
||||
# Strip run-to-run noise so committed files diff cleanly.
|
||||
scrub() {
|
||||
sed -E \
|
||||
-e 's/\r$//' \
|
||||
-e 's/[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9:.]+(Z|\+[0-9:]+)?/<timestamp>/g' \
|
||||
-e 's/(JSESSIONID=)[0-9A-F]+/\1<session>/g' \
|
||||
-e 's/(XSRF-TOKEN=|MY-CSRF=)[0-9a-f-]{36}/\1<token>/g' \
|
||||
-e 's/(X-XSRF-TOKEN: |X-CSRF-TOKEN: )[0-9a-f-]{36}/\1<token>/g' \
|
||||
-e '/^(Date|Keep-Alive|Connection|Content-Length|Transfer-Encoding|Expires):/d' \
|
||||
-e 's/PID [0-9]+/PID <pid>/g' \
|
||||
-e 's/in [0-9.]+ seconds \(process running for [0-9.]+\)/in <n> seconds/g' \
|
||||
-e 's/ [0-9]+ --- / <pid> --- /g' \
|
||||
-e 's/\[nio-8080-exec-[0-9]+\]/[nio-8080-exec-N]/g' \
|
||||
-e '/Picked up JAVA_TOOL_OPTIONS/d' \
|
||||
| cat -s
|
||||
}
|
||||
|
||||
headers() { # headers <curl args...>
|
||||
curl -s -i "$@" | sed -n '1,/^\r$/p' | grep -viE '^(date|keep-alive|connection|content-length|transfer-encoding|content-type|content-language):'
|
||||
}
|
||||
|
||||
logs_since() { # logs_since <marker-line-count> <grep-pattern>
|
||||
sed -n "$(( $1 + 1 )),\$p" /tmp/cors-csrf-app.log | grep -E "$2" || true
|
||||
}
|
||||
|
||||
########################################################################################
|
||||
# 1. CORS on the MVC layer only - the preflight never reaches the servlet
|
||||
########################################################################################
|
||||
./scripts/run.sh mvconly > /dev/null
|
||||
{
|
||||
hdr "docs/output/01-mvc-only.txt
|
||||
CORS configured with WebMvcConfigurer.addCorsMappings and nothing else.
|
||||
Profile: mvconly"
|
||||
echo "# The security chain. Note what is NOT in it."
|
||||
echo "\$ curl -s localhost:8080/diag/chain"
|
||||
curl -s localhost:8080/diag/chain | python3 -m json.tool
|
||||
echo
|
||||
echo "# CorsConfigurationSource beans in the context."
|
||||
echo "\$ curl -s localhost:8080/diag/cors-sources"
|
||||
curl -s localhost:8080/diag/cors-sources | python3 -m json.tool
|
||||
echo
|
||||
./scripts/preflight.sh
|
||||
echo
|
||||
echo "# The MVC CORS mapping is real - it just never runs, because the request is"
|
||||
echo "# rejected at AuthorizationFilter (order 4200) and the DispatcherServlet is"
|
||||
echo "# downstream of the entire filter chain."
|
||||
} | scrub > "$OUT/01-mvc-only.txt"
|
||||
|
||||
########################################################################################
|
||||
# 2. The same MVC configuration, with .cors(withDefaults()) added
|
||||
########################################################################################
|
||||
./scripts/run.sh mvcbridge > /dev/null
|
||||
{
|
||||
hdr "docs/output/02-mvc-bridge.txt
|
||||
The identical MVC CORS mapping plus one line: .cors(Customizer.withDefaults()).
|
||||
Profile: mvcbridge"
|
||||
echo "\$ curl -s localhost:8080/diag/chain"
|
||||
curl -s localhost:8080/diag/chain | python3 -m json.tool
|
||||
echo
|
||||
./scripts/preflight.sh
|
||||
echo
|
||||
echo "# CorsFilter is now in the chain at order 1000, between HeaderWriterFilter (900)"
|
||||
echo "# and CsrfFilter (1100), and it short-circuits the preflight before authorization"
|
||||
echo "# ever sees it. Note Access-Control-Max-Age: 1800 - that default comes from MVC's"
|
||||
echo "# CorsRegistration, not from CorsConfiguration."
|
||||
} | scrub > "$OUT/02-mvc-bridge.txt"
|
||||
|
||||
########################################################################################
|
||||
# 3. A CorsConfigurationSource bean, correctly named
|
||||
########################################################################################
|
||||
./scripts/run.sh securitysource > /dev/null
|
||||
{
|
||||
hdr "docs/output/03-security-source.txt
|
||||
A @Bean named corsConfigurationSource. .cors(..) is never called - it is applied for us.
|
||||
Profile: securitysource"
|
||||
echo "\$ curl -s localhost:8080/diag/cors-sources"
|
||||
curl -s localhost:8080/diag/cors-sources | python3 -m json.tool
|
||||
echo
|
||||
./scripts/preflight.sh
|
||||
echo
|
||||
echo "# Compare with 02: there is no Access-Control-Max-Age here. CorsConfiguration"
|
||||
echo "# leaves maxAge null, so every single cross-origin call re-runs the preflight."
|
||||
} | scrub > "$OUT/03-security-source.txt"
|
||||
|
||||
########################################################################################
|
||||
# 4. Three rejections that look identical from the client
|
||||
########################################################################################
|
||||
{
|
||||
hdr "docs/output/04-three-identical-403s.txt
|
||||
Origin not allowed, method not allowed, header not allowed. One status, one shape.
|
||||
Profile: securitysource, CORS_LOG_LEVEL=DEBUG"
|
||||
} > "$OUT/04-three-identical-403s.txt"
|
||||
CORS_LOG_LEVEL=DEBUG ./scripts/run.sh securitysource > /dev/null
|
||||
MARK=$(wc -l < /tmp/cors-csrf-app.log)
|
||||
{
|
||||
echo "# 1. disallowed origin"
|
||||
headers -X OPTIONS localhost:8080/api/data -H 'Origin: https://evil.example.com' -H 'Access-Control-Request-Method: POST'
|
||||
echo "# 2. disallowed method"
|
||||
headers -X OPTIONS localhost:8080/api/data -H 'Origin: https://spa.example.com' -H 'Access-Control-Request-Method: DELETE'
|
||||
echo "# 3. disallowed request header"
|
||||
headers -X OPTIONS localhost:8080/api/data -H 'Origin: https://spa.example.com' -H 'Access-Control-Request-Method: POST' -H 'Access-Control-Request-Headers: authorization'
|
||||
echo "# body of a rejected preflight:"
|
||||
curl -s -X OPTIONS localhost:8080/api/data -H 'Origin: https://evil.example.com' -H 'Access-Control-Request-Method: POST'
|
||||
echo
|
||||
echo
|
||||
echo "# The only thing that distinguishes them is a DEBUG line from DefaultCorsProcessor:"
|
||||
sleep 1
|
||||
logs_since "$MARK" 'DefaultCorsProcessor'
|
||||
} | scrub >> "$OUT/04-three-identical-403s.txt"
|
||||
|
||||
########################################################################################
|
||||
# 5. The bean-name trap: right type, wrong name
|
||||
########################################################################################
|
||||
CORS_LOG_LEVEL=DEBUG ./scripts/run.sh misnamed > /dev/null
|
||||
MARK=$(wc -l < /tmp/cors-csrf-app.log)
|
||||
{
|
||||
hdr "docs/output/05-misnamed-bean.txt
|
||||
The same UrlBasedCorsConfigurationSource bean, named apiCorsSource instead of
|
||||
corsConfigurationSource. It starts. The preflight returns 200. It carries no CORS headers.
|
||||
Profile: misnamed"
|
||||
echo "\$ curl -s localhost:8080/diag/cors-sources"
|
||||
curl -s localhost:8080/diag/cors-sources | python3 -m json.tool
|
||||
echo
|
||||
./scripts/preflight.sh
|
||||
echo
|
||||
sleep 1
|
||||
logs_since "$MARK" 'DefaultCorsProcessor'
|
||||
echo
|
||||
echo "# Two different lookups. HttpSecurityConfiguration.applyCorsIfAvailable asks"
|
||||
echo "# getBeanNamesForType(UrlBasedCorsConfigurationSource.class) and enables CORS if the"
|
||||
echo "# array is non-empty, so the bean above DID switch the configurer on."
|
||||
echo "# CorsConfigurer.getCorsConfigurationSource then asks"
|
||||
echo "# containsBeanDefinition(\"corsConfigurationSource\"), which is false, and falls back"
|
||||
echo "# to Spring MVC's registrations - of which there are none."
|
||||
echo "# CorsFilter returns from every preflight whether or not it found a configuration:"
|
||||
echo "# if (!isValid || CorsUtils.isPreFlightRequest(request)) { return; }"
|
||||
echo "# so the OPTIONS never reaches AuthorizationFilter and the client gets a bare 200."
|
||||
} | scrub > "$OUT/05-misnamed-bean.txt"
|
||||
|
||||
########################################################################################
|
||||
# 6. Two sources - the documentation says CORS is not configured. It is.
|
||||
########################################################################################
|
||||
CORS_LOG_LEVEL=DEBUG ./scripts/run.sh twosources > /dev/null
|
||||
MARK=$(wc -l < /tmp/cors-csrf-app.log)
|
||||
{
|
||||
hdr "docs/output/06-two-sources.txt
|
||||
Two UrlBasedCorsConfigurationSource beans. The reference documentation says Spring Security
|
||||
'won't automatically configure CORS support for you, because it cannot decide which one to
|
||||
use'. In 7.1.1 it configures it, and the bean NAME decides.
|
||||
Profile: twosources"
|
||||
curl -s localhost:8080/diag/cors-sources | python3 -m json.tool
|
||||
echo
|
||||
echo "# the origin allowed by the bean named corsConfigurationSource:"
|
||||
headers -X OPTIONS localhost:8080/api/data -H 'Origin: https://spa.example.com' -H 'Access-Control-Request-Method: POST' -H 'Access-Control-Request-Headers: content-type'
|
||||
echo "# the origin allowed by adminCorsSource, which is never consulted:"
|
||||
headers -X OPTIONS localhost:8080/api/data -H 'Origin: https://admin.example.com' -H 'Access-Control-Request-Method: POST' -H 'Access-Control-Request-Headers: content-type'
|
||||
sleep 1
|
||||
logs_since "$MARK" 'DefaultCorsProcessor'
|
||||
} | scrub > "$OUT/06-two-sources.txt"
|
||||
|
||||
########################################################################################
|
||||
# 7. allowedOrigins("*") with allowCredentials(true)
|
||||
########################################################################################
|
||||
./scripts/run.sh wildcard > /dev/null
|
||||
MARK=$(wc -l < /tmp/cors-csrf-app.log)
|
||||
{
|
||||
hdr "docs/output/07-wildcard-credentials.txt
|
||||
allowedOrigins(\"*\") together with allowCredentials(true). Legal to configure, illegal to
|
||||
serve. The failure is thrown on the request, not at startup - and it does not surface as a 500.
|
||||
Profile: wildcard"
|
||||
headers -X OPTIONS localhost:8080/api/data -H 'Origin: https://spa.example.com' -H 'Access-Control-Request-Method: POST'
|
||||
echo "# and a plain authenticated GET, with correct credentials:"
|
||||
headers -u alice:password localhost:8080/api/data -H 'Origin: https://spa.example.com'
|
||||
echo
|
||||
sleep 1
|
||||
logs_since "$MARK" 'IllegalArgumentException: When allowCredentials|at org.springframework.web.cors' | head -5
|
||||
echo
|
||||
echo "# 401, not 500. The exception escapes CorsFilter, Tomcat re-dispatches to /error,"
|
||||
echo "# the security chain runs again on that dispatch without re-reading the credential,"
|
||||
echo "# and the anonymous second pass is what answers."
|
||||
} | scrub > "$OUT/07-wildcard-credentials.txt"
|
||||
|
||||
########################################################################################
|
||||
# 8. CSRF for a SPA: the pre-6.0 recipe
|
||||
########################################################################################
|
||||
CSRF_LOG_LEVEL=DEBUG ./scripts/run.sh csrfnaive > /dev/null
|
||||
MARK=$(wc -l < /tmp/cors-csrf-app.log)
|
||||
J=$(mktemp); rm -f "$J"
|
||||
{
|
||||
hdr "docs/output/08-csrf-naive.txt
|
||||
CookieCsrfTokenRepository.withHttpOnlyFalse() on its own - the recipe from every pre-6.0
|
||||
tutorial. Three separate things go wrong.
|
||||
Profile: csrfnaive"
|
||||
echo "# 1. The bootstrap GET. A SPA expects an XSRF-TOKEN cookie here."
|
||||
headers -c "$J" -u alice:password localhost:8080/api/data
|
||||
echo "# cookie jar after the GET:"
|
||||
{ grep -v '^#' "$J" | sed 's/\t/ | /g' | grep . || echo "(empty - no cookie was set)"; }
|
||||
echo
|
||||
echo "# 2. POST with no token."
|
||||
headers -b "$J" -c "$J" -u alice:password -X POST localhost:8080/api/data -H 'Content-Type: application/json' -d '{}'
|
||||
echo "# cookie jar now:"
|
||||
grep -v '^#' "$J" | sed 's/\t/ | /g'
|
||||
echo
|
||||
echo "# 3. POST echoing the raw cookie value back in X-XSRF-TOKEN, which is what every"
|
||||
echo "# SPA snippet on the internet does."
|
||||
TOK=$(grep XSRF-TOKEN "$J" | awk '{print $NF}')
|
||||
headers -b "$J" -u alice:password -X POST localhost:8080/api/data -H "X-XSRF-TOKEN: $TOK" -H 'Content-Type: application/json' -d '{}'
|
||||
echo
|
||||
sleep 1
|
||||
logs_since "$MARK" 'CsrfFilter'
|
||||
} | scrub > "$OUT/08-csrf-naive.txt"
|
||||
|
||||
########################################################################################
|
||||
# 9. The same failure with /error permitted - the status the SPA never sees
|
||||
########################################################################################
|
||||
CSRF_LOG_LEVEL=DEBUG ./scripts/run.sh csrfnaive,errorpermit > /dev/null
|
||||
{
|
||||
hdr "docs/output/09-error-dispatch.txt
|
||||
The identical CSRF failure, with one extra filter chain that permits /error.
|
||||
Profile: csrfnaive,errorpermit"
|
||||
headers -u alice:password -X POST localhost:8080/api/data -H 'Content-Type: application/json' -d '{}'
|
||||
curl -s -u alice:password -X POST localhost:8080/api/data -H 'Content-Type: application/json' -d '{}'
|
||||
echo
|
||||
echo
|
||||
echo "# 403, and a body. Without the /error chain the same request answers 401 with an"
|
||||
echo "# empty body and a WWW-Authenticate header - see 08. AccessDeniedHandlerImpl calls"
|
||||
echo "# response.sendError(403), the container re-dispatches to /error, and the security"
|
||||
echo "# chain runs a second time on that dispatch. BasicAuthenticationFilter extends"
|
||||
echo "# OncePerRequestFilter and skips error dispatches, so the second pass is anonymous"
|
||||
echo "# and AuthorizationFilter answers 401 over the top of the 403."
|
||||
} | scrub > "$OUT/09-error-dispatch.txt"
|
||||
|
||||
########################################################################################
|
||||
# 10. csrf.spa()
|
||||
########################################################################################
|
||||
./scripts/run.sh csrfspa > /dev/null
|
||||
J=$(mktemp); rm -f "$J"
|
||||
{
|
||||
hdr "docs/output/10-csrf-spa.txt
|
||||
The same flow under csrf.spa(), added in Spring Security 7.0.
|
||||
Profile: csrfspa"
|
||||
echo "# 1. The bootstrap GET now DOES set the cookie."
|
||||
headers -c "$J" -u alice:password localhost:8080/api/data
|
||||
echo
|
||||
echo "# 2. POST with no token still fails, as it must."
|
||||
headers -b "$J" -u alice:password -X POST localhost:8080/api/data -H 'Content-Type: application/json' -d '{}'
|
||||
echo
|
||||
echo "# 3. POST echoing the raw cookie value in X-XSRF-TOKEN."
|
||||
TOK=$(grep XSRF-TOKEN "$J" | awk '{print $NF}')
|
||||
headers -b "$J" -u alice:password -X POST localhost:8080/api/data -H "X-XSRF-TOKEN: $TOK" -H 'Content-Type: application/json' -d '{}'
|
||||
curl -s -b "$J" -u alice:password -X POST localhost:8080/api/data -H "X-XSRF-TOKEN: $TOK" -H 'Content-Type: application/json' -d '{}'
|
||||
echo
|
||||
echo
|
||||
echo "# Note the cookie attributes: Path=/ and nothing else. No SameSite, no Secure,"
|
||||
echo "# no HttpOnly. A cookie with no SameSite attribute is treated as Lax, so a"
|
||||
echo "# genuinely cross-site SPA still never receives it. See 12."
|
||||
} | scrub > "$OUT/10-csrf-spa.txt"
|
||||
|
||||
########################################################################################
|
||||
# 11. spa() discards a repository configured before it
|
||||
########################################################################################
|
||||
./scripts/run.sh spaorder > /dev/null
|
||||
J=$(mktemp); rm -f "$J"
|
||||
{
|
||||
hdr "docs/output/11-spa-ordering.txt
|
||||
.csrf(c -> c.csrfTokenRepository(custom).spa()) - the custom repository asked for a cookie
|
||||
named MY-CSRF and a header named X-CSRF-TOKEN. Neither reaches the running application.
|
||||
Profile: spaorder"
|
||||
headers -c "$J" -u alice:password localhost:8080/api/data
|
||||
echo "# cookie jar:"
|
||||
grep -v '^#' "$J" | sed 's/\t/ | /g'
|
||||
echo
|
||||
echo "# spa() assigns csrfTokenRepository and requestHandler unconditionally; it is not a"
|
||||
echo "# 'defaults if unset' method. Swap the two calls and MY-CSRF appears."
|
||||
} | scrub > "$OUT/11-spa-ordering.txt"
|
||||
|
||||
########################################################################################
|
||||
# 12. SameSite - what is actually written, and what a browser does with it
|
||||
########################################################################################
|
||||
{
|
||||
hdr "docs/output/12-samesite.txt
|
||||
The Set-Cookie headers this application emits under four configurations, and what
|
||||
SpecCookieJar - a model of RFC 6265bis 5.5 and 5.8.3 - does with them."
|
||||
} > "$OUT/12-samesite.txt"
|
||||
|
||||
emit() { # emit <label> <env...>
|
||||
local label="$1"; shift
|
||||
env "$@" ./scripts/run.sh "$PROFILE" > /dev/null
|
||||
echo "## $label"
|
||||
curl -s -D- -o /dev/null -u alice:password localhost:8080/api/data | grep -i '^set-cookie' | sed 's/\r$//'
|
||||
curl -s -D- -o /dev/null localhost:8080/api/data | grep -i '^set-cookie' | grep -i jsessionid | sed 's/\r$//' || true
|
||||
echo
|
||||
}
|
||||
{
|
||||
PROFILE=csrfspa
|
||||
emit "csrf.spa() defaults, session cookie left at same-site=lax" SESSION_SAME_SITE=lax SESSION_SECURE=false
|
||||
emit "session cookie set to same-site=none, secure=false" SESSION_SAME_SITE=none SESSION_SECURE=false
|
||||
PROFILE=crosssite
|
||||
emit "crosssite profile: SameSite=None and Secure on both cookies" SESSION_SAME_SITE=none SESSION_SECURE=true
|
||||
emit "crosssite profile with -DOMIT_SECURE=true" JVM_ARGS=-DOMIT_SECURE=true SESSION_SAME_SITE=none SESSION_SECURE=false
|
||||
} | scrub >> "$OUT/12-samesite.txt"
|
||||
|
||||
./scripts/run.sh csrfspa > /dev/null
|
||||
{
|
||||
echo "## The same headers, run through SpecCookieJar"
|
||||
python3 - <<'PY'
|
||||
import urllib.parse, urllib.request, json
|
||||
headers = [
|
||||
"JSESSIONID=s1; Path=/; HttpOnly; SameSite=Lax",
|
||||
"JSESSIONID=s2; Path=/; HttpOnly; SameSite=None",
|
||||
"JSESSIONID=s3; Path=/; Secure; HttpOnly; SameSite=None",
|
||||
"XSRF-TOKEN=t1; Path=/",
|
||||
"XSRF-TOKEN=t2; Path=/; SameSite=None",
|
||||
"XSRF-TOKEN=t3; Path=/; Secure; SameSite=None",
|
||||
]
|
||||
query = "&".join("h=" + urllib.parse.quote(h) for h in headers)
|
||||
for secure in ("false", "true"):
|
||||
url = f"http://localhost:8080/diag/cookie-spec?{query}&secure={secure}"
|
||||
print(json.dumps(json.load(urllib.request.urlopen(url)), indent=2))
|
||||
print()
|
||||
PY
|
||||
echo "# Read the second block first: over a trustworthy origin, the only two of the six"
|
||||
echo "# that reach a cross-site fetch are the two carrying Secure AND SameSite=None."
|
||||
echo "# Then read the first: over plain http, none do -"
|
||||
echo "# which is why a cross-site SPA cannot be developed against http://127.0.0.1."
|
||||
echo "# (http://localhost itself is treated as trustworthy by current browsers; a bare IP"
|
||||
echo "# is not.)"
|
||||
} | scrub >> "$OUT/12-samesite.txt"
|
||||
|
||||
########################################################################################
|
||||
# 13. The assertions
|
||||
########################################################################################
|
||||
{
|
||||
hdr "docs/output/13-tests.txt
|
||||
mvn -B test"
|
||||
(cd . && mvn -B test 2>&1) | grep -E 'Tests run|ERROR|BUILD|CorsCsrf' | head -30
|
||||
} | scrub > "$OUT/13-tests.txt"
|
||||
|
||||
./scripts/stop.sh
|
||||
echo "regenerated $(ls "$OUT" | wc -l) files under $OUT"
|
||||
37
cors-csrf/scripts/run.sh
Executable file
37
cors-csrf/scripts/run.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# Start the demo application under a given profile and wait until it answers.
|
||||
#
|
||||
# ./scripts/run.sh securitysource
|
||||
# ./scripts/run.sh mvconly
|
||||
# SESSION_SAME_SITE=none SESSION_SECURE=false ./scripts/run.sh crosssite
|
||||
# JVM_ARGS=-DOMIT_SECURE=true ./scripts/run.sh crosssite
|
||||
# CSRF_LOG_LEVEL=DEBUG ./scripts/run.sh csrfnaive
|
||||
#
|
||||
# Two profiles are expected to FAIL to start - `misnamed` and `preflightclash`. That is what
|
||||
# they demonstrate, so this script returns 1 for them and the transcript keeps the exception.
|
||||
set -eu
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
PROFILE="${1:-securitysource}"
|
||||
LOG="${LOG:-/tmp/cors-csrf-app.log}"
|
||||
|
||||
./scripts/stop.sh
|
||||
|
||||
setsid nohup mvn -B org.springframework.boot:spring-boot-maven-plugin:run \
|
||||
-Dspring-boot.run.profiles="$PROFILE" \
|
||||
-Dspring-boot.run.jvmArguments="${JVM_ARGS:-}" \
|
||||
> "$LOG" 2>&1 < /dev/null &
|
||||
|
||||
for _ in $(seq 1 90); do
|
||||
if curl -sf -o /dev/null http://localhost:8080/diag/chain 2>/dev/null; then
|
||||
echo "started with profile: $PROFILE (log: $LOG)"
|
||||
exit 0
|
||||
fi
|
||||
if grep -q 'APPLICATION FAILED TO START' "$LOG" 2>/dev/null; then
|
||||
echo "application failed to start under profile: $PROFILE (log: $LOG)" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "application did not become ready; see $LOG" >&2
|
||||
exit 1
|
||||
11
cors-csrf/scripts/stop.sh
Executable file
11
cors-csrf/scripts/stop.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stop the demo application.
|
||||
#
|
||||
# Note the bracket in the grep pattern: it stops the pattern matching this script's own
|
||||
# process. Match the MAIN CLASS, never 'spring-boot' - that pattern also matches the shell
|
||||
# command line that started the application, so pkill -f 'spring-boot' kills your own shell.
|
||||
set -eu
|
||||
for pid in $(ps -eo pid,cmd | grep '[C]orsCsrfApplication' | awk '{print $1}'); do
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
done
|
||||
sleep 1
|
||||
Reference in New Issue
Block a user