OAuth2多重身份验证中的空客户端 [英] null client in OAuth2 Multi-Factor Authentication

查看:390
本文介绍了OAuth2多重身份验证中的空客户端的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

用于多因素身份验证的Spring OAuth2实现的完整代码已上传到您可以使用的文件共享站点点击此链接下载.以下说明说明了如何使用链接在任何计算机上重新创建当前问题. 提供500点赏金.

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. Instructions below explain how to use the link to recreate the current problem on any computer. A 500 point bounty is offered.


当前错误:

当用户尝试通过 Spring Boot OAuth2应用程序中的两因素身份验证进行身份验证时,会触发错误上段中的链接.当应用程序应在第二页上请求用户提供用于确认用户身份的个人识别码时,便会在该过程中抛出该错误.

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. 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.

鉴于空客户端正在触发此错误,问题似乎出在Spring Boot OAuth2中如何将ClientDetailsService连接到Custom OAuth2RequestFactory.

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.

点击文件链接站点上的整个调试日志即可.日志中的完整堆栈跟踪仅包含对应用程序中实际代码的一个引用,该行代码为:

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:

在前面的流程图中,当前错误是在 Username&密码查看获取/secure/two_factor_authenticated 步骤.

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.

此OP的解决方案仅限于FIRST PASS,即1.)穿越/oauth/authorize端点,然后2.)通过TwoFactorAuthenticationController返回至/oauth/authorize端点.

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.

因此,我们只想解析NoSuchClientException,同时还表明已在POST /secure/two_factor_authenticated中成功为客户端授予了ROLE_TWO_FACTOR_AUTHENTICATED的权限.鉴于后续步骤都是样板操作,只要用户输入 SECOND PASS ,就可以在 SECOND PASS 项中将流明确打断到CustomOAuth2RequestFactory中. strong>以及成功完成 FIRST PASS 的所有工件.只要我们在此处成功解决了第一次通行证第二次通行证可以是一个单独的问题.

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. 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. The SECOND PASS can be a separate question as long as we successfully resolve the FIRST PASS here.


相关代码例外:

这是AuthorizationServerConfigurerAdapter的代码,我尝试在其中建立连接:

Here is the code for the AuthorizationServerConfigurerAdapter, where I attempt to set up the connection:

@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的代码,其中包含上面触发错误的代码:

Here is the code for the TwoFactorAuthenticationFilter, which contains the code above that is triggering the error:

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;
    }
} 


在计算机上重新创建问题:

您可以按照以下简单步骤在几分钟内在任何计算机上重新创建问题:

You can recreate the problem on any computer in only a few minutes by following these simple steps:

1.)通过单击此链接从文件共享站点下载该应用程序的压缩版本.

1.) Download the zipped version of the app from a file sharing site by clicking on this link.

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

2.) Unzip the app by typing: tar -zxvf oauth2.tar(1).gz

3.)导航到oauth2/authserver,然后键入mvn spring-boot:run,启动authserver应用.

3.) launch the authserver app by navigating to oauth2/authserver and then typing mvn spring-boot:run.

4.)导航到oauth2/resource,然后键入mvn spring-boot:run

4.) launch the resource app by navigating to oauth2/resource and then typing mvn spring-boot:run

5.)通过导航到oauth2/ui然后键入mvn spring-boot:run

5.) launch the ui app by navigating to oauth2/ui and then typing mvn spring-boot:run

6.)打开Web浏览器并导航到http : // localhost : 8080

6.) Open a web browser and navigate to http : // localhost : 8080

7.)单击Login,然后输入Frodo作为用户,输入MyRing作为密码,然后单击提交. 这将触发上面显示的错误.

7.) Click Login and then enter Frodo as the user and MyRing as the password, and click to submit. This will trigger the error shown above.

您可以通过以下方式查看完整的源代码:

You can view the complete source code by:

a.)将Maven项目导入到您的IDE中,或通过

a.) importing the maven projects into your IDE, or by

b.)在解压缩的目录中导航并使用文本编辑器打开.

b.) navigating within the unzipped directories and opening with a text editor.

注意:上面文件共享链接中的代码是 @ James在此链接上提供的2因子身份验证建议. Spring Boot GitHub示例的唯一更改是在authserver应用程序中,特别是在

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. 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.


解决问题:


根据@AbrahamGrief的建议,我添加了FilterConfigurationBean,从而解决了NoSuchClientException的问题.但是OP要求如何通过图中的控制流程完成首次通过,以获得 500点赏金.


NARROWING THE PROBLEM:


Per @AbrahamGrief's suggestion, I added a FilterConfigurationBean, which resolved the NoSuchClientException. But the OP asks how to complete the FIRST PASS through the control flow in the diagram for a 500 point bounty.

然后我通过在Users.loadUserByUername()中将ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED设置如下来缩小问题:

I then narrowed the problem by setting ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED in Users.loadUserByUername() as follows:

@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_authentication可以使用SYSO ROLE_TWO_FACTOR_AUTHENTICATED?

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. 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?

推荐答案

该项目需要进行很多修改才能实现所描述的流程,这超出了单个问题的范围.该答案将仅着重于解决方法:

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:否 具有请求的ID的客户:空

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

在Spring Boot授权服务器中运行时尝试使用SecurityWebApplicationInitializerFilter bean时.

when trying to use a SecurityWebApplicationInitializer and a Filter bean while running in a Spring Boot authorization server.

发生此异常的原因是因为 WebApplicationInitializer实例是不是由Spring Boot运行.这包括在部署到独立Servlet容器的WAR中可以使用的所有AbstractSecurityWebApplicationInitializer子类.因此,发生的事情是Spring Boot根据@Bean注释创建了过滤器,忽略了AbstractSecurityWebApplicationInitializer并将过滤器应用于所有URL.同时,您只希望将过滤器应用于要传递给addMappingForUrlPatterns的那些URL.

The reason this exception is happening is because WebApplicationInitializer instances are not run by Spring Boot. That includes any AbstractSecurityWebApplicationInitializer subclasses that would work in a WAR deployed to a standalone Servlet container. 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. Meanwhile, you only want your filter applied to those URLs that you're trying to pass to addMappingForUrlPatterns.

相反,要将Servlet过滤器应用于Spring Boot中的特定URL,您应该定义FilterConfigurationBean .对于问题中描述的流程,该流程正尝试将自定义TwoFactorAuthenticationFilter应用于/oauth/authorize,如下所示:

Instead, to apply a servlet Filter to particular URLs in Spring Boot, you should define a FilterConfigurationBean. For the flow described in the question, which is trying to apply a custom TwoFactorAuthenticationFilter to /oauth/authorize, that would look as follows:

@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();
}

这篇关于OAuth2多重身份验证中的空客户端的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