Spring Security中具有密码授予权限的oAuth2客户端 [英] oAuth2 client with password grant in Spring Security

查看:104
本文介绍了Spring Security中具有密码授予权限的oAuth2客户端的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用一组受oAuth2保护的服务.当前它是这样工作的:客户端使用其用户名和密码登录.我用这些交换令牌.我将令牌保存在会话中,并在每次调用服务时提交.它可以工作,但是问题是我完全手动执行此操作,而没有使用很多Spring Security oAuth2支持. 外观如下:

I'm consuming a set of oAuth2 protected services. It currently works like this: the client logs in using their username and password. I exchange these for a token. I keep the token in the session and submit it every time I want to call a service. It works, but the problem is that I do this completely manually, without using much of Spring Security oAuth2 support. Here's how it looks:

<!-- Configure Authentication mechanism -->
<authentication-manager alias="authenticationManager">
    <authentication-provider ref="oAuth2AuthenticationProvider"/>
</authentication-manager>


<beans:bean id="oAuth2AuthenticationProvider" class="my.custom.Oauth2AuthenticationProvider">
    <beans:constructor-arg name="accessTokenUri" value="http://x.x.x.x/oauth/token"/>
    <beans:constructor-arg name="clientId" value="myClientId"/>
    <beans:constructor-arg name="clientSecret" value="myClientSecret"/>
    <beans:constructor-arg name="scope">
        <beans:list>
            <beans:value>myScope</beans:value>
        </beans:list>
    </beans:constructor-arg>
</beans:bean>

<beans:bean id="resourceOwnerPasswordAccessTokenProvider" class="org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider"/>

如您所见,我自己创建了身份验证提供程序.它正在接受标准UsernamePasswordAuthenticationToken,但正在产生我自己的扩展,该扩展也保留了实际的OAuth2AccessToken,从而将其保留在安全上下文中.

As you can see I made the authentication provider myself. It is accepting the standard UsernamePasswordAuthenticationToken but is producing my own extension of it that keeps the actual OAuth2AccessToken as well, thus keeping it in the security context.

public class Oauth2AuthenticationProvider implements AuthenticationProvider {

@Autowired
private ResourceOwnerPasswordAccessTokenProvider provider;

private String accessTokenUri;
private String clientId;
private String clientSecret;
private List<String> scope;

public Oauth2AuthenticationProvider(String accessTokenUri, String clientId, String clientSecret, List<String> scope) {
    this.accessTokenUri = accessTokenUri;
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.scope = scope;
}

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    String username = authentication.getName();
    String password = authentication.getCredentials().toString();
    OAuth2AccessToken token = obtainToken(username, password);
    return handleLogonSuccess(authentication, token);
}

private OAuth2AccessToken obtainToken(String username, String password) {
    ResourceOwnerPasswordResourceDetails passwordResourceDetails = new ResourceOwnerPasswordResourceDetails();
    passwordResourceDetails.setUsername(username);
    passwordResourceDetails.setPassword(password);
    passwordResourceDetails.setClientId(clientId);
    passwordResourceDetails.setClientSecret(clientSecret);
    passwordResourceDetails.setScope(scope);
    passwordResourceDetails.setAccessTokenUri(accessTokenUri);
    DefaultAccessTokenRequest defaultAccessTokenRequest = new DefaultAccessTokenRequest();
    OAuth2AccessToken token;
    try {
        token = provider.obtainAccessToken(passwordResourceDetails, defaultAccessTokenRequest);
    } catch (OAuth2AccessDeniedException accessDeniedException) {
        throw new BadCredentialsException("Invalid credentials", accessDeniedException);
    }

    return token;
}

public OAuth2AccessToken refreshToken(OAuth2AuthenticationToken authentication) {
    OAuth2AccessToken token = authentication.getoAuth2AccessToken();
    OAuth2RefreshToken refreshToken = token.getRefreshToken();
    BaseOAuth2ProtectedResourceDetails resourceDetails = new BaseOAuth2ProtectedResourceDetails();
    resourceDetails.setClientId(clientId);
    resourceDetails.setClientSecret(clientSecret);
    resourceDetails.setScope(scope);
    resourceDetails.setAccessTokenUri(accessTokenUri);
    OAuth2AccessToken newToken = provider.refreshAccessToken(resourceDetails, refreshToken, new DefaultAccessTokenRequest());
    authentication.setoAuth2AccessToken(newToken);
    return newToken;
}

public boolean supports(Class<?> authentication) {
    return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}

private Authentication handleLogonSuccess(Authentication authentication, OAuth2AccessToken token) {

    MyCustomOAuth2AuthenticationToken successAuthenticationToken = new MyCustomOAuth2AuthenticationToken(user, authentication.getCredentials(), calculateAuthorities(authentication), token);

    return successAuthenticationToken;
}

