乐趣区

Spring Cloud OAuth 微服务内部Token传递的源码实现解析

背景分析

1. 客户端携带认证中心发放的 token, 请求资源服务器 A(Spring Security OAuth 发放 Token 源码解析)
2. 客户端携带令牌直接访问资源服务器,资源服务器通过对 token 的校验([Spring Cloud OAuth2 资源服务器 CheckToken 源码解析

](https://my.oschina.net/giegie…)判断用户的合法性,并保存到上下文中
3.A 服务接口接收到请求,需要通过 Feign 或者其他 RPC 框架调用 B 服务来组装返回数据
本文主要来探讨第三部 A –> B,token 自定维护的源码实现
如何实现 token 传递
配置 OAuth2FeignRequestInterceptor 即可
此类是 Feign 的拦截器实现

@Bean
@ConditionalOnProperty(“security.oauth2.client.client-id”)
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext,
OAuth2ProtectedResourceDetails resource,) {
return new OAuth2FeignRequestInterceptor(oAuth2ClientContext, resource);
}
源码解析
获取上下文中的 token,组装到请求头
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
// 给请求增加 token
@Override
public void apply(RequestTemplate template) {
template.header(header, extract(tokenType));
}

protected String extract(String tokenType) {
OAuth2AccessToken accessToken = getToken();
return String.format(“%s %s”, tokenType, accessToken.getValue());
}

// 从 spring security 上下文中获取 token
public OAuth2AccessToken getToken() {

OAuth2AccessToken accessToken = oAuth2ClientContext.getAccessToken();
if (accessToken == null || accessToken.isExpired()) {
try {
accessToken = acquireAccessToken();
}
}
return accessToken;
}

}
再来看 AccessTokenContextRelay,上下文 token 中转器. 非常简单从上下文获取认证信息得到把 token 放到上下文
public class AccessTokenContextRelay {

private OAuth2ClientContext context;

public AccessTokenContextRelay(OAuth2ClientContext context) {
this.context = context;
}

public boolean copyToken() {
if (context.getAccessToken() == null) {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication != null) {
Object details = authentication.getDetails();
if (details instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails holder = (OAuth2AuthenticationDetails) details;
String token = holder.getTokenValue();
DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(
token);
String tokenType = holder.getTokenType();
if (tokenType != null) {
accessToken.setTokenType(tokenType);
}
context.setAccessToken(accessToken);
return true;
}
}
}
return false;
}

}
什么时候执行中转,oauth2 资源服务器非常简单暴力,加了个拦截器给转发。

源码非常简单
谈谈 spring security oauth 实现的问题

当请求上线文没有 Token, 如果调用 feign 会直接,这个 OAuth2FeignRequestInterceptor 肯定会报错,因为上下文 copy 失败
如果设置线程隔离,这里也会报错。导致安全上下问题传递不到子线程中。
强制使用拦截器去处理 token 转发到这里上下文,使用的业务场景只有这里,影响性能高

这三个问题,大家在使用的过程中一定会遇到
自定义 OAuth2FeignRequestInterceptor
通过外部条件是否执行 token 中转
public void apply(RequestTemplate template) {
Collection<String> fromHeader = template.headers().get(SecurityConstants.FROM);
if (CollUtil.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN)) {
return;
}

accessTokenContextRelay.copyToken();
if (oAuth2ClientContext != null
&& oAuth2ClientContext.getAccessToken() != null) {
super.apply(template);
}
}
手动调用 accessTokenContextRelay 的 copy,当然需要覆盖原生 oauth 客户端的配置

总结

以上源码参考个人项目 基于 Spring Cloud、OAuth2.0 开发基于 Vue 前后分离的开发平台

QQ: 2270033969 一起来聊聊你们是咋用 spring cloud 的吧。

欢迎关注我们获得更多的好玩 JavaEE 实践

退出移动版