当前位置: 首页 > 知识库问答 >
问题:

OAuth2多因素身份验证中的客户端为空

郎项禹
2023-03-14

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

当用户试图使用Spring Boot OAuth2应用程序中的双因素身份验证(来自上一段中的链接)进行身份验证时,将触发一个错误。当应用程序应该提供第二个页面来询问用户的pin码以确认用户的身份时,错误就会抛出。

假设一个null客户端触发了此错误,问题似乎是如何在Spring Boot OAuth2中将ClientDetailsService连接到自定义OAuth2RequestFactory

通过单击此链接,可以在文件共享站点读取整个调试日志。日志中的完整堆栈跟踪只包含一个对实际存在于应用程序中的代码的引用,这一行代码是:

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

调试日志中抛出的错误为:

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

我创建了以下流程图来说明@James建议实现中多因素身份验证请求的预期流程:

因此,我们只想解析nosuchclientexception,同时在post/secure/two_factor_authenticated中演示客户端已成功地被授予role_two_factor_authenticated。假设后续步骤是boil-plate的,那么只要用户带着成功完成了第一次操作的所有工件进入第二次操作,那么流在第二次操作中明显地中断到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;
    }
} 

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

您只需执行以下简单步骤,即可在任何计算机上重新创建问题:

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

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

5.)导航到oauth2/ui并键入mvn spring-boot:run启动ui应用程序

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

7.)单击login,然后输入frodo作为用户,输入myring作为密码,然后单击提交。这将触发上面所示的错误。

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

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

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

@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的请求。要完成控制流的第一次传递,还需要做哪些进一步的更改,以便post/secure/two_factor_authentication可以SYSOrole_two_factor_authenticated

共有1个答案

东和怡
2023-03-14

为了实现所描述的流程,该项目需要进行大量修改,而不是在单个问题的范围内进行修改。这个答案将只关注如何解决:

org.springframework.security.oauth2.provider.noSuchClientException:没有请求ID为null的客户端

当在Spring Boot授权服务器中运行时尝试使用SecurityWebApplicationInitializerFilterbean时。

发生此异常的原因是WebApplicationInitializer实例不是由Spring Boot运行的。其中包括任何AbstractSecurityWebApplicationInitializer子类,这些子类将在部署到独立Servlet容器的WAR中工作。因此,正在发生的是Spring Boot由于@bean注释而创建筛选器,忽略AbstractSecurityWebApplicationInitializer,并将筛选器应用于所有URL。同时,您只希望将筛选器应用于试图传递给AddMappingForURLPatterns的URL。

相反,要在Spring Boot中将servlet过滤器应用到特定的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();
}
 类似资料:
  • 我不熟悉SSL和证书。我一直在做关于客户端证书认证的研究。我看过这个和wiki。 因此,如果我必须为我的B2B REST服务实现客户端证书身份验证解决方案,我应该执行以下操作 要求客户端生成自己的私钥,并为其公钥生成证书(CA 颁发?)。通过电子邮件或 USB 闪存盘发送该证书。 在服务器端将客户端的公共证书导入信任存储区并启用客户端身份验证 在握手期间,客户端会出示其证书并进行身份验证,因为服务

  • 授权服务器为进行客户端身份验证的目的,为Web应用客户端创建客户端凭据。授权服务器被鼓励考虑比客户端密码更强的客户端身份验证手段。Web应用程序客户端必须确保客户端密码和其他客户端凭据的机密性。 授权不得向本地应用程序或基于用户代理的应用客户端颁发客户端密码或其他客户端凭据用于客户端验证目的。授权服务器可以颁发客户端密码或其他凭据给专门的设备上特定安装的本地应用程序客户端。 当客户端身份验证不可用

  • 在向令牌端点发起请求时,机密客户端或其他被颁发客户端凭据的客户端必须如2.3节所述与授权服务器进行身份验证。客户端身份验证用于: 实施刷新令牌和授权码到它们被颁发给的客户端的绑定。当授权码在不安全通道上向重定向端点传输时,或者 当重定向URI没有被完全注册时,客户端身份验证是关键的。 通过禁用客户端或者改变其凭据从被入侵的客户端恢复,从而防止攻击者滥用被盗的刷新令牌。改变单套客户端凭据显然快于撤销

  • 如果客户端类型是机密的,客户端和授权服务器建立适合于授权服务器的安全性要求的客户端身份验证方法。授权服务器可以接受符合其安全要求的任何形式的客户端身份验证。 机密客户端通常颁发(或建立)一组客户端凭据用于与授权服务器进行身份验证(例如,密码、公/私钥对)。授权服务器可以与公共客户端建立客户端身份验证方法。然而,授权服务器不能依靠公共客户端身份验证达到识别客户端的目的。 客户端在每次请求中不能使用一

  • 有时需要对某些网络资源(如Servlet、JSP等)进行访问权限验证,也就是说,有访问权限的用户才能访问该网络资源。进行访问权限验证的方法很多,但通过HTTP响应消息头的WWW-Authenticate字段进行访问权限的验证应该是众多权限验证方法中比较简单的一个。 通过HTTP响应消息头的WWW-Authenticate字段可以使浏览器出现一个验证对话框,访问者需要在这个对话框中输入用户名和密码,

  • 我正在寻找如何使用spring security Oauth2实现双因素身份验证(2FA)的方法。要求是用户只需要对具有敏感信息的特定应用程序进行双因素身份验证。这些webapp都有自己的客户端ID。 我脑海中浮现的一个想法是“误用”范围审批页面,迫使用户输入2FA代码/PIN(或其他)。 示例流如下所示: null null