一尘不染

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

spring-mvc

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

当前错误:

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

鉴于空客户端触发了此错误,问题似乎出在 Spring Boot OAuth2中如何将a连接ClientDetailsServiceaCustom 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步骤之间的某个点抛出。

该OP的溶液在范围仅限于第一遍即1)行进通过/oauth/authorize端点,然后2)返回到所述/oauth/authorize端点通过TwoFactorAuthenticationController

因此,我们只是想解决NoSuchClientException,同时也表明客户端已成功授权ROLE_TWO_FACTOR_AUTHENTICATEDPOST /secure/two_factor_authenticated。鉴于后续步骤锅炉-板,它是用于流在demonstrably打破可接受SECONDPASS进入CustomOAuth2RequestFactory作为用户输入,只要
第二遍用成功在完成了所有的工件
第一遍。只要我们在此处 成功解决了“ 第一通行证 ”,第二通行证就可以成为一个单独的问题。

相关代码例外:

这是的代码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

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

4.)resource通过导航到oauth2/resource然后键入来启动应用程序mvn spring-boot:run

5.)ui通过导航到oauth2/ui然后键入来启动应用程序mvn spring-boot:run

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

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

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

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

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

注意:上面的文件共享链接中的代码是此链接上的SpringBoot OAuth2 GitHub示例以及 @James在此 链接上提供的2
因子身份验证建议的组合。Spring BootGitHub示例的唯一更改是在authserver应用程序中,尤其是authserver/src/main/java和中authserver/src/main/resources/templates

提出问题:

根据@AbrahamGrief的建议,我添加了FilterConfigurationBean,解决了NoSuchClientException。但是OP询问如何通过图表中的控制流程完成
FIRST PASS以获得500点
赏金。

然后,我通过设置将问题范围缩小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?


阅读 321

收藏
2020-06-01

共1个答案

一尘不染

这里有很多需要该项目实施的修改
描述流,不止在范围上应为一个单一的问题。该
答案将仅关注如何解决:

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();
}
2020-06-01