我正在使用一组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
}
如您所见,它基本上确保令牌在安全范围内,我可以在每次调用后端服务之前手动提取它。同样,在每次调用之前,我会检查令牌的新鲜度。
这种方法很有效,但我相信我可以使用Spring的oauth命名空间在XML中(我没有使用Java配置)以更少的代码方式实现相同的效果。我找到的大多数示例都包括我不关心的oAuth服务器实现,使我感到困惑。
请问是否有人可以帮助我?
ResourceOwnerPasswordResourceDetails
实例的details
上使用details.setClientAuthenticationScheme(AuthenticationScheme.form);
,这样认证数据就会通过 HTTP 正文发送,而不是使用头部。 - Bernhard