首页 > 解决方案 > 需要进行身份验证才能获得访问令牌 - 使用“密码”授权和 Spring 的 ResourceOwnerPasswordResourceDetails 时

问题描述

password我是 Spring Security 的新手,我想为只接受授权的 OAUTH2 安全服务实现一个客户端。

access_token使用 http 正文中的数据完成从身份验证服务器的获取,如下所示:

client_id={{clientId}}&client_secret={{client_secret}}&grant_type=password&username={{username}}&password={{password}}

之后access_token必须在标头字段中使用Authorization才能访问实际服务。(例如Authorization=Bearer <access_token>

我的目标是使用 Spring Security OAuth2 提供的功能从access_token身份验证服务请求一个,并使用它来访问服务端点,直到令牌到期。我还希望使用来自身份验证服务器access_token的值自动刷新my 。refresh_token我想在充分利用 Spring 功能的同时实现这一目标。

我发现我可以将OAuth2RestTemplatewithResourceOwnerPasswordResourceDetails用于 grant_type password

StackOverflow post oAuth2 client with password grant in Spring Security对我很有帮助,但我还没有让它工作。我还发现 post Authentication is required to get an access token (anonymous not allowed)用户遇到相同的异常,但使用client_credentialsand 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. 我不明白为什么会出现此异常,因为如果我access_token使用 curl 命令直接从身份验证服务器请求一个,我只能使用所述提供的数据进行身份验证。

access_token在调用之前添加以下代码时,我手动获得了这样的成功restTemplate.getForObject(...)

ResourceOwnerPasswordAccessTokenProvider accessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
OAuth2AccessToken token = accessTokenProvider.obtainAccessToken(resource(), new DefaultAccessTokenRequest());
restTemplate.getOAuth2ClientContext().setAccessToken(token);
String token = restTemplate.getAccessToken();

但是,手动获取access_token并不是我想要的。有什么我想念的吗?是否可以使用带有授权的 Spring Security自动获取access_token并刷新它?password尽管在 Github、StackOverflow 等上检查了多个小时的代码……但我无法让我的代码正常工作。


更新:

我发现我的ResourceOwnerPasswordResourceDetails实例里面的OAuth2RestTemplate实例没有初始化,当我想在里面使用它时getDataFromService()。(即用户名等字段为空)。在@JoeGrandja 的澄清和帮助之后,我的问题现在并不是真正针对 Spring Security,而是针对 Spring。

我可以做些什么来利用带注释的方法中@Value@Bean注释。目前,当使用带注释的方法restTemplate构造时,显然还没有来自 的值。@Beanresource()application.yml

标签: springspring-securityoauth-2.0spring-security-oauth2spring-oauth2

解决方案


在@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);
    }
}

推荐阅读