简体   繁体   English

OAuth2多重身份验证中的null客户端

[英]null client in OAuth2 Multi-Factor Authentication

Complete code for a Spring OAuth2 implementation of multi-factor authentication has been uploaded to a file sharing site that you can download by clicking on this link . Spring OAuth2多因素身份验证实现的完整代码已上载到文件共享站点,您可以通过单击此链接下载该站点 Instructions below explain how to use the link to recreate the current problem on any computer. 下面的说明解释了如何使用该链接在任何计算机上重新创建当前问题。 A 500 point bounty is offered. 提供500点赏金。


THE CURRENT ERROR: 当前错误:


An error is being triggered when a user tries to authenticate using two factor authentication in the Spring Boot OAuth2 app from the link in the preceding paragraph . 当用户尝试使用前一段中链接的Spring Boot OAuth2应用程序双因素身份验证进行身份验证时,将触发错误。 The error is thrown at the point in the process when the app should serve up a second page asking the user for a pin code to confirm the user's identity. 当应用程序应该提供第二页时要求用户输入密码来确认用户的身份,该过程中会出现错误。

Given that a null client is triggering this error, the problem seems to be how to connect a ClientDetailsService to a Custom OAuth2RequestFactory in Spring Boot OAuth2. 鉴于null客户Custom OAuth2RequestFactory在触发此错误,问题似乎是如何将ClientDetailsService连接到Spring Boot OAuth2中的Custom OAuth2RequestFactory

The entire debug log can be read at a file sharing site by clicking on this link . 通过单击此链接可以在文件共享站点读取整个调试日志 The complete stack trace in the logs contains only one reference to code that is actually in the app, and that line of code is: 日志中的完整堆栈跟踪仅包含对应用程序中实际代码的一个引用,该代码行为:

 AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request)); 

The error thrown in the debug logs is: 调试日志中抛出的错误是:

 org.springframework.security.oauth2.provider.NoSuchClientException: No client with requested id: null 


CONTROL FLOW WHEN ERROR IS THROWN: 错误发生时控制流量:


I created the following flowchart to illustrate the intended flow of multi-factor authentication requests in @James' suggested implementation : 我创建了以下流程图,以说明@James建议实现中的多因素身份验证请求的预期流程:

In the preceding flowchart, the current error is being thrown at some point between the Username & Password View and the GET /secure/two_factor_authenticated steps. 在上面的流程图中,在用户名和密码视图GET / secure / two_factor_authenticated步骤之间的某个时刻抛出当前错误。

The solution to this OP is limited in scope to the FIRST PASS that 1.) travels through the /oauth/authorize endpoint and then 2.) returns back to the /oauth/authorize endpoint via TwoFactorAuthenticationController . 该OP的解决方案的范围仅限于FIRST PASS,其中1.)通过/oauth/authorize端点然后2.)通过TwoFactorAuthenticationController返回到/oauth/authorize端点。

So we simply want to resolve the NoSuchClientException while also demonstrating that the client has been successfully granted ROLE_TWO_FACTOR_AUTHENTICATED in the POST /secure/two_factor_authenticated . 因此,我们只是想解决NoSuchClientException ,同时也证明了客户端已成功授权ROLE_TWO_FACTOR_AUTHENTICATEDPOST /secure/two_factor_authenticated Given that the subsequent steps are boiler-plate, it is acceptable for the flow to demonstrably break in the SECOND PASS entry into CustomOAuth2RequestFactory , as long as the user enters the SECOND PASS with all the artifacts of successfully having completed the FIRST PASS . 鉴于后续步骤是CustomOAuth2RequestFactory ,只要用户输入SECOND PASS并且成功完成FIRST PASS的所有工件,就可以在SECOND PASS条目中CustomOAuth2RequestFactory将流程中断为CustomOAuth2RequestFactory The SECOND PASS can be a separate question as long as we successfully resolve the FIRST PASS here. 只要我们在这里成功解决FIRST PASSSECOND PASS就可以成为一个单独的问题。


RELEVANT CODE EXCERPTS: 相关代码EXCERPTS:


