[英]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点赏金。
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
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_AUTHENTICATED
在POST /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 PASS , SECOND PASS就可以成为一个单独的问题。
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/java
和authserver/src/main/resources/templates
。
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授权服务器中运行时尝试使用SecurityWebApplicationInitializer
和Filter
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.