当前问题:
大多数身份验证算法都能正常工作。程序直到下面所示的控制流结束时才会中断。具体来说,下面的SECOND PASS结束时会抛出Invalid CSRF token found for http://localhost:9999/uaa/oauth/token
错误。上面链接中的应用程序是通过添加自定义OAuth2RequestFactory
开发的,TwoFactorAuthenticationFilter
和TwoFactorAuthenticationController
添加到此Spring Boot OAuth2 GitHub sample的authserver
app。需要对以下代码进行哪些特定更改才能解决此CSRF令牌错误并启用双因素身份验证?
我的研究使我怀疑CustomOAuth2RequestFactory
(API at this link)可能是配置解决方案的地方,因为它定义了管理AuthorizationRequest
和TokenRequest
的方法。
OAuth2官方规范的这一部分指出,向授权端点发出的请求的state
参数是添加csrf
令牌的地方。
此外,链接中的代码使用了官方规范中的授权代码授予类型,这意味着流程中的步骤C不会更新csrf
代码,从而触发了步骤D中的错误。(您可以在官方规范中查看包括步骤C和步骤D的整个流程。)
当前错误周围的控制流:
在下面的流程图中,当前错误是在通过TwoFactorAuthenticationFilter
的SECOND PASS期间抛出的。在控制流进入SECOND PASS之前,一切都按预期工作。
以下流程图说明了可下载应用程序中的代码所采用的双因素身份验证过程的控制流。
x1c 0d1x的数据
具体来说,POST
s和GET
s序列的Firefox HTTP
标头显示,序列中的每个请求都会发送相同的XSRF
cookie。XSRF
令牌值不会导致问题,直到POST /secure/two_factor_authentication
之后,这会触发/oauth/authorize
和/oauth/token
端点上的服务器处理,/oauth/token
抛出Invalid CSRF token found for http://localhost:9999/uaa/oauth/token
错误。
为了理解上述控制流程图与/oauth/authorize
和/oauth/token
端点之间的关系,您可以在单独的浏览器窗口中将上述流程图与官方规格中的单因素流程图进行并排比较。上面的Second Pass只是将单因素官方规格中的步骤再次运行一次,但在第二次通过期间具有更大的权限。
日志内容:
HTTP请求和响应头表明:
1.)使用正确的username
和password
提交到9999/login
的POST会导致重定向到9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v
,然后是GET 9999/secure/two_factor_authenticated
。一个XSRF令牌在这些交换中保持不变。
2.)使用正确的pin代码对9999/secure/two_factor_authentication
进行POST发送相同的XSRF
令牌,并成功重定向到POST 9999/oauth/authorize
,使其进入TwoFactorAuthenticationFilter.doFilterInternal()
并继续到request 9999/oauth/token
,但9999/oauth/token
拒绝请求,因为相同的旧XSRF令牌与新的XSRF
令牌值不匹配,这显然是在第一次通过期间创建的。1.)
和2.)
之间的一个明显区别是2.)
中的第二个request 9999/oauth/authorize
不包含1.)
中第一个请求9999/authorize?client_id=acme&redirect_uri=/login&response_type=code&state=sGXQ4v
中包含的url参数,并且在官方规范中也定义了。但不清楚这是否导致了问题。
此外,不清楚如何访问参数以从TwoFactorAuthenticationController.POST
发送完全格式的请求。我在POST 9999/secure/two_factor_authentication
控制器方法的HttpServletRequest
中对parameters
Map
进行了SYSO,它包含的所有变量都是pinVal
和_csrf
。
您可以在文件共享站点by clicking on this link上读取所有HTTP Header和Sping Boot 日志。
一个最好的方法:
我在Spring Security 3.2环境中尝试了@RobWinch的方法来解决类似的问题,但该方法似乎不适用于Spring OAuth2的上下文。具体来说,当下面的TwoFactorAuthenticationFilter
代码中取消注解以下XSRF
更新代码块时,下游请求头确实显示了不同/新的XSRF
令牌值,但抛出了相同的错误。
if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
response.setHeader("XSRF-TOKEN"/*"X-CSRF-TOKEN"*/, token.getToken());
}
字符串
**这表示需要更新XSRF
配置,使/oauth/authorize
和/oauth/token
能够相互通信,并与客户端和资源应用程序通信,以成功管理XSRF
令牌值。**可能需要更改CustomOAuth2RequestFactory
才能实现此目的。但如何更改?
相关代码:
CustomOAuth2RequestFactory
的代码是:
public class CustomOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
public static final String SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME = "savedAuthorizationRequest";
public CustomOAuth2RequestFactory(ClientDetailsService clientDetailsService) {
super(clientDetailsService);
}
@Override
public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession(false);
if (session != null) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
if (authorizationRequest != null) {
session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
return authorizationRequest;
}
}
return super.createAuthorizationRequest(authorizationParameters);
}
}
型TwoFactorAuthenticationFilter
的代码是:
//This class is added per: https://stackoverflow.com/questions/30319666/two-factor-authentication-with-spring-security-oauth2
/**
* Stores the oauth authorizationRequest in the session so that it can
* later be picked by the {@link com.example.CustomOAuth2RequestFactory}
* to continue with the authoriztion flow.
*/
public class TwoFactorAuthenticationFilter extends OncePerRequestFilter {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private OAuth2RequestFactory oAuth2RequestFactory;
//These next two are added as a test to avoid the compilation errors that happened when they were not defined.
public static final String ROLE_TWO_FACTOR_AUTHENTICATED = "ROLE_TWO_FACTOR_AUTHENTICATED";
public static final String ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED = "ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED";
@Autowired
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService);
}
private boolean twoFactorAuthenticationEnabled(Collection<? extends GrantedAuthority> authorities) {
System.out.println(">>>>>>>>>>> List of authorities includes: ");
for (GrantedAuthority authority : authorities) {
System.out.println("auth: "+authority.getAuthority() );
}
return authorities.stream().anyMatch(
authority -> ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED.equals(authority.getAuthority())
);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("------------------ INSIDE TwoFactorAuthenticationFilter.doFilterInternal() ------------------------");
// Check if the user hasn't done the two factor authentication.
if (AuthenticationUtil.isAuthenticated() && !AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)) {
System.out.println("++++++++++++++++++++++++ AUTHENTICATED BUT NOT TWO FACTOR +++++++++++++++++++++++++");
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request));
/* Check if the client's authorities (authorizationRequest.getAuthorities()) or the user's ones
require two factor authenticatoin. */
System.out.println("======================== twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) is: " + twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) );
System.out.println("======================== twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) is: " + twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) );
if (twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) ||
twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities())) {
// Save the authorizationRequest in the session. This allows the CustomOAuth2RequestFactory
// to return this saved request to the AuthenticationEndpoint after the user successfully
// did the two factor authentication.
request.getSession().setAttribute(CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME, authorizationRequest);
// redirect the the page where the user needs to enter the two factor authentiation code
redirectStrategy.sendRedirect(request, response,
ServletUriComponentsBuilder.fromCurrentContextPath()
.path(TwoFactorAuthenticationController.PATH)
.toUriString());
return;
}
}
//THE NEXT "IF" BLOCK DOES NOT RESOLVE THE ERROR WHEN UNCOMMENTED
//if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
// CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
// this is the value of the token to be included as either a header or an HTTP parameter
// response.setHeader("XSRF-TOKEN", token.getToken());
//}
filterChain.doFilter(request, response);
}
private Map<String, String> paramsFromRequest(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
params.put(entry.getKey(), entry.getValue()[0]);
}
return params;
}
}
型
在计算机上重新创建问题:
您可以在几分钟内在任何计算机上重现问题,只需遵循以下简单步骤:
1.)下载该应用程序的压缩版本(编者注:不幸的是,这不再可用)
2.)通过键入以下内容解压缩应用程序:tar -zxvf oauth2.tar(2).gz
3.)通过导航到oauth2/authserver
然后键入mvn spring-boot:run
来启动authserver
应用程序。
4.)通过导航到oauth2/resource
,然后键入mvn spring-boot:run
,启动resource
应用程序
5.)通过导航到oauth2/ui
,然后键入mvn spring-boot:run
,启动ui
应用程序
6.)打开Web浏览器并导航到http : // localhost : 8080
7.)单击Login
,然后输入Frodo
作为用户,MyRing
作为密码,然后单击提交。
8.)输入Pin Code
作为Pin Code
,然后单击提交。这将触发上面显示的错误。
您可以通过以下方式查看完整的源代码:
a.)将maven项目导入IDE,或者
B.)在解压缩的目录中导航并使用文本编辑器打开。
2条答案
按热度按时间pw9qyyiw1#
我突然想到一个主意:
如果会话固定被激活,在用户成功通过身份验证后,将创建一个新会话(参见SessionFixationProtectionStrategy)。如果您使用默认的HttpSessionCsrfTokenRepository,这当然也会创建一个新的csrf令牌。由于您提到了XSRF-TOKEN头,我假设您使用了一些JavaScript前端。我可以想象用于登录的原始csrf令牌被存储并在之后重用-这将不起作用,因为该CSRF令牌不再有效。
您可以尝试禁用会话固定(
http.sessionManagement().sessionFixation().none()
或<session-management session-fixation-protection="none"/>
)或在登录后重新获取当前CSRF令牌。zrfyljdw2#
您的
CustomOAuth2RequestFactory
将以前的request
替换为当前的request
。但是,在进行此切换时,您没有更新旧request
中的XSRF
令牌。以下是我对更新后的CustomOAuth2Request
的建议:字符串
我重新审视这一点,因为我最初的回答草案被否决。这个版本是进一步沿着相同的道路,我相信这是正确的途径。