多租户实现(3)feign调用拦截器和outh2

之前的操作已经针对单个服务全局添加了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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值