用于多因素身份验证的Spring OAuth2实现的完整代码已上传到文件共享站点,您可以通过单击此链接下载该文件。以下说明说明了如何使用链接在任何计算机上重新创建当前问题。提供500点赏金。
当前错误:
当用户尝试通过上 一段中的链接在Spring Boot OAuth2应用程序中使用两因素身份验证进行身份验证时,将触发错误。 当应用程序应在第二页上请求 用户提供用于确认用户身份的个人识别码时,该错误就抛出了。
Spring Boot OAuth2
鉴于空客户端触发了此错误,问题似乎出在 Spring Boot OAuth2中如何将a连接ClientDetailsService到a。Custom OAuth2RequestFactory
a
ClientDetailsService
Custom OAuth2RequestFactory
在整个调试日志可以在文件共享网站通过点击此上阅读的链接。日志中的完整堆栈跟踪仅包含对应用程序中实际代码的一个引用,该行代码为:
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request));
调试日志中引发的错误是:
org.springframework.security.oauth2.provider.NoSuchClientException: No client with requested id: null
引发错误时的控制流:
我创建了以下流程图,以说明@James建议 实施中的多 因素身份验证请求的预期流程:
在前面的流程图中,当前错误在“ 用户名和密码视图”与GET/ secure / two_factor_authenticated步骤之间的某个点抛出。
GET/ secure / two_factor_authenticated
该OP的溶液在范围仅限于第一遍即1)行进通过/oauth/authorize端点,然后2)返回到所述/oauth/authorize端点通过TwoFactorAuthenticationController。
/oauth/authorize
TwoFactorAuthenticationController
因此,我们只是想解决NoSuchClientException,同时也表明客户端已成功授权ROLE_TWO_FACTOR_AUTHENTICATED的POST /secure/two_factor_authenticated。鉴于后续步骤锅炉-板,它是用于流在demonstrably打破可接受SECONDPASS进入CustomOAuth2RequestFactory作为用户输入,只要 第二遍用成功在完成了所有的工件 第一遍。只要我们在此处 成功解决了“ 第一通行证 ”,第二通行证就可以成为一个单独的问题。
NoSuchClientException
ROLE_TWO_FACTOR_AUTHENTICATED
POST /secure/two_factor_authenticated
demonstrably
SECONDPASS
CustomOAuth2RequestFactory
相关代码例外:
这是的代码AuthorizationServerConfigurerAdapter,我尝试在其中建立连接:
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()"); } }
以下是的代码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; } }
重新创建计算机上的问题:
您可以按照以下简单步骤在几分钟内在任何计算机上重新创建问题:
1.)通过 单击此链接,从文件共享站点下载该应用程序的压缩版本。
2.)通过键入以下内容解压缩应用程序: tar -zxvf oauth2.tar(1).gz
tar -zxvf oauth2.tar(1).gz
3.)authserver通过导航到oauth2/authserver,然后键入来启动应用程序mvn spring-boot:run。
oauth2/authserver
4.)resource通过导航到oauth2/resource然后键入来启动应用程序mvn spring-boot:run
oauth2/resource
mvn spring-boot:run
5.)ui通过导航到oauth2/ui然后键入来启动应用程序mvn spring-boot:run
oauth2/ui
6.)打开网络浏览器并导航到 http : // localhost : 8080
http : // localhost : 8080
7)单击Login,然后输入Frodo作为用户和MyRing作为 密码,并点击提交。这将触发上面显示的错误。
Login
Frodo
您可以通过以下方式查看完整的源代码:
a。)将Maven项目导入到您的IDE中,或通过
b。)在解压缩的目录中导航并使用文本编辑器打开。
注意:上面的文件共享链接中的代码是此链接上的SpringBoot OAuth2 GitHub示例以及 @James在此 链接上提供的2 因子身份验证建议的组合。Spring BootGitHub示例的唯一更改是在authserver应用程序中,尤其是authserver/src/main/java和中authserver/src/main/resources/templates。
authserver/src/main/java
authserver/src/main/resources/templates
提出问题:
根据@AbrahamGrief的建议,我添加了FilterConfigurationBean,解决了NoSuchClientException。但是OP询问如何通过图表中的控制流程完成 FIRST PASS以获得500点 赏金。
@AbrahamGrief
FilterConfigurationBean
然后,我通过设置将问题范围缩小ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED在Users.loadUserByUername()为 如下:
ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED在Users.loadUserByUername()
@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); }
这样就无需配置客户端和资源,因此 当前问题仍然很狭窄。但是,下一个障碍是Spring Security拒绝用户的请求 /security/two_factor_authentication。为了 控制流完成FIRST PASS,还需要做哪些进一步的更改,以便POST /secure/two_factor_authenticationSYSO可以ROLE_TWO_FACTOR_AUTHENTICATED?
这里有很多需要该项目实施的修改 描述流,不止在范围上应为一个单一的问题。该 答案将仅关注如何解决:
org.springframework.security.oauth2.provider.NoSuchClientException:没有 请求ID为null的客户端
在 Spring Boot授权服务器中运行时尝试使用SecurityWebApplicationInitializer和FilterBean 时。
发生此异常的原因是因为WebApplicationInitializer实例不是由SpringBoot运行的。这包括AbstractSecurityWebApplicationInitializer可在部署到独立Servlet容器的WAR中工作的所有子类。因此,发生的事情是Spring Boot根据@Bean注释创建了过滤器,忽略了AbstractSecurityWebApplicationInitializer,并将过滤器应用于所有 URL。同时,您只希望将过滤器应用于要传递给的网址addMappingForUrlPatterns。
相反,要将Servlet过滤器应用于Spring Boot中的特定URL,应定义一个FilterConfigurationBean。对于问题中描述的流程,该流程试图将自定义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(); }