之前的操作已经针对单个服务全局添加了tenantId,这一步是针对在进行微服务相互调用时将tenantId传递给其他服务,由于之前将tenantId置入了请求头中,这里使用feign拦截器获取tenantId并重新置入请求头。说明一下token的问题,如果没有开启新的线程那么请求头中的token是会进行传递的,如果是开启新的线程调用其他微服务,请求头会丢失,下面代码将一并解决这两个问题
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
@Slf4j
@Configuration
public class TokenConfig implements RequestInterceptor {
private static final String AUTHORIZATION = "Authorization";
// private static final String TENANTID = "tenantId";
/**
* 不需要拦截的请求
*/
private List<String> noAuthUrlList = Arrays.asList(
"/userClient/listByIdsWithoutAuthorization",
"/tenantclient/getTenantInfoForInner/",
"/oauth/token",
"/ticketTemplate/selectByIdNoAuth"
);
@Resource
private TenantClient tenantClient;
@Resource
private UserClient userClient;
@Resource
private RemoteOauth2Client remoteOauth2Client;
public TokenConfig(){
super();
}
/**
* 根据租户获取token
*
* @return
*/
public String getTokenByTenantId() {
String userId = (String) ThreadLocalUtil.get("userId");
String tenantId = (String) ThreadLocalUtil.get(ThreadLocalUtil.TENANT_ID);
String tenantIdUnSubmit = (String) ThreadLocalUtil.get(ThreadLocalUtil.TENANT_ID, false);
if (StringUtils.isEmpty(tenantId) && StringUtils.isNotEmpty(tenantIdUnSubmit)) {
tenantId = tenantIdUnSubmit;
}
String realToken = "";
OauthVO oauthVO = null;
try {
String userName = "";
//优先通过用户获取token
if(org.apache.commons.lang3.StringUtils.isNotEmpty(userId)) {
log.info("userClient->listByIdsWithoutAuthorization 入参:" + JSON.toJSONString(userId));
RestResult<List<User>> listRestResult = userClient.listByIdsWithoutAuthorization(Lists.newArrayList(userId));
log.info("userClient->listByIdsWithoutAuthorization 出差:" + JSON.toJSONString(listRestResult));
if (RestUtil.dataExistsFlag(listRestResult) && CollectionUtils.isNotEmpty(listRestResult.getResultData())) {
userName = listRestResult.getResultData().get(0).getLoginName();
}
}
//如果用户获取不到或者每有用户id则通过租户id获取
if (org.apache.commons.lang3.StringUtils.isEmpty(userName) && org.apache.commons.lang3.StringUtils.isNotEmpty(tenantId)) {
log.info("tenantClient->getTenantInfoForInner 入参:" + tenantId);
RestResult<TenantVO> tenantInfoForInner = tenantClient.getTenantInfoForInner("Y", tenantId);
log.info("tenantClient->getTenantInfoForInner 出参:" + JSON.toJSONString(tenantInfoForInner));
if (RestUtil.dataExistsFlag(tenantInfoForInner)) {
userName = tenantInfoForInner.getResultData().getAccountName();
}
}
if(StringUtils.isEmpty(userName)){
return null;
}
//通过用户名免密登录获取token
log.info("remoteOauth2Client->oauthToken 入参:" + userName);
oauthVO = remoteOauth2Client.oauthToken("Basic d2F0ZXJ3b3JrOndwZ0AyMDIw", "read", "non_password", userName, "");
log.info("remoteOauth2Client->oauthToken 出参:" + oauthVO);
} catch (Exception e) {
log.error("自动登录获取token异常", e);
}
if (Objects.nonNull(oauthVO)) {
String accessToken = oauthVO.getAccessToken();
if(StringUtils.isEmpty(accessToken)){
accessToken = oauthVO.getResultData().getToken();
}
if(StringUtils.isEmpty(accessToken)){
log.error("获取的token为空");
}
realToken = "Bearer " + accessToken;
}
return realToken;
}
@Override
public void apply(RequestTemplate requestTemplate) {
//feign接口调用时将tenantId置入请求头
String tenantId = (String) ThreadLocalUtil.get(ThreadLocalUtil.TENANT_ID);
String tenantIdUnSubmit = (String) ThreadLocalUtil.get(ThreadLocalUtil.TENANT_ID, false);
if (StringUtils.isEmpty(tenantId) && StringUtils.isNotEmpty(tenantIdUnSubmit)) {
tenantId = tenantIdUnSubmit;
}
if(StringUtils.isNotEmpty(tenantId)){
//log.info("apply==========="+Thread.currentThread()+"======================="+tenantId);
requestTemplate.header(ThreadLocalUtil.TENANT_ID,tenantId);
}
String url = requestTemplate.url();
//过滤除不需要拦截处理的url
if(url.contains("/selectByIdNoAuth")) {
log.info("查询工单模板路径:" + url);
}
if (noAuthUrlList.contains(url.contains("?") ? url.split("\\?")[0] : url) || url.contains("/tenantclient/getTenantInfoForInner") || url.contains("/ticketTemplate/selectByIdNoAuth")){
return;
}
if(requestTemplate.headers().containsKey(AUTHORIZATION) || requestTemplate.headers().containsKey("authorization")){
return;
}
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if(attributes==null){
String token = this.getTokenByTenantId();
if(!org.springframework.util.StringUtils.isEmpty(token)) {
requestTemplate.header(AUTHORIZATION, token);
}
} else {
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
if(AUTHORIZATION.equals(name)||"authorization".equals(name)){
requestTemplate.header(name,values);
return;
}
}
String token = this.getTokenByTenantId();
requestTemplate.header(AUTHORIZATION, token);
}
}
}
}
再简单介绍下outh2,这也是我们公司使用的鉴权方式
一、首先来看一下OAuth2.0的原理
OAuth 2.0 是一种授权机制,主要用来颁发令牌(token)。而在传统的客户端-服务端授权模型中,客户端想要请求服务端受保护的资源就必须通过资源拥有者的凭证。但是为了提供给第三方应用访问资源的权限,那么资源拥有者就必须把这个凭证共享给第三方,此时会出现几个问题:
1、第三方应用需要存储资源拥有者的凭据以供以后使用,通常密码以明文形式。
2、服务端需要支持密码认证,尽管这种来自密码的安全性比较弱。
3、第三方应用获得资源的广泛的权限,是的资源拥有者不能限制访问时间和权限。
4、资源拥有者不能中断单个第三方的访问权限,除非中断所有的第三方权限,这时就必须修改密码了。
OAuth通过引入authorization层解决了这些问题,并且将资源拥有者和客户端这些角色分离开来。替代使用资源拥有者的凭据,而是使用一个权限令牌token,它具有具体的范围限制,生命周期,以及其他的权限属性。
OAuth定义了四个角色:分别是resource owner、resource server、client、authorization server。
resource owner 是可以授权资源权限的一个实体,一般指的是终端用户;
resource server 是部署资源的服务器,它可以接收并且响应对资源的携带token的请求;
client 是指应用,比如服务端应用,桌面应用,或者其他的设备应用;
authorization server 它是发行token给client的服务器;
另外想说token可以设置时效,如果的一般的多线程使用微服务接口调用,可以在第一次获取token之后缓存起来,在调用的时候先进行拦截之后再设置。目前我们公司也会给应用本身设置获取token的方式和权限,比如我这边开发的是核心业务(想统计用户数据等),就可以直接根据应用code本身直接获取用户信息进行免密登录获取token。