[英]Oauth2 password grant for Web Client against REST server using spring security
[英]oAuth2 client with password grant in Spring Security
我正在使用一組受oAuth2保護的服務。 它目前的工作方式如下:客戶端使用用戶名和密碼登錄。 我交換這些代幣。 我將令牌保留在會話中,並在每次要呼叫服務時提交。 它可以工作,但問題是我完全手動執行此操作,而不使用Spring Security oAuth2支持。 以下是它的外觀:
<!-- 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
,從而將其保留在安全上下文中。
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配置)以更少配置的代碼方式實現相同的功能。 我發現的大多數例子都包括我不關心的oAuth服務器實現,只是讓我困惑。
有人可以幫我這個嗎?
我通過瀏覽Spring Security OAuth源代碼以及在線發現的其他解決方案的各個部分,搗碎了類似的解決方案。 我正在使用Java Config,但也許它可以幫助您映射到xml配置,在這里:
@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;
}
}
我發現的一個重要方面是,即使對於單個Provider,您也需要使用AccessTokenProviderChain,否則自動令牌刷新(在身份驗證之后)將不起作用。
要在第一個請求上設置用戶憑據,您需要:
@Autowired
private OAuth2RestTemplate restTemplate;
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("username", username);
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("password", password);
然后,您可以使用RestTemplate方法正常發出請求,例如:
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:
logging.level.org.apache.http = DEBUG
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.