public list<GrantedAuthority> calculateAuthorities(Authentication authentication) {
        //my custom logic that assigns the correct role. e.g. ROLE_USER
}

}

如您所见,它基本上可以确保令牌保留在安全范围内,在每次调用后端服务之前,我可以从该域中简单地手动提取令牌.同样,我将在每次调用前检查令牌的新鲜度. 这很好用,但是我确定我可以在XML中使用Spring的oauth命名空间(我没有在使用Java config),从而以更少配置的代码方式实现了相同目的.我发现的大多数示例都包括我不在乎而只是让我感到困惑的oAuth服务器实现.

As you see, it basically makes sure the token remains in the security scope from where I can simply extract it manually before each call to the back-end services. Similarly, I will check the freshness of the token before each call. This works well, but I'm sure I can use Spring's oauth namespace in XML (I'm not using Java config) to achieve the same in a more-config-less-code way. Most examples I find include the oAuth server implementation which I do not care about and just confuse me.

有人可以帮我吗?

推荐答案

我通过浏览Spring Security OAuth源代码以及在线找到的其他解决方案,捣碎了类似的解决方案.我正在使用Java Config,但也许它可以帮助您映射到xml配置,如下所示:

I've mashed a similar solution from browsing the Spring Security OAuth sources and bits and pieces of other solutions found online. I'm using Java Config but maybe it can help you map to a xml configuration, here it goes:

@Configuration
@EnableOAuth2Client
public class RestClientConfig {

    @Value("${http.client.maxPoolSize}")
    private Integer maxPoolSize;

    @Value("${oauth2.resourceId}")
    private String resourceId;

    @Value("${oauth2.clientId}")
    private String clientId;

    @Value("${oauth2.clientSecret}")
    private String clientSecret;

    @Value("${oauth2.accessTokenUri}")
    private String accessTokenUri;


    @Autowired
    private OAuth2ClientContext oauth2ClientContext;


    @Bean
    public ClientHttpRequestFactory httpRequestFactory() {
        return new HttpComponentsClientHttpRequestFactory(httpClient());
    }

    @Bean
    public HttpClient httpClient() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(maxPoolSize);
        // This client is for internal connections so only one route is expected
        connectionManager.setDefaultMaxPerRoute(maxPoolSize);
        return HttpClientBuilder.create().setConnectionManager(connectionManager).build();
    } 

    @Bean
    public OAuth2ProtectedResourceDetails oauth2ProtectedResourceDetails() {
        ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
        details.setId(resourceId);
        details.setClientId(clientId);
        details.setClientSecret(clientSecret);
        details.setAccessTokenUri(accessTokenUri);
        return details;
    }

    @Bean
    public AccessTokenProvider accessTokenProvider() {
        ResourceOwnerPasswordAccessTokenProvider tokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
        tokenProvider.setRequestFactory(httpRequestFactory());
        return new AccessTokenProviderChain(
                  Arrays.<AccessTokenProvider> asList(tokenProvider)
                );
    }

    @Bean
    public OAuth2RestTemplate restTemplate() {
        OAuth2RestTemplate template = new OAuth2RestTemplate(oauth2ProtectedResourceDetails(), oauth2ClientContext);
        template.setRequestFactory(httpRequestFactory());
        template.setAccessTokenProvider(accessTokenProvider());
        return template;
    }   
}

我发现的一个重要方面是,即使对于单个提供程序,也需要使用AccessTokenProviderChain,否则自动令牌刷新(在身份验证之后)将无法工作.

One important bit I found is that you need to use the AccessTokenProviderChain even for a single Provider otherwise the automatic token refresh (after authentication) won't work.

要在第一个请求上设置用户凭据,您需要这样做:

To set the user credentials on the first request you'll need this:

@Autowired
private OAuth2RestTemplate restTemplate;

restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("username", username);
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("password", password);

然后,您可以使用RestTemplate方法正常发出请求,例如:

Then you can issue requests as normal using the RestTemplate methods, e.g:

    String url = "http://localhost:{port}/api/users/search/findByUsername?username={username}";

    ResponseEntity<User> responseEntity = restTemplate.getForEntity(
            url, User.class, 8081, username);

如果要跟踪网络上的请求,可以将apache http客户端上的日志级别设置为DEBUG,例如使用Spring Boot:

If you want to trace the requests on the wire you can set the log level on apache http client to DEBUG, e.g. with Spring Boot:

logging.level.org.apache.http = DEBUG

logging.level.org.apache.http=DEBUG

这篇关于Spring Security中具有密码授予权限的oAuth2客户端的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