是否可以在没有客户端密钥的情况下从 Spring OAuth2 服务器获取 access_token?

Is it possible to get an access_token from Spring OAuth2 server without client secret?

我正在使用 Spring Security 的 OAuth2 服务器实现。我正在尝试使用 OAuth2 "Password" 授权类型从服务器的 /oauth/token 端点获取 access_token,方法是仅提供用户名和密码以及不提供客户端密码的客户端 ID。 只要我在 HTTP 请求的 Authorization header 中提供客户端 ID 和客户端密码,就可以正常工作,如下所示:

curl -u clientid:clientsecret http://myhost ... -d "grant_type=password&username=user&password=pw&client_id=OAUTH_CLIENT"

按照此处的建议:,我设法禁用了 /auth/token 端点的 HTTP 基本身份验证。但是当我尝试通过 cURL 获取 access_token 时:

curl http://myhost ... -d "grant_type=password&username=user&password=pw&client_id=OAUTH_CLIENT"

我收到 BadCredentialsException 并且可以看到消息:

Authentication failed: password does not match stored value

在我的服务器日志中。在这一点上,我有点恼火,因为据我所知,只有当用户名 and/or 密码有问题时才会显示此消息,而不是客户端 ID and/or 机密。在 cURL 命令中额外提供客户端密码后,如下所示:

 curl http://myhost ... -d "grant_type=password&username=user&password=pw&client_id=OAUTH_CLIENT&client_secret=SECRET"

一切又好了。

那么这是否意味着我必须以一种或另一种方式提供客户端密码才能访问 /auth/token 端点?

PS:我知道这样一个事实,就安全性而言,通过 HTTP 基本身份验证保护此端点通常是一个好主意,但在某些用例中,人们宁愿不这样做.

编辑:

我好像找到了省略客户端密码的方法。这是我的 OAuth2 服务器配置(注意对 allowFormAuthenticationForClients()autoApprove(true) 的调用):

@Configuration
@EnableAuthorizationServer
class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;

    public OAuth2Config(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(this.authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauth) throws Exception {
        // allows access of /auth/token endpoint without HTTP Basic authentication
        oauth.allowFormAuthenticationForClients();  
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
        .inMemory()
        .withClient("acme")
        .autoApprove(true) // <- allows for client id only
        .authorizedGrantTypes("authorization_code", "refresh_token", "password").scopes("openid");
    }

}

编辑二:

此处的问题: 与此问题密切相关,但处理的是 OAuth2 授权类型 "Authorization Code",这会导致不同的工作流程,就像您使用授权类型 "Password".

根据规范 (RFC 6749),如果您的应用程序的客户端类型为 public,则客户端秘密不是必需的。相反,如果客户端类型是 confidential,则需要客户端密码。

如果Spring提供API来设置客户端类型,请尝试将客户端类型设置为public.

Spring Boot 的实现需要传入客户端密码以进行身份​​验证。但是,您可以通过创建 AuthorizationServerConfigurer 类型的 bean 并自行配置来覆盖它。 This is the link to the documenation...

使用基本身份验证,但将密码留空。

AuthorizationServerConfigurerAdapter 的实现中覆盖 configure 并将密码编码器设置为原始文本编码器(不要将其用作默认密码编码器!)。

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .passwordEncoder(plainTextPasswordEncoder())
                .allowFormAuthenticationForClients();
    }

    private PasswordEncoder plainTextPasswordEncoder() {
        return new PasswordEncoder() {
            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return !StringUtils.hasText(encodedPassword) || passwordEncoder.matches(rawPassword, encodedPassword);
            }

            @Override
            public String encode(CharSequence rawPassword) {
                return passwordEncoder.encode(rawPassword);
            }
        };
    }

}

现在,对于 OAuth 客户端详细信息(在内存或数据库中),将客户端密码设置为 null。在这种情况下,客户端将被视为 public,并且不需要 client_secret 参数。如果您为 OAuth 客户端详细信息(例如 BCrypt 哈希)设置客户端机密,则客户端将被视为机密。它将依赖默认密码编码器(例如 BCrypt)并需要发送 client_secret 参数以获得访问令牌。