Spring 启动更改默认身份验证系统
Spring boot change default authentication system
默认spring 引导使用HttpServletRequest
从客户端默认接受值到"/login" 路由。
如果我想创建一个自定义身份验证系统,选项如下:
- 登录端点,如:“/api/v1/auth/sign”将接受电子邮件和密码
- 与其创建 CustomAuthFilter(如 YT 中的许多视频所示),不如在 auth 控制器中创建一个方法来处理发回 jwt 令牌。
现在我已经知道要更改默认登录路径我需要:
.formLogin().loginProcessingUrl("/api/v1/login")
但是下一部分呢?
我是否需要创建像 SignInRequest
和 SignInResponse
这样的对象?
如果是这样,客户端应用程序是否需要根据 SignInRequest
和 SignInResponse
映射数据?
这是我的 Signup
服务:
@Override
public User signup(User user) {
String encodedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
return authRepository.save(user);
}
我想为登录创建一个类似的服务,例如:
@Override
public User signin(String email, String password) {
// somehow do login and return the user with access and refresh tokens?
}
即使我创建一个 SigninRequest
对象,客户端应用程序也会始终发送电子邮件和密码,对吗?
由于我没有使用过复杂的后端,所以我对如何解决这个问题的想法非常有限。
任何见解或资源都会有所帮助,谢谢。
我目前的attemptAuthentication
方法:
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
StringBuffer sb = new StringBuffer();
BufferedReader reader = null;
String content = "";
String email = "";
String password = "";
try {
reader = request.getReader();
char[] buffer = new char[1024];
int read;
while ((read = reader.read(buffer, 0, buffer.length)) != -1) {
sb.append(buffer, 0, read);
}
content = sb.toString();
Map<String, String> map = new ObjectMapper().readValue(content, Map.class);
email = map.get("email");
password = map.get("password");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
try {
throw ex;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
return authenticationManager.authenticate(authenticationToken);
}
Spring 支持许多复杂的自定义身份验证方法。
spring 安全文档中提供了完整的概述:
Spring Security Docs - Authentication
根据您的问题,我得出的结论是您想坚持某种 user/password 身份验证。这在此处的文档中进行了描述:
Spring Security Docs - User/Password Authentication
你基本上有三个选择:
- 表单登录:在这里,一个动态生成的 HTML 页面呈现给用户,他可以在其中输入用户(在您的例子中是电子邮件地址)和密码。
- 基本身份验证:这里不需要单独的 HTML 页面。相反,浏览器会直接显示一个弹出对话框,用户可以在其中输入他的凭据。
- 摘要式身份验证:相当不常见,spring.org 不推荐。
同样,根据你的问题,我得出的结论是你想坚持使用表单登录。
Spring 为这种替代方案提供了许多选项,这些选项在上述文档中有广泛介绍。
另一个很好的起点是以下流行教程:
Baeldung Tutorial about Spring Security Form Login
除了单纯的实施方面,这里当然还有许多涉及身份验证方法问题的安全考虑因素:User/password身份验证被认为是一种相当不安全的身份验证形式,因为它需要交换用户和服务器之间的秘密。更安全的形式是例如使用一次性密码或证书(例如由浏览器处理的 SSL 客户端证书)。作为进一步阅读的起点,我可以建议:
Rising Stack - Web Authentication Methods Explained
Port Swigger - Authentication vulnerabilities
为了更清楚地说明,我在此处添加了一些示例代码,说明如何使用 React 客户端进行表单登录。此示例基于 Spring 框架中的反应式 WebTestClient。
表单登录涉及以下步骤:
- 客户端发送请求(GET 或 POST),其中包含所需的 URI(可以是静态或动态 HTML 页面或 REST 资源)。
- 当服务器识别出所请求的 URI 需要身份验证时,他会使用指向登录页面的 HTTP 重定向状态代码 (302) 进行响应。
- 现在客户端通过向登录页面发送 GET 请求来启动登录序列。虽然步骤 1 和 2 是可选的并且可以省略,但此步骤至关重要,因为它会在服务器上启动 session。
- 当收到对登录页面的请求时,服务器会创建一个新的 HTTP session、一个 CSRF 令牌(请参阅 Spring 文档),并生成动态登录页面。
- 客户端回复 POST 请求,其中包括 user/password 和 body 中的 CSRF 令牌以及请求 header 中的 session id (例如作为 cookie)。
- 当一切正确并且没有省略第 1 步和第 2 步时,服务器响应重定向到第 1 步中最初请求的资源(如果省略第 1 步和第 2 步,则重定向到默认登录页面)。
- 现在客户端可以再次请求想要的资源了。对于以下所有请求,它必须在请求 header.
中包含 session id
这里是 Java 中的测试客户端实现,它在客户端执行此序列:
public class WebTestClientUtil {
public static class ResponseHolder {
public String baseUrl;
public HttpStatus status;
public String body;
public String sessionId;
public String csrfToken;
public String location;
public ResponseHolder(boolean sslEnabled, int port) {
this.baseUrl = getBaseUrl(sslEnabled, port);
}
}
public enum SessionIdResolutionMethod {
HEADER,
COOKIE,
URL
}
/**
* Determine the server base url
*
* @param port of the server to connect to
* @return the server url
*/
static public String getBaseUrl(boolean sslEnabled, int port) {
return "http" + (sslEnabled ? "s" : "") + "://localhost:" + port + "/";
}
/**
* Build a client for testing
*
* @param ref the response holder to be used for further communication
* @return the client
*/
public static WebTestClient buildClient(ResponseHolder ref) {
return WebTestClient
.bindToServer()
.baseUrl(ref.baseUrl)
.responseTimeout(Duration.ofMinutes(10)) // uncomment for debugging
.build();
}
/**
* Perform a form login and return a response spec to formulate expectations about the result
*
* @param client the client to be used for connecting to the server
* @param uri for the request
* @param username for authentication
* @param password for authentication
* @param ref container for the server response
* @return the response spec
*/
public static WebTestClient.ResponseSpec performLoginSequence(
WebTestClient client,
String uri,
String username,
String password,
ResponseHolder ref) {
return performLoginSequence(client, uri, username, password, ref, SessionIdResolutionMethod.HEADER);
}
/**
* Perform a form login and return a response spec to formulate expectations about the result
*
* @param client the client to be used for connecting to the server
* @param uri for the request
* @param username for authentication
* @param password for authentication
* @param ref container for the server response
* @param sessionIdResolutionMethod true if cookies shall be used for session id resolution
* @return the response spec
*/
public static WebTestClient.ResponseSpec performLoginSequence(
WebTestClient client,
String uri,
String username,
String password,
ResponseHolder ref,
SessionIdResolutionMethod sessionIdResolutionMethod) {
System.out.println();
System.out.println("----------------------------------------------------------------------");
System.out.println("New login sequence initiated for user " + username + " with password " + password + "...");
System.out.println("----------------------------------------------------------------------");
// Send api request
evaluateResponse(client.get().uri(uri).exchange(), ref, sessionIdResolutionMethod)
.expectStatus().is3xxRedirection()
.expectHeader().location(ref.baseUrl + "login");
// Send login request
evaluateResponse(getRequestSpec(client, "login", ref, sessionIdResolutionMethod)
.accept(MediaType.TEXT_HTML)
.acceptCharset(StandardCharsets.UTF_8)
.exchange(), ref, sessionIdResolutionMethod)
.expectStatus().isOk();
assertThat(ref.body).contains("name=\"username\"").contains("name=\"password\"").contains("name=\"_csrf\"");
assertThat(ref.csrfToken).isNotNull();
// Send login details
WebTestClient.ResponseSpec response =
evaluateResponse(postRequestSpec(client, "login", ref, sessionIdResolutionMethod)
.body(BodyInserters
.fromFormData("_csrf", ref.csrfToken)
.with("username", username)
.with("password", password))
.exchange(), ref, sessionIdResolutionMethod);
// In case of errors abort login process
System.out.println("Login sequence completed...");
System.out.println("----------------------------------------------------------------------");
if (ref.status != HttpStatus.FOUND) return response;
if (!ref.location.equals(ref.baseUrl + uri)) return response;
// After successful login continue with redirect
System.out.println("Continuing with original request " + uri + "...");
return evaluateRedirect(client, ref, sessionIdResolutionMethod);
}
public static WebTestClient.ResponseSpec evaluateResponse(WebTestClient.ResponseSpec response, ResponseHolder r) {
return evaluateResponse(response, r, SessionIdResolutionMethod.HEADER);
}
public static WebTestClient.ResponseSpec evaluateResponse(
WebTestClient.ResponseSpec response,
ResponseHolder r,
SessionIdResolutionMethod sessionIdResolutionMethod) {
ExchangeResult result = response
.expectBody(String.class).consumeWith(v -> r.body = v.getResponseBody())
.returnResult();
r.status = result.getStatus();
System.out.println();
System.out.println("Response for request " + result.getUrl());
System.out.println(" HTTP status = " + r.status);
String sessionId = null;
switch (sessionIdResolutionMethod) {
case HEADER, URL -> sessionId = result.getResponseHeaders().getFirst(ResponseHeaderFilter.SESSION_ID_HEADER_NAME);
case COOKIE -> {
ResponseCookie sessionIdCookie = result.getResponseCookies().getFirst("SESSION");
if (sessionIdCookie != null) sessionId = sessionIdCookie.getValue();
}
}
if (sessionId == null) {
System.out.println(" Session id not set in " + sessionIdResolutionMethod +
" - continuing with old (" + r.sessionId + ")");
} else {
r.sessionId = sessionId;
System.out.println(" Session id --> " + r.sessionId);
}
if (r.body == null) r.body = "";
String csrfToken = getCsrfToken(r.body);
if (csrfToken != null) r.csrfToken = csrfToken;
csrfToken = result.getResponseHeaders().getFirst(ResponseHeaderFilter.CSRF_HEADER_NAME);
if (csrfToken != null) {
csrfToken = result.getResponseHeaders().getFirst(csrfToken);
if (csrfToken != null) r.csrfToken = csrfToken;
}
System.out.println(" CSRF token = " + csrfToken + " --> " + r.csrfToken);
System.out.println(" Response Headers:");
for (Map.Entry<String, List<String>> header : result.getResponseHeaders().entrySet()) {
System.out.println(" " +
header.getKey() + ": " +
String.join("; ", header.getValue()));
}
r.location = result.getResponseHeaders().getFirst("Location");
System.out.println(" Body:\n" + r.body);
return response;
}
public static WebTestClient.RequestHeadersSpec<?> getRequestSpec(
WebTestClient client,
String uri,
ResponseHolder ref,
SessionIdResolutionMethod sessionIdResolutionMethod) {
switch (sessionIdResolutionMethod) {
case HEADER -> {
WebTestClient.RequestHeadersSpec<?> request = client.get().uri(uri);
return request.header(ResponseHeaderFilter.SESSION_ID_HEADER_NAME, ref.sessionId);
}
case COOKIE -> {
WebTestClient.RequestHeadersSpec<?> request = client.get().uri(uri);
return request.cookie("SESSION", ref.sessionId);
}
case URL -> {
return client.get().uri(uri +
(uri.contains("?") ? "&" : "?") +
"xAuthToken=" +
new String(Base64.getEncoder().encode(ref.sessionId.getBytes())));
}
}
throw new IllegalArgumentException("Invalid session id resolution method: " + sessionIdResolutionMethod);
}
public static WebTestClient.RequestBodySpec postRequestSpec(
WebTestClient client,
String uri,
ResponseHolder ref,
SessionIdResolutionMethod sessionIdResolutionMethod) {
switch (sessionIdResolutionMethod) {
case HEADER -> {
WebTestClient.RequestBodySpec request = client.post().uri(uri);
return request.header(ResponseHeaderFilter.SESSION_ID_HEADER_NAME, ref.sessionId);
}
case COOKIE -> {
WebTestClient.RequestBodySpec request = client.post().uri(uri);
return request.cookie("SESSION", ref.sessionId);
}
case URL -> {
return client.post().uri(uri +
(uri.contains("?") ? "&" : "?") +
"xAuthToken=" +
new String(Base64.getEncoder().encode(ref.sessionId.getBytes())));
}
}
throw new IllegalArgumentException("Invalid session id resolution method: " + sessionIdResolutionMethod);
}
public static String getCsrfToken(String body) {
int pos = body.indexOf("name=\"_csrf\"");
if (pos < 0) return null;
int start = body.indexOf("value=\"", pos) + 7;
if (start < 0) return null;
int end = body.indexOf("\"", start);
if (end < 0) return null;
return body.substring(start, end);
}
@SuppressWarnings("UnusedReturnValue")
public static WebTestClient.ResponseSpec evaluateRedirect(WebTestClient client, ResponseHolder r) {
return evaluateRedirect(client, r, SessionIdResolutionMethod.HEADER);
}
public static WebTestClient.ResponseSpec evaluateRedirect(WebTestClient client, ResponseHolder r, SessionIdResolutionMethod sessionIdResolutionMethod) {
return evaluateResponse(getRequestSpec(client, r.location, r, sessionIdResolutionMethod)
.accept(MediaType.APPLICATION_JSON)
.acceptCharset(StandardCharsets.UTF_8)
.exchange(), r, sessionIdResolutionMethod);
}
public static void performLogout(WebTestClient client, ResponseHolder r) {
performLogout(client, r, SessionIdResolutionMethod.HEADER);
}
public static void performLogout(WebTestClient client, ResponseHolder r, SessionIdResolutionMethod sessionIdResolutionMethod) {
evaluateResponse(postRequestSpec(client, r.baseUrl + "logout", r, sessionIdResolutionMethod)
.acceptCharset(StandardCharsets.UTF_8)
.exchange(), r, sessionIdResolutionMethod)
.expectStatus().is3xxRedirection();
assertThat(r.location).isEqualTo(r.baseUrl + "login?logout");
}
}
默认spring 引导使用HttpServletRequest
从客户端默认接受值到"/login" 路由。
如果我想创建一个自定义身份验证系统,选项如下:
- 登录端点,如:“/api/v1/auth/sign”将接受电子邮件和密码
- 与其创建 CustomAuthFilter(如 YT 中的许多视频所示),不如在 auth 控制器中创建一个方法来处理发回 jwt 令牌。
现在我已经知道要更改默认登录路径我需要:
.formLogin().loginProcessingUrl("/api/v1/login")
但是下一部分呢?
我是否需要创建像 SignInRequest
和 SignInResponse
这样的对象?
如果是这样,客户端应用程序是否需要根据 SignInRequest
和 SignInResponse
映射数据?
这是我的 Signup
服务:
@Override
public User signup(User user) {
String encodedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
return authRepository.save(user);
}
我想为登录创建一个类似的服务,例如:
@Override
public User signin(String email, String password) {
// somehow do login and return the user with access and refresh tokens?
}
即使我创建一个 SigninRequest
对象,客户端应用程序也会始终发送电子邮件和密码,对吗?
由于我没有使用过复杂的后端,所以我对如何解决这个问题的想法非常有限。
任何见解或资源都会有所帮助,谢谢。
我目前的attemptAuthentication
方法:
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
StringBuffer sb = new StringBuffer();
BufferedReader reader = null;
String content = "";
String email = "";
String password = "";
try {
reader = request.getReader();
char[] buffer = new char[1024];
int read;
while ((read = reader.read(buffer, 0, buffer.length)) != -1) {
sb.append(buffer, 0, read);
}
content = sb.toString();
Map<String, String> map = new ObjectMapper().readValue(content, Map.class);
email = map.get("email");
password = map.get("password");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
try {
throw ex;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
return authenticationManager.authenticate(authenticationToken);
}
Spring 支持许多复杂的自定义身份验证方法。 spring 安全文档中提供了完整的概述:
Spring Security Docs - Authentication
根据您的问题,我得出的结论是您想坚持某种 user/password 身份验证。这在此处的文档中进行了描述:
Spring Security Docs - User/Password Authentication
你基本上有三个选择:
- 表单登录:在这里,一个动态生成的 HTML 页面呈现给用户,他可以在其中输入用户(在您的例子中是电子邮件地址)和密码。
- 基本身份验证:这里不需要单独的 HTML 页面。相反,浏览器会直接显示一个弹出对话框,用户可以在其中输入他的凭据。
- 摘要式身份验证:相当不常见,spring.org 不推荐。
同样,根据你的问题,我得出的结论是你想坚持使用表单登录。 Spring 为这种替代方案提供了许多选项,这些选项在上述文档中有广泛介绍。
另一个很好的起点是以下流行教程:
Baeldung Tutorial about Spring Security Form Login
除了单纯的实施方面,这里当然还有许多涉及身份验证方法问题的安全考虑因素:User/password身份验证被认为是一种相当不安全的身份验证形式,因为它需要交换用户和服务器之间的秘密。更安全的形式是例如使用一次性密码或证书(例如由浏览器处理的 SSL 客户端证书)。作为进一步阅读的起点,我可以建议:
Rising Stack - Web Authentication Methods Explained
Port Swigger - Authentication vulnerabilities
为了更清楚地说明,我在此处添加了一些示例代码,说明如何使用 React 客户端进行表单登录。此示例基于 Spring 框架中的反应式 WebTestClient。
表单登录涉及以下步骤:
- 客户端发送请求(GET 或 POST),其中包含所需的 URI(可以是静态或动态 HTML 页面或 REST 资源)。
- 当服务器识别出所请求的 URI 需要身份验证时,他会使用指向登录页面的 HTTP 重定向状态代码 (302) 进行响应。
- 现在客户端通过向登录页面发送 GET 请求来启动登录序列。虽然步骤 1 和 2 是可选的并且可以省略,但此步骤至关重要,因为它会在服务器上启动 session。
- 当收到对登录页面的请求时,服务器会创建一个新的 HTTP session、一个 CSRF 令牌(请参阅 Spring 文档),并生成动态登录页面。
- 客户端回复 POST 请求,其中包括 user/password 和 body 中的 CSRF 令牌以及请求 header 中的 session id (例如作为 cookie)。
- 当一切正确并且没有省略第 1 步和第 2 步时,服务器响应重定向到第 1 步中最初请求的资源(如果省略第 1 步和第 2 步,则重定向到默认登录页面)。
- 现在客户端可以再次请求想要的资源了。对于以下所有请求,它必须在请求 header. 中包含 session id
这里是 Java 中的测试客户端实现,它在客户端执行此序列:
public class WebTestClientUtil {
public static class ResponseHolder {
public String baseUrl;
public HttpStatus status;
public String body;
public String sessionId;
public String csrfToken;
public String location;
public ResponseHolder(boolean sslEnabled, int port) {
this.baseUrl = getBaseUrl(sslEnabled, port);
}
}
public enum SessionIdResolutionMethod {
HEADER,
COOKIE,
URL
}
/**
* Determine the server base url
*
* @param port of the server to connect to
* @return the server url
*/
static public String getBaseUrl(boolean sslEnabled, int port) {
return "http" + (sslEnabled ? "s" : "") + "://localhost:" + port + "/";
}
/**
* Build a client for testing
*
* @param ref the response holder to be used for further communication
* @return the client
*/
public static WebTestClient buildClient(ResponseHolder ref) {
return WebTestClient
.bindToServer()
.baseUrl(ref.baseUrl)
.responseTimeout(Duration.ofMinutes(10)) // uncomment for debugging
.build();
}
/**
* Perform a form login and return a response spec to formulate expectations about the result
*
* @param client the client to be used for connecting to the server
* @param uri for the request
* @param username for authentication
* @param password for authentication
* @param ref container for the server response
* @return the response spec
*/
public static WebTestClient.ResponseSpec performLoginSequence(
WebTestClient client,
String uri,
String username,
String password,
ResponseHolder ref) {
return performLoginSequence(client, uri, username, password, ref, SessionIdResolutionMethod.HEADER);
}
/**
* Perform a form login and return a response spec to formulate expectations about the result
*
* @param client the client to be used for connecting to the server
* @param uri for the request
* @param username for authentication
* @param password for authentication
* @param ref container for the server response
* @param sessionIdResolutionMethod true if cookies shall be used for session id resolution
* @return the response spec
*/
public static WebTestClient.ResponseSpec performLoginSequence(
WebTestClient client,
String uri,
String username,
String password,
ResponseHolder ref,
SessionIdResolutionMethod sessionIdResolutionMethod) {
System.out.println();
System.out.println("----------------------------------------------------------------------");
System.out.println("New login sequence initiated for user " + username + " with password " + password + "...");
System.out.println("----------------------------------------------------------------------");
// Send api request
evaluateResponse(client.get().uri(uri).exchange(), ref, sessionIdResolutionMethod)
.expectStatus().is3xxRedirection()
.expectHeader().location(ref.baseUrl + "login");
// Send login request
evaluateResponse(getRequestSpec(client, "login", ref, sessionIdResolutionMethod)
.accept(MediaType.TEXT_HTML)
.acceptCharset(StandardCharsets.UTF_8)
.exchange(), ref, sessionIdResolutionMethod)
.expectStatus().isOk();
assertThat(ref.body).contains("name=\"username\"").contains("name=\"password\"").contains("name=\"_csrf\"");
assertThat(ref.csrfToken).isNotNull();
// Send login details
WebTestClient.ResponseSpec response =
evaluateResponse(postRequestSpec(client, "login", ref, sessionIdResolutionMethod)
.body(BodyInserters
.fromFormData("_csrf", ref.csrfToken)
.with("username", username)
.with("password", password))
.exchange(), ref, sessionIdResolutionMethod);
// In case of errors abort login process
System.out.println("Login sequence completed...");
System.out.println("----------------------------------------------------------------------");
if (ref.status != HttpStatus.FOUND) return response;
if (!ref.location.equals(ref.baseUrl + uri)) return response;
// After successful login continue with redirect
System.out.println("Continuing with original request " + uri + "...");
return evaluateRedirect(client, ref, sessionIdResolutionMethod);
}
public static WebTestClient.ResponseSpec evaluateResponse(WebTestClient.ResponseSpec response, ResponseHolder r) {
return evaluateResponse(response, r, SessionIdResolutionMethod.HEADER);
}
public static WebTestClient.ResponseSpec evaluateResponse(
WebTestClient.ResponseSpec response,
ResponseHolder r,
SessionIdResolutionMethod sessionIdResolutionMethod) {
ExchangeResult result = response
.expectBody(String.class).consumeWith(v -> r.body = v.getResponseBody())
.returnResult();
r.status = result.getStatus();
System.out.println();
System.out.println("Response for request " + result.getUrl());
System.out.println(" HTTP status = " + r.status);
String sessionId = null;
switch (sessionIdResolutionMethod) {
case HEADER, URL -> sessionId = result.getResponseHeaders().getFirst(ResponseHeaderFilter.SESSION_ID_HEADER_NAME);
case COOKIE -> {
ResponseCookie sessionIdCookie = result.getResponseCookies().getFirst("SESSION");
if (sessionIdCookie != null) sessionId = sessionIdCookie.getValue();
}
}
if (sessionId == null) {
System.out.println(" Session id not set in " + sessionIdResolutionMethod +
" - continuing with old (" + r.sessionId + ")");
} else {
r.sessionId = sessionId;
System.out.println(" Session id --> " + r.sessionId);
}
if (r.body == null) r.body = "";
String csrfToken = getCsrfToken(r.body);
if (csrfToken != null) r.csrfToken = csrfToken;
csrfToken = result.getResponseHeaders().getFirst(ResponseHeaderFilter.CSRF_HEADER_NAME);
if (csrfToken != null) {
csrfToken = result.getResponseHeaders().getFirst(csrfToken);
if (csrfToken != null) r.csrfToken = csrfToken;
}
System.out.println(" CSRF token = " + csrfToken + " --> " + r.csrfToken);
System.out.println(" Response Headers:");
for (Map.Entry<String, List<String>> header : result.getResponseHeaders().entrySet()) {
System.out.println(" " +
header.getKey() + ": " +
String.join("; ", header.getValue()));
}
r.location = result.getResponseHeaders().getFirst("Location");
System.out.println(" Body:\n" + r.body);
return response;
}
public static WebTestClient.RequestHeadersSpec<?> getRequestSpec(
WebTestClient client,
String uri,
ResponseHolder ref,
SessionIdResolutionMethod sessionIdResolutionMethod) {
switch (sessionIdResolutionMethod) {
case HEADER -> {
WebTestClient.RequestHeadersSpec<?> request = client.get().uri(uri);
return request.header(ResponseHeaderFilter.SESSION_ID_HEADER_NAME, ref.sessionId);
}
case COOKIE -> {
WebTestClient.RequestHeadersSpec<?> request = client.get().uri(uri);
return request.cookie("SESSION", ref.sessionId);
}
case URL -> {
return client.get().uri(uri +
(uri.contains("?") ? "&" : "?") +
"xAuthToken=" +
new String(Base64.getEncoder().encode(ref.sessionId.getBytes())));
}
}
throw new IllegalArgumentException("Invalid session id resolution method: " + sessionIdResolutionMethod);
}
public static WebTestClient.RequestBodySpec postRequestSpec(
WebTestClient client,
String uri,
ResponseHolder ref,
SessionIdResolutionMethod sessionIdResolutionMethod) {
switch (sessionIdResolutionMethod) {
case HEADER -> {
WebTestClient.RequestBodySpec request = client.post().uri(uri);
return request.header(ResponseHeaderFilter.SESSION_ID_HEADER_NAME, ref.sessionId);
}
case COOKIE -> {
WebTestClient.RequestBodySpec request = client.post().uri(uri);
return request.cookie("SESSION", ref.sessionId);
}
case URL -> {
return client.post().uri(uri +
(uri.contains("?") ? "&" : "?") +
"xAuthToken=" +
new String(Base64.getEncoder().encode(ref.sessionId.getBytes())));
}
}
throw new IllegalArgumentException("Invalid session id resolution method: " + sessionIdResolutionMethod);
}
public static String getCsrfToken(String body) {
int pos = body.indexOf("name=\"_csrf\"");
if (pos < 0) return null;
int start = body.indexOf("value=\"", pos) + 7;
if (start < 0) return null;
int end = body.indexOf("\"", start);
if (end < 0) return null;
return body.substring(start, end);
}
@SuppressWarnings("UnusedReturnValue")
public static WebTestClient.ResponseSpec evaluateRedirect(WebTestClient client, ResponseHolder r) {
return evaluateRedirect(client, r, SessionIdResolutionMethod.HEADER);
}
public static WebTestClient.ResponseSpec evaluateRedirect(WebTestClient client, ResponseHolder r, SessionIdResolutionMethod sessionIdResolutionMethod) {
return evaluateResponse(getRequestSpec(client, r.location, r, sessionIdResolutionMethod)
.accept(MediaType.APPLICATION_JSON)
.acceptCharset(StandardCharsets.UTF_8)
.exchange(), r, sessionIdResolutionMethod);
}
public static void performLogout(WebTestClient client, ResponseHolder r) {
performLogout(client, r, SessionIdResolutionMethod.HEADER);
}
public static void performLogout(WebTestClient client, ResponseHolder r, SessionIdResolutionMethod sessionIdResolutionMethod) {
evaluateResponse(postRequestSpec(client, r.baseUrl + "logout", r, sessionIdResolutionMethod)
.acceptCharset(StandardCharsets.UTF_8)
.exchange(), r, sessionIdResolutionMethod)
.expectStatus().is3xxRedirection();
assertThat(r.location).isEqualTo(r.baseUrl + "login?logout");
}
}