|
4 | 4 | import jakarta.servlet.ServletException; |
5 | 5 | import jakarta.servlet.http.HttpServletRequest; |
6 | 6 | 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; |
8 | 10 | import org.springframework.security.web.util.matcher.RequestMatcher; |
9 | 11 | import org.springframework.web.filter.OncePerRequestFilter; |
10 | 12 |
|
11 | 13 | import java.io.IOException; |
12 | | -import java.nio.charset.StandardCharsets; |
13 | 14 |
|
14 | 15 | public final class WebEidLoginPageGeneratingFilter extends OncePerRequestFilter { |
15 | 16 |
|
16 | | - private final RequestMatcher requestMatcher = new AntPathRequestMatcher("/auth/eid/login", "GET"); |
| 17 | + private final RequestMatcher requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, "/auth/eid/login"); |
17 | 18 | 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; } |
31 | 32 |
|
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 | + } |
36 | 37 |
|
37 | | - const authToken = payload["auth-token"] ?? payload; |
| 38 | + const csrfToken = document.querySelector('#csrftoken').content; |
| 39 | + const csrfHeaderName = document.querySelector('#csrfheadername').content; |
38 | 40 |
|
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; |
55 | 42 |
|
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 | + """; |
61 | 57 |
|
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); |
64 | 62 | return; |
65 | 63 | } |
66 | 64 |
|
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); |
69 | 73 | response.setContentType("text/html;charset=UTF-8"); |
70 | | - response.setContentLength(bytes.length); |
71 | | - response.getOutputStream().write(bytes); |
| 74 | + response.getWriter().write(html); |
72 | 75 | } |
73 | 76 |
|
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); |
76 | 80 | } |
77 | 81 | } |
0 commit comments