Skip to content

Commit 9f2109c

Browse files
NFC-47 Review findings. Improve csrf logic.
1 parent 14b29c8 commit 9f2109c

File tree

3 files changed

+60
-53
lines changed

3 files changed

+60
-53
lines changed

example/src/main/java/eu/webeid/example/config/ApplicationConfiguration.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,12 @@ public SecurityFilterChain filterChain(HttpSecurity http, AuthTokenDTOAuthentica
5050
var filter = new WebEidAjaxLoginProcessingFilter("/auth/login", authConfig.getAuthenticationManager());
5151

5252
return http
53-
.csrf(csrf -> csrf.ignoringRequestMatchers("/auth/login", "/auth/mobile/auth/init"))
5453
.authorizeHttpRequests(auth -> auth
5554
.requestMatchers("/", "/error").permitAll()
5655
.requestMatchers(HttpMethod.GET, "/auth/eid/login").permitAll()
5756
.requestMatchers("/auth/challenge").permitAll()
5857
.requestMatchers(HttpMethod.POST, "/auth/mobile/auth/init").permitAll()
59-
.requestMatchers("/favicon.ico", "/css/**", "/files/**", "/img/**", "/js/**", "/webjars/**").permitAll()
58+
.requestMatchers("/favicon.ico", "/css/**", "/files/**", "/img/**", "/js/**").permitAll()
6059
.anyRequest().authenticated()
6160
)
6261
.authenticationProvider(authTokenDTOAuthenticationProvider)

example/src/main/java/eu/webeid/example/security/ui/WebEidLoginPageGeneratingFilter.java

Lines changed: 54 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,74 +4,78 @@
44
import jakarta.servlet.ServletException;
55
import jakarta.servlet.http.HttpServletRequest;
66
import jakarta.servlet.http.HttpServletResponse;
7-
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
7+
import org.springframework.http.HttpMethod;
8+
import org.springframework.lang.NonNull;
9+
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
810
import org.springframework.security.web.util.matcher.RequestMatcher;
911
import org.springframework.web.filter.OncePerRequestFilter;
1012

1113
import java.io.IOException;
12-
import java.nio.charset.StandardCharsets;
1314

1415
public final class WebEidLoginPageGeneratingFilter extends OncePerRequestFilter {
1516

16-
private final RequestMatcher requestMatcher = new AntPathRequestMatcher("/auth/eid/login", "GET");
17+
private final RequestMatcher requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, "/auth/eid/login");
1718
private static final String LOGIN_PAGE_HTML = """
18-
<!doctype html>
19-
<html lang="en">
20-
<head>
21-
<meta charset="utf-8">
22-
<meta name="viewport" content="width=device-width, initial-scale=1">
23-
<title>Signing you in…</title>
24-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; connect-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline'; base-uri 'self'; form-action 'self';">
25-
</head>
26-
<body>
27-
<script>
28-
(function () {
29-
const frag = location.hash ? location.hash.substring(1) : "";
30-
if (!frag) { location.replace("/?mobileAuthError=missing_payload"); return; }
19+
<!doctype html>
20+
<html lang="en">
21+
<head>
22+
<meta charset="utf-8">
23+
<title>Signing you in…</title>
24+
<meta id="csrftoken" content="%s"/>
25+
<meta id="csrfheadername" content="%s"/>
26+
</head>
27+
<body>
28+
<script>
29+
(function () {
30+
const frag = location.hash ? location.hash.substring(1) : "";
31+
if (!frag) { location.replace("/?mobileAuthError=missing_payload"); return; }
3132
32-
let payload;
33-
try { payload = JSON.parse(atob(frag)); } catch (e) {
34-
location.replace("/?mobileAuthError=bad_payload"); return;
35-
}
33+
let payload;
34+
try { payload = JSON.parse(atob(frag)); } catch (e) {
35+
location.replace("/?mobileAuthError=bad_payload"); return;
36+
}
3637
37-
const authToken = payload["auth-token"] ?? payload;
38+
const csrfToken = document.querySelector('#csrftoken').content;
39+
const csrfHeaderName = document.querySelector('#csrfheadername').content;
3840
39-
fetch("/auth/login", {
40-
method: "POST",
41-
headers: { "Content-Type": "application/json" },
42-
body: JSON.stringify({ "auth-token": authToken })
43-
})
44-
.then(r => {
45-
if (!r.ok) throw new Error("HTTP " + r.status);
46-
location.replace("/welcome");
47-
})
48-
.catch(() => location.replace("/?mobileAuthError=login_failed"));
49-
})();
50-
</script>
51-
Signing you in…
52-
</body>
53-
</html>
54-
""";
41+
const authToken = payload["auth-token"] ?? payload;
5542
56-
@Override
57-
protected void doFilterInternal(HttpServletRequest request,
58-
HttpServletResponse response,
59-
FilterChain filterChain)
60-
throws ServletException, IOException {
43+
fetch("/auth/login", {
44+
method: "POST",
45+
headers: { "Content-Type": "application/json", [csrfHeaderName]: csrfToken },
46+
body: JSON.stringify({ "auth-token": authToken }),
47+
credentials: "include"
48+
})
49+
.then(r => { if (!r.ok) throw new Error("HTTP " + r.status); location.replace("/welcome"); })
50+
.catch(() => location.replace("/?mobileAuthError=login_failed"));
51+
})();
52+
</script>
53+
Signing you in…
54+
</body>
55+
</html>
56+
""";
6157

62-
if (!this.requestMatcher.matches(request)) {
63-
filterChain.doFilter(request, response);
58+
@Override
59+
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain chain) throws IOException, ServletException {
60+
if (!requestMatcher.matches(request)) {
61+
chain.doFilter(request, response);
6462
return;
6563
}
6664

67-
String html = generateHtml();
68-
byte[] bytes = html.getBytes(StandardCharsets.UTF_8);
65+
var csrf = (org.springframework.security.web.csrf.CsrfToken) request.getAttribute(org.springframework.security.web.csrf.CsrfToken.class.getName());
66+
if (csrf == null) {
67+
csrf = (org.springframework.security.web.csrf.CsrfToken) request.getAttribute("_csrf");
68+
}
69+
String token = csrf != null ? csrf.getToken() : "";
70+
String header = csrf != null ? csrf.getHeaderName() : "X-CSRF-TOKEN";
71+
72+
String html = generateHtml(token, header);
6973
response.setContentType("text/html;charset=UTF-8");
70-
response.setContentLength(bytes.length);
71-
response.getOutputStream().write(bytes);
74+
response.getWriter().write(html);
7275
}
7376

74-
private String generateHtml() {
75-
return LOGIN_PAGE_HTML;
77+
78+
private String generateHtml(String csrfToken, String csrfHeaderName) {
79+
return String.format(LOGIN_PAGE_HTML, csrfToken, csrfHeaderName);
7680
}
7781
}

example/src/main/resources/templates/index.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,11 @@ <h3><a id="for-developers"></a>For developers</h3>
267267
if (isMobileDevice()) {
268268
const resp = await fetch("/auth/mobile/auth/init", {
269269
method: "POST",
270-
headers: { "Content-Type": "application/json" },
270+
headers: {
271+
"Content-Type": "application/json",
272+
[csrfHeaderName]: csrfToken,
273+
},
274+
credentials: "include"
271275
});
272276
await checkHttpError(resp);
273277
const { eidAuthUri } = await resp.json();

0 commit comments

Comments
 (0)