Spring 安全 OAuth 2.0 - 授予授权码始终需要客户端密码
Spring Security OAuth 2.0 - client secret always required for authorization code grant
根据规范,只要在请求中包含client_id
和和,使用授权码授予的令牌请求不需要进行身份验证client_id
与用于生成代码的相同。但是,对于 Spring Security OAuth 2.0 实施,/oauth/token
端点似乎始终需要基本身份验证,即使从未为客户端分配秘密。
由于 ClientDetails
接口中的 isSecretRequired()
方法,似乎支持允许客户端没有密码。我需要做什么才能使没有密码的客户端在 /oauth/token
URL 上进行身份验证?
4.1.3. Access Token Request
The client makes a request to the token endpoint by sending the
following parameters using the "application/x-www-form-urlencoded"
format per Appendix B with a character encoding of UTF-8 in the HTTP
request entity-body:
grant_type
REQUIRED. Value MUST be set to "authorization_code".
code
REQUIRED. The authorization code received from the
authorization server.
redirect_uri
REQUIRED, if the "redirect_uri" parameter was included in the
authorization request as described in Section 4.1.1, and their
values MUST be identical.
client_id
REQUIRED, if the client is not authenticating with the
authorization server as described in Section 3.2.1.
If the client type is confidential or the client was issued client
credentials (or assigned other authentication requirements), the
client MUST authenticate with the authorization server as described
in Section 3.2.1.
使用 allowFormAuthenticationForClients()
方法启用使用表单参数而不是基本身份验证来验证客户端,如下面的代码示例所示。
class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
@Override
void configure(AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients()
}
}
allowFormAuthenticationForClients()
方法触发添加 ClientCredentialsTokenEndpointFilter
允许通过表单参数进行身份验证。
Spring 允许您使用空密钥定义 OAuth2 客户端。
这些可以被认为是 "public" 客户,或者无法保守秘密的客户。想想 Javascript 应用程序、移动应用程序......您通常不希望将客户端机密存储在那里。
正如您所指出的,根据 OAuth2 规范,令牌端点可以选择不需要这些 public 客户端的秘密。
所以在 Spring 中,简单地定义一个空秘密的 OAuth2 客户端,并为一组有限的授权类型(authorization_code 和 refresh_token)配置它
Spring 安全实施令牌 url 将接受没有特定 OAuth2 客户端的客户端机密的令牌交换。
像这样:
@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();
}
没关系。
最初我有一个与接受的答案类似的设置,这绝对是完成这项工作的先决条件。但缺少的是您不能简单地将密码设置为空。您必须将其设置为空密码,例如:
String secret = PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("");
clientDetails.setClientSecret(secret);
如果不这样做,您仍然会收到 401!
为了解决这个问题,请参阅 class 的方法 loadUserByUsername
:org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService
:
if (clientSecret == null || clientSecret.trim().length() == 0) {
clientSecret = this.emptyPassword;
}
可能在您的情况下 emptyPassword
尚未由您的密码编码器使用空编码密码进行初始化。
在 AuthorizationServerConfigurerAdapter
中设置缺少的密码编码器:
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.passwordEncoder(passwordEncoder);
}
这对我有用
@Override
public void configure(AuthorizationServerSecurityConfigurer cfg) throws Exception {
cfg
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients()
.passwordEncoder(clientPasswordEncoder());
}
@Bean("clientPasswordEncoder")
PasswordEncoder clientPasswordEncoder() {
return new BCryptPasswordEncoder(4);
}
测试 1:
测试 2:
我使用Spring boot 2.5.0,不需要allowFormAuthenticationForClients()
。我的密码编码器看起来像这样:
@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);
}
};
}
我通过这个 website
将秘密生成为空字符串
只需单击“Bcrypt”按钮,然后获取要插入数据库的哈希值。
根据规范,只要在请求中包含client_id
和和,使用授权码授予的令牌请求不需要进行身份验证client_id
与用于生成代码的相同。但是,对于 Spring Security OAuth 2.0 实施,/oauth/token
端点似乎始终需要基本身份验证,即使从未为客户端分配秘密。
由于 ClientDetails
接口中的 isSecretRequired()
方法,似乎支持允许客户端没有密码。我需要做什么才能使没有密码的客户端在 /oauth/token
URL 上进行身份验证?
4.1.3. Access Token Request
The client makes a request to the token endpoint by sending the
following parameters using the "application/x-www-form-urlencoded"
format per Appendix B with a character encoding of UTF-8 in the HTTP
request entity-body:grant_type REQUIRED. Value MUST be set to "authorization_code".
code REQUIRED. The authorization code received from the authorization server.
redirect_uri REQUIRED, if the "redirect_uri" parameter was included in the authorization request as described in Section 4.1.1, and their values MUST be identical.
client_id REQUIRED, if the client is not authenticating with the authorization server as described in Section 3.2.1.
If the client type is confidential or the client was issued client credentials (or assigned other authentication requirements), the
client MUST authenticate with the authorization server as described
in Section 3.2.1.
使用 allowFormAuthenticationForClients()
方法启用使用表单参数而不是基本身份验证来验证客户端,如下面的代码示例所示。
class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
@Override
void configure(AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients()
}
}
allowFormAuthenticationForClients()
方法触发添加 ClientCredentialsTokenEndpointFilter
允许通过表单参数进行身份验证。
Spring 允许您使用空密钥定义 OAuth2 客户端。 这些可以被认为是 "public" 客户,或者无法保守秘密的客户。想想 Javascript 应用程序、移动应用程序......您通常不希望将客户端机密存储在那里。
正如您所指出的,根据 OAuth2 规范,令牌端点可以选择不需要这些 public 客户端的秘密。
所以在 Spring 中,简单地定义一个空秘密的 OAuth2 客户端,并为一组有限的授权类型(authorization_code 和 refresh_token)配置它
Spring 安全实施令牌 url 将接受没有特定 OAuth2 客户端的客户端机密的令牌交换。
像这样:
@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();
}
没关系。
最初我有一个与接受的答案类似的设置,这绝对是完成这项工作的先决条件。但缺少的是您不能简单地将密码设置为空。您必须将其设置为空密码,例如:
String secret = PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("");
clientDetails.setClientSecret(secret);
如果不这样做,您仍然会收到 401!
为了解决这个问题,请参阅 class 的方法 loadUserByUsername
:org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService
:
if (clientSecret == null || clientSecret.trim().length() == 0) {
clientSecret = this.emptyPassword;
}
可能在您的情况下 emptyPassword
尚未由您的密码编码器使用空编码密码进行初始化。
在 AuthorizationServerConfigurerAdapter
中设置缺少的密码编码器:
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.passwordEncoder(passwordEncoder);
}
这对我有用
@Override
public void configure(AuthorizationServerSecurityConfigurer cfg) throws Exception {
cfg
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients()
.passwordEncoder(clientPasswordEncoder());
}
@Bean("clientPasswordEncoder")
PasswordEncoder clientPasswordEncoder() {
return new BCryptPasswordEncoder(4);
}
测试 1:
测试 2:
我使用Spring boot 2.5.0,不需要allowFormAuthenticationForClients()
。我的密码编码器看起来像这样:
@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);
}
};
}
我通过这个 website
将秘密生成为空字符串只需单击“Bcrypt”按钮,然后获取要插入数据库的哈希值。