Here is the code for the AuthorizationServerConfigurerAdapter , where I attempt to set up the connection: 以下是AuthorizationServerConfigurerAdapter的代码,我尝试设置连接:

 @Configuration @EnableAuthorizationServer protected static class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired//ADDED AS A TEST TO TRY TO HOOK UP THE CUSTOM REQUEST FACTORY private ClientDetailsService clientDetailsService; @Autowired//Added per: https://stackoverflow.com/questions/30319666/two-factor-authentication-with-spring-security-oauth2 private CustomOAuth2RequestFactory customOAuth2RequestFactory; //THIS NEXT BEAN IS A TEST @Bean CustomOAuth2RequestFactory customOAuth2RequestFactory(){ return new CustomOAuth2RequestFactory(clientDetailsService); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); KeyPair keyPair = new KeyStoreKeyFactory( new ClassPathResource("keystore.jks"), "foobar".toCharArray() ) .getKeyPair("test"); converter.setKeyPair(keyPair); return converter; } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("acme")//API: http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/config/annotation/builders/ClientDetailsServiceBuilder.ClientBuilder.html .secret("acmesecret") .authorizedGrantTypes("authorization_code", "refresh_token", "password") .scopes("openid"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints//API: http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.html .authenticationManager(authenticationManager) .accessTokenConverter(jwtAccessTokenConverter()) .requestFactory(customOAuth2RequestFactory);//Added per: https://stackoverflow.com/questions/30319666/two-factor-authentication-with-spring-security-oauth2 } @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer//API: http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerSecurityConfigurer.html .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()"); } } 

Here is the code for the TwoFactorAuthenticationFilter , which contains the code above that is triggering the error: 以下是TwoFactorAuthenticationFilter的代码,其中包含触发错误的上述代码:

 package demo; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.OAuth2RequestFactory; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.RedirectStrategy; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; //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) { 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 { // Check if the user hasn't done the two factor authentication. if (AuthenticationUtil.isAuthenticated() && !AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)) { AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request)); /* Check if the client's authorities (authorizationRequest.getAuthorities()) or the user's ones require two factor authenticatoin. */ 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; } } 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; } } 

RE-CREATING THE PROBLEM ON YOUR COMPUTER: 在您的计算机上重新创建问题:


You can recreate the problem on any computer in only a few minutes by following these simple steps: 您可以通过以下简单步骤在几分钟内在任何计算机上重新创建问题:

1.) Download the zipped version of the app from a file sharing site by clicking on this link . 1.) 通过单击此链接从文件共享站点下载应用程序压缩版本

2.) Unzip the app by typing: tar -zxvf oauth2.tar(1).gz 2.)键入以下内容解压缩应用程序: tar -zxvf oauth2.tar(1).gz

3.) launch the authserver app by navigating to oauth2/authserver and then typing mvn spring-boot:run . 3.)通过导航到oauth2/authserver然后输入mvn spring-boot:run来启动authserver应用程序。

4.) launch the resource app by navigating to oauth2/resource and then typing mvn spring-boot:run 4.)通过导航到oauth2/resource然后输入mvn spring-boot:run来启动resource应用程序

5.) launch the ui app by navigating to oauth2/ui and then typing mvn spring-boot:run 5.)通过导航到oauth2/ui然后输入mvn spring-boot:run来启动ui应用程序

6.) Open a web browser and navigate to http : // localhost : 8080 6.)打开Web浏览器并导航到http : // localhost : 8080

7.) Click Login and then enter Frodo as the user and MyRing as the password, and click to submit. 7.)单击“ Login ,然后输入Frodo作为用户,输入MyRing作为密码,然后单击“提交”。 This will trigger the error shown above. 这将触发上面显示的错误。

You can view the complete source code by: 您可以通过以下方式查看完整的源代码:

a.) importing the maven projects into your IDE, or by a。)将maven项目导入IDE,或者

b.) navigating within the unzipped directories and opening with a text editor. b。)在解压缩的目录中导航并使用文本编辑器打开。

Note: The code in the file sharing link above is a combination of the Spring Boot OAuth2 GitHub sample at this link , and the suggestions for 2 Factor Authentication offered by @James at this link . 注意:上面文件共享链接中的代码是此链接上的Spring Boot OAuth2 GitHub示例的组合,以及@James在此链接上提供的2因素身份验证建议 The only changes to the Spring Boot GitHub sample have been in the authserver app, specifically in authserver/src/main/java and in authserver/src/main/resources/templates . 对Spring Boot GitHub示例的唯一更改是在authserver应用程序中,特别是在authserver/src/main/javaauthserver/src/main/resources/templates


