需要身份验证才能获得访问令牌 - 使用 'password' 授权和 Spring 的 ResourceOwnerPasswordResourceDetails 时
Authentication is required to obtain an access token - when using 'password' grant and Spring's ResourceOwnerPasswordResourceDetails
我是 Spring 安全方面的新手,我想为仅接受 password
授权的 OAUTH2 安全服务实施一个客户端。
从 auth 服务器获取 access_token
是使用 http 正文中的数据完成的,如下所示:
client_id={{clientId}}&client_secret={{client_secret}}&grant_type=password&username={{username}}&password={{password}}
之后必须在头字段Authorization
中使用access_token
才能访问实际服务。 (例如 Authorization=Bearer <access_token>
)
我的目标是使用 Spring Security OAuth2 提供的功能从身份验证服务请求 access_token
,并使用它来访问服务端点,直到令牌过期。我还希望使用来自身份验证服务器的 refresh_token
值自动刷新我的 access_token
。我想在充分利用 Spring 的功能的同时实现这一目标。
我发现我可以将 OAuth2RestTemplate
与 ResourceOwnerPasswordResourceDetails
一起用于 grant_type password
。
Whosebug post 对我很有帮助,但我还没有使用它。
我还发现 post Authentication is required to obtain an access token (anonymous not allowed) 用户遇到相同的异常,但使用 client_credentials
和 AuthorizationCodeResourceDetails
.
目前我的代码如下所示。
@Service
public class MyClient {
@Autowired
private OAuth2RestTemplate restTemplate;
@Value("${authServer.accessTokenUri}")
private String accessTokenUri;
@Value("${authServer.clientId}")
private String clientId;
@Value("${authServer.clientSecret}")
private String clientSecret;
@Value("${authServer.username}")
private String username;
@Value("${authServer.password}")
private String password;
@Value("${serviceUrl}")
private String serviceUrl;
@Bean
public OAuth2RestTemplate restTemplate(OAuth2ClientContext oauth2ClientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), oauth2ClientContext);
template.setAccessTokenProvider(accessTokenProvider());
return template;
}
@Bean
public AccessTokenProvider accessTokenProvider() {
ResourceOwnerPasswordAccessTokenProvider tokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
return new AccessTokenProviderChain(
Arrays.<AccessTokenProvider>asList(tokenProvider)
);
}
@Bean
protected OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setId(clientId);
resource.setAccessTokenUri(accessTokenUri);
resource.setClientId(clientId);
resource.setClientSecret(clientSecret);
resource.setGrantType("password");
resource.setClientAuthenticationScheme(AuthenticationScheme.form); // fetch access_token by sending authentication data in HTTP Body
resource.setAuthenticationScheme(AuthenticationScheme.header); // send access_token via HTTP Header 'Bearer' field when accessing actual service
resource.setUsername(username);
resource.setPassword(password);
return resource;
}
public void getDataFromService() {
String response = restTemplate.getForObject(serviceUrl, String.class);
}
}
由于此块,AccessTokenProviderChain
中抛出异常。
if (auth instanceof AnonymousAuthenticationToken) {
if (!resource.isClientOnly()) {
throw new InsufficientAuthenticationException("Authentication is required to obtain an access token (anonymous not allowed)");
}
}
这是异常堆栈跟踪。
org.springframework.security.authentication.InsufficientAuthenticationException: Authentication is required to obtain an access token (anonymous not allowed)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:91) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:731) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:128) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:670) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:311) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
如您所见,我无法请求 access_token
。我不明白为什么会出现此异常,因为如果我使用 curl 命令直接从 auth 服务器请求 access_token
,我只能使用所提供的数据进行身份验证。
我在调用restTemplate.getForObject(...)
之前添加以下代码时,像这样成功地手动获得了一个access_token
。
ResourceOwnerPasswordAccessTokenProvider accessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
OAuth2AccessToken token = accessTokenProvider.obtainAccessToken(resource(), new DefaultAccessTokenRequest());
restTemplate.getOAuth2ClientContext().setAccessToken(token);
String token = restTemplate.getAccessToken();
但是,手动获取 access_token
并不是我想要的。有什么我想念的吗?是否可以自动获取 access_token
并使用 Spring 安全性和 password
授权刷新它?
尽管在 Github、Whosebug 等上检查了多个小时的代码……但我无法让我的代码正常工作。
更新:
我发现 OAuth2RestTemplate
实例中的 ResourceOwnerPasswordResourceDetails
实例未初始化,而我想在 getDataFromService()
中使用它。 (即用户名等字段为空)。在@JoeGrandja 的澄清和帮助之后,我的问题现在并不是真正针对 Spring 安全性,而是 Spring.
如何在 @Bean
注释方法中使用 @Value
注释。目前,当 restTemplate
使用 @Bean
注释方法 resource()
构造时,application.yml
中的值显然还不可用。
在@JoeGrandja 的帮助和支持下,我找到了解决方案。非常感谢你! :)
如果其他人有问题,这是我的工作解决方案。我还建议阅读上面@JoeGrandja 的评论。
@Configuration
@ConfigurationProperties(prefix = "authserver")
public class AuthServerConfigProperties {
private String accessTokenUri;
private String clientId;
private String grantType;
private String clientSecret;
private String username;
private String password;
// Getter & Setter for all properties ...
}
@Configuration
public class CommConfig {
@Autowired
AuthServerConfigProperties configProperties;
@Bean
public OAuth2RestOperations restTemplate(OAuth2ClientContext oauth2ClientContext) {
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource(), oauth2ClientContext);
oAuth2RestTemplate.setAccessTokenProvider(new ResourceOwnerPasswordAccessTokenProvider());
return oAuth2RestTemplate;
}
@Bean
protected OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setId(configProperties.getClientId()); // not necessary
resource.setAccessTokenUri(configProperties.getAccessTokenUri());
resource.setClientId(configProperties.getClientId());
resource.setClientSecret(configProperties.getClientSecret());
resource.setGrantType(configProperties.getGrantType());
resource.setClientAuthenticationScheme(AuthenticationScheme.form); // fetch access_token by sending authentication data in HTTP Body
resource.setAuthenticationScheme(AuthenticationScheme.header); // send access_token via HTTP Header 'Bearer' field when accessing actual service
resource.setUsername(configProperties.getUsername());
resource.setPassword(configProperties.getPassword());
return resource;
}
}
@RestController
public class MyController {
@Autowired
private OAuth2RestOperations restTemplate;
@Value("${serviceUrl}")
private String serviceUrl;
@RequestMapping(value = "/getData", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<String> getData() {
String response = restTemplate.getForObject(serviceUrl, String.class);
return new ResponseEntity(response, HttpStatus.OK);
}
}
我有一个类似的问题:rest 请求是匿名的,但内部处理需要 oauth2 授权,通过简单的扩展解决:
public class CustomResourceOwnerPasswordResourceDetails extends ResourceOwnerPasswordResourceDetails {
@Override
public boolean isClientOnly() {
return true;
}
}
我是 Spring 安全方面的新手,我想为仅接受 password
授权的 OAUTH2 安全服务实施一个客户端。
从 auth 服务器获取 access_token
是使用 http 正文中的数据完成的,如下所示:
client_id={{clientId}}&client_secret={{client_secret}}&grant_type=password&username={{username}}&password={{password}}
之后必须在头字段Authorization
中使用access_token
才能访问实际服务。 (例如 Authorization=Bearer <access_token>
)
我的目标是使用 Spring Security OAuth2 提供的功能从身份验证服务请求 access_token
,并使用它来访问服务端点,直到令牌过期。我还希望使用来自身份验证服务器的 refresh_token
值自动刷新我的 access_token
。我想在充分利用 Spring 的功能的同时实现这一目标。
我发现我可以将 OAuth2RestTemplate
与 ResourceOwnerPasswordResourceDetails
一起用于 grant_type password
。
Whosebug post client_credentials
和 AuthorizationCodeResourceDetails
.
目前我的代码如下所示。
@Service
public class MyClient {
@Autowired
private OAuth2RestTemplate restTemplate;
@Value("${authServer.accessTokenUri}")
private String accessTokenUri;
@Value("${authServer.clientId}")
private String clientId;
@Value("${authServer.clientSecret}")
private String clientSecret;
@Value("${authServer.username}")
private String username;
@Value("${authServer.password}")
private String password;
@Value("${serviceUrl}")
private String serviceUrl;
@Bean
public OAuth2RestTemplate restTemplate(OAuth2ClientContext oauth2ClientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), oauth2ClientContext);
template.setAccessTokenProvider(accessTokenProvider());
return template;
}
@Bean
public AccessTokenProvider accessTokenProvider() {
ResourceOwnerPasswordAccessTokenProvider tokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
return new AccessTokenProviderChain(
Arrays.<AccessTokenProvider>asList(tokenProvider)
);
}
@Bean
protected OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setId(clientId);
resource.setAccessTokenUri(accessTokenUri);
resource.setClientId(clientId);
resource.setClientSecret(clientSecret);
resource.setGrantType("password");
resource.setClientAuthenticationScheme(AuthenticationScheme.form); // fetch access_token by sending authentication data in HTTP Body
resource.setAuthenticationScheme(AuthenticationScheme.header); // send access_token via HTTP Header 'Bearer' field when accessing actual service
resource.setUsername(username);
resource.setPassword(password);
return resource;
}
public void getDataFromService() {
String response = restTemplate.getForObject(serviceUrl, String.class);
}
}
由于此块,AccessTokenProviderChain
中抛出异常。
if (auth instanceof AnonymousAuthenticationToken) {
if (!resource.isClientOnly()) {
throw new InsufficientAuthenticationException("Authentication is required to obtain an access token (anonymous not allowed)");
}
}
这是异常堆栈跟踪。
org.springframework.security.authentication.InsufficientAuthenticationException: Authentication is required to obtain an access token (anonymous not allowed)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:91) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:731) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:128) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:670) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:311) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
如您所见,我无法请求 access_token
。我不明白为什么会出现此异常,因为如果我使用 curl 命令直接从 auth 服务器请求 access_token
,我只能使用所提供的数据进行身份验证。
我在调用restTemplate.getForObject(...)
之前添加以下代码时,像这样成功地手动获得了一个access_token
。
ResourceOwnerPasswordAccessTokenProvider accessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
OAuth2AccessToken token = accessTokenProvider.obtainAccessToken(resource(), new DefaultAccessTokenRequest());
restTemplate.getOAuth2ClientContext().setAccessToken(token);
String token = restTemplate.getAccessToken();
但是,手动获取 access_token
并不是我想要的。有什么我想念的吗?是否可以自动获取 access_token
并使用 Spring 安全性和 password
授权刷新它?
尽管在 Github、Whosebug 等上检查了多个小时的代码……但我无法让我的代码正常工作。
更新:
我发现 OAuth2RestTemplate
实例中的 ResourceOwnerPasswordResourceDetails
实例未初始化,而我想在 getDataFromService()
中使用它。 (即用户名等字段为空)。在@JoeGrandja 的澄清和帮助之后,我的问题现在并不是真正针对 Spring 安全性,而是 Spring.
如何在 @Bean
注释方法中使用 @Value
注释。目前,当 restTemplate
使用 @Bean
注释方法 resource()
构造时,application.yml
中的值显然还不可用。
在@JoeGrandja 的帮助和支持下,我找到了解决方案。非常感谢你! :)
如果其他人有问题,这是我的工作解决方案。我还建议阅读上面@JoeGrandja 的评论。
@Configuration
@ConfigurationProperties(prefix = "authserver")
public class AuthServerConfigProperties {
private String accessTokenUri;
private String clientId;
private String grantType;
private String clientSecret;
private String username;
private String password;
// Getter & Setter for all properties ...
}
@Configuration
public class CommConfig {
@Autowired
AuthServerConfigProperties configProperties;
@Bean
public OAuth2RestOperations restTemplate(OAuth2ClientContext oauth2ClientContext) {
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource(), oauth2ClientContext);
oAuth2RestTemplate.setAccessTokenProvider(new ResourceOwnerPasswordAccessTokenProvider());
return oAuth2RestTemplate;
}
@Bean
protected OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setId(configProperties.getClientId()); // not necessary
resource.setAccessTokenUri(configProperties.getAccessTokenUri());
resource.setClientId(configProperties.getClientId());
resource.setClientSecret(configProperties.getClientSecret());
resource.setGrantType(configProperties.getGrantType());
resource.setClientAuthenticationScheme(AuthenticationScheme.form); // fetch access_token by sending authentication data in HTTP Body
resource.setAuthenticationScheme(AuthenticationScheme.header); // send access_token via HTTP Header 'Bearer' field when accessing actual service
resource.setUsername(configProperties.getUsername());
resource.setPassword(configProperties.getPassword());
return resource;
}
}
@RestController
public class MyController {
@Autowired
private OAuth2RestOperations restTemplate;
@Value("${serviceUrl}")
private String serviceUrl;
@RequestMapping(value = "/getData", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<String> getData() {
String response = restTemplate.getForObject(serviceUrl, String.class);
return new ResponseEntity(response, HttpStatus.OK);
}
}
我有一个类似的问题:rest 请求是匿名的,但内部处理需要 oauth2 授权,通过简单的扩展解决:
public class CustomResourceOwnerPasswordResourceDetails extends ResourceOwnerPasswordResourceDetails {
@Override
public boolean isClientOnly() {
return true;
}
}