Spring Security OAuth 2.0 - 授权码授权必须始终提供客户端密钥

15
根据规范,使用授权码授权请求的令牌不需要经过身份验证,只要在请求中包含client_id并且该client_id与用于生成代码的相同即可。然而,在Spring Security OAuth 2.0实现中,即使客户端从未被分配密钥,似乎始终需要基本身份验证在/oauth/token端点上。
看起来支持允许没有密钥的客户端,因为ClientDetails接口中有一个isSecretRequired()方法。我需要做什么才能使没有密钥的客户端在/oauth/token URL上得到身份验证?
引用如下:
客户端通过在HTTP请求的实体主体中使用“application/x-www-form-urlencoded”格式和UTF-8的字符编码发送以下参数,按附录B,以以下方式向令牌端点发出请求:
grant_type 必需。值必须设置为“authorization_code”。
code 必需。授权服务器收到的授权码。
redirect_uri 如果在第4.1.1节中描述的授权请求中包括了“redirect_uri”参数,则必需,并且它们的值必须相同。
client_id 如果客户端未按照第3.2.1节中所述与授权服务器进行身份验证,则必需。
如果客户端类型是机密的或者客户端已获得客户端凭据(或分配了其他身份验证要求),则客户端必须按照第3.2.1节中所述与授权服务器进行身份验证。
7个回答

13

可以使用以下代码示例中显示的allowFormAuthenticationForClients()方法,通过表单参数而不是基本身份验证来验证客户端。

class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    @Override
    void configure(AuthorizationServerSecurityConfigurer security) {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients()
    }
}

allowFormAuthenticationForClients() 方法会触发添加 ClientCredentialsTokenEndpointFilter,该过滤器允许通过表单参数进行身份验证。


2
对我来说仍然不起作用。当我省略授权标头时,确实会收到401代码的返回。 - ECostello
@ECostello 你最终解决了问题吗?我和你处于同样的情况。 - Sebastiaan van den Broek
@SebastiaanvandenBroek,这个问题对你来说还存在吗?如果是的话,请尝试将密钥设置为null。这意味着无论是有密钥还是无密钥都将被接受。 - Anddo
我有一个非常类似的情况。但是在我的情况下,我有多个客户端,其中一些使用基本身份验证,而其他一些使用表单参数。每当我添加allowFormAuthenticationForClients()时,使用基本身份验证的客户端无法访问。是否可能同时促进这两种方法? - kundante
所以我花了一整天的时间,只为找到一个一行解决方案 :-/ - Ankit
然而,它仍然不能从JSON表单中获取参数,只能从表单编码中获取。有任何解决方案吗? - Ankit

7
Spring允许您定义OAuth2客户端并将其密钥设置为空。这些可以被视为“公共”客户端或无法保持机密的客户端。考虑JavaScript应用程序、移动应用程序等。通常情况下,您不希望在这些地方存储客户端机密。
正如您所指出的,根据OAuth2规范,令牌终结点可以选择不要求这些公共客户端提供机密。
因此,在Spring中,只需定义一个OAuth2客户端并将其密钥设置为空,并为其配置一组有限的授权类型(authorization_code和refresh_token)即可。
Spring Security实现的令牌URL将接受该特定OAuth2客户端的无客户端机密的令牌交换。

2
这正是我所想的,但我仍然收到 401 的错误。EnableAuthorizationServer 的文档也表示:"但 Token Endpoint (/oauth/token) 将自动使用客户端凭据在 HTTP 基本身份验证下进行保护"。 - lilalinux
@lilalinux 你解决了自己的问题吗?我也处在同样的情况下。 - Sebastiaan van den Broek
1
@SebastiaanvandenBroek 在客户端定义中尝试使用“.secret(”{noop}“)”。这应该接受空密码。 - lilalinux
1
@lilalinux 谢谢您的回复,我使用标准密码编码器成功地对空密码进行了编码,可以在这里看到我的答案。我想这归结于同样的事情。 - Sebastiaan van den Broek

1
为解决该问题,请查看org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService类的loadUserByUsername方法:
if (clientSecret == null || clientSecret.trim().length() == 0) {
   clientSecret = this.emptyPassword;
}

在您的情况下,可能 emptyPassword 没有被密码编码器初始化为空编码密码。在 AuthorizationServerConfigurerAdapter 中设置缺失的密码编码器:
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
    oauthServer.passwordEncoder(passwordEncoder);
}

1

这对我有效

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


    @Bean("clientPasswordEncoder")
    PasswordEncoder clientPasswordEncoder() {
        return new BCryptPasswordEncoder(4);
    }

Test 1: 输入图像描述 输入图像描述 Test 2: 输入图像描述

0

起初我有一个与被接受的答案相似的设置,这绝对是使其正常工作的先决条件。但缺失的是,你不能简单地将密码设为null。你必须将其设置为空密码,例如像这样:

String secret = PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("");
clientDetails.setClientSecret(secret);

如果你不这样做,你仍然会得到401错误!


0

我使用Spring Boot 2.5.0,不需要allowFormAuthenticationForClients()。我的PasswordEncoder如下:

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return BCrypt.hashpw(charSequence.toString(), BCrypt.gensalt());
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return BCrypt.checkpw(charSequence.toString(), s);
            }
        };
    }

我通过这个网站生成了一个空字符串作为密钥。

只需点击“Bcrypt”按钮,获取哈希值并将其插入数据库即可。


-1

像这样:

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            .withClient("client").secret(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("")) //empty
            .authorizedGrantTypes("authorization_code", "refresh_token")
            .redirectUris("http://www.dev.com")
            .scopes("all")
            .autoApprove(true);
} @Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
        throws Exception {
    oauthServer.tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()")
            .allowFormAuthenticationForClients();
}

没问题。


2
为什么这个解决方案是最佳的解决方案需要更多的解释说明。仅仅给出代码的回答并不有帮助。 - wandermonk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接