NARROWING THE PROBLEM: 解决问题:


Per @AbrahamGrief's suggestion, I added a FilterConfigurationBean , which resolved the NoSuchClientException . 根据@ AbrahamGrief的建议,我添加了一个FilterConfigurationBean ,它解决了NoSuchClientException But the OP asks how to complete the FIRST PASS through the control flow in the diagram for a 500 point bounty . 但是OP询问如何通过图表中的控制流完成FIRST PASS以获得500点奖励

I then narrowed the problem by setting ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED in Users.loadUserByUername() as follows: 然后,我通过在Users.loadUserByUername()设置ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED来缩小问题,如下所示:

 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { String password; List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"); if (username.equals("Samwise")) {//ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED will need to come from the resource, NOT the user auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_HOBBIT, ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED"); password = "TheShire"; } else if (username.equals("Frodo")){//ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED will need to come from the resource, NOT the user auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_HOBBIT, ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED"); password = "MyRing"; } else{throw new UsernameNotFoundException("Username was not found. ");} return new org.springframework.security.core.userdetails.User(username, password, auth); } 

This eliminates the need to configure clients and resources, so that the current problem remains narrow. 这消除了配置客户端和资源的需要,因此当前问题仍然很窄。 However, the next roadblock is that Spring Security is rejecting the user's request for /security/two_factor_authentication . 但是,下一个障碍是Spring Security拒绝用户对/security/two_factor_authentication的请求。 What further changes need to be made to complete the FIRST PASS through the control flow, so that the POST /secure/two_factor_authentication can SYSO ROLE_TWO_FACTOR_AUTHENTICATED ? 需要进一步更改才能通过控制流完成FIRST PASS,以便POST /secure/two_factor_authentication可以SYSO ROLE_TWO_FACTOR_AUTHENTICATED

There are a lot of modifications needed for that project to implement the described flow, more than should be in scope for a single question. 该项目需要进行大量修改才能实现所描述的流程,而不仅仅是单个问题的范围。 This answer will focus solely on how to resolve: 这个答案将仅关注如何解决:

org.springframework.security.oauth2.provider.NoSuchClientException: No client with requested id: null org.springframework.security.oauth2.provider.NoSuchClientException:没有请求id的客户端:null

when trying to use a SecurityWebApplicationInitializer and a Filter bean while running in a Spring Boot authorization server. 在Spring Boot授权服务器中运行时尝试使用SecurityWebApplicationInitializerFilter bean时。

The reason this exception is happening is because WebApplicationInitializer instances are not run by Spring Boot . 发生此异常的原因是因为WebApplicationInitializer 运行WebApplicationInitializer实例 That includes any AbstractSecurityWebApplicationInitializer subclasses that would work in a WAR deployed to a standalone Servlet container. 这包括任何可以在部署到独立Servlet容器的WAR中工作的AbstractSecurityWebApplicationInitializer子类。 So what is happening is Spring Boot creates your filter because of the @Bean annotation, ignores your AbstractSecurityWebApplicationInitializer , and applies your filter to all URLs. 所以正在发生的事情是Spring Boot因为@Bean注释而创建了你的过滤器,忽略了你的AbstractSecurityWebApplicationInitializer ,并将你的过滤器应用于所有的URL。 Meanwhile, you only want your filter applied to those URLs that you're trying to pass to addMappingForUrlPatterns . 同时,您只希望将过滤器应用于您尝试传递给addMappingForUrlPatterns

Instead, to apply a servlet Filter to particular URLs in Spring Boot, you should define a FilterConfigurationBean . 相反,要将Servlet Filter应用于Spring Boot中的特定URL,您应该定义FilterConfigurationBean For the flow described in the question, which is trying to apply a custom TwoFactorAuthenticationFilter to /oauth/authorize , that would look as follows: 对于问题中描述的流程,它试图将自定义的TwoFactorAuthenticationFilter应用于/oauth/authorize ,如下所示:

@Bean
public FilterRegistrationBean twoFactorAuthenticationFilterRegistration() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(twoFactorAuthenticationFilter());
    registration.addUrlPatterns("/oauth/authorize");
    registration.setName("twoFactorAuthenticationFilter");
    return registration;
}

@Bean
public TwoFactorAuthenticationFilter twoFactorAuthenticationFilter() {
    return new TwoFactorAuthenticationFilter();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM