场景描述
使用jeecg搭建SpringCloud微服务系统模块,各个系统模块单独创建了拦截器进行权限校验。结果发现跨微服务调用存在鉴权失败问题。不能正常跨微服务调用。
原因描述
单个微服务鉴权拦截器。
package org.jeecg.modules.taxation.inerceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 配置请求拦截器
*/
@Configuration
public class TaxAppInterceptorConfig implements WebMvcConfigurer {
@Bean
public TaxAppInterceptor appInterceptor() {
return new TaxAppInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 这里的拦截器是new出来的,在Spring框架中可以交给IOC进行依赖注入,直接使用@Autowired注入
registry.addInterceptor(appInterceptor())
//授权接口
.addPathPatterns("/api/tax/**")
//排除接口
//用户
.excludePathPatterns("/api/tax/member/**")
//申报
.excludePathPatterns("/api/tax/declare/**")
//if
.excludePathPatterns("/wx/callback/alipayCallback");
}
}
小程序等用户请求,拦截器
/**
* 小程序等用户请求,拦截器
*/
@Component
public class TaxAppInterceptor implements HandlerInterceptor {
... 省略代码
}
解决方案
方案1:更改FeignConfig,添加token传递,jeecg原生jar包,这个修改不了。
方案2:替换FeignConfig,添加token传递, 类冲突,无法替换, allow-circular-references 无效。
方案3:抽取每个服务的拦截器到网关统一验证。如果放行shiroConfig,用网关鉴权,可能存在单jar包,直接访问问题,
所以①不能放行shiroConfig,②更改shiro拦截代码,③添加网关统一校验。
方案4:服务端的filter区分客户端是feign还是前端浏览器,是feign就不鉴权了
方案5:feign能设置全局默认传的参数,可以加一些参数让服务端识别出是feign。
目前为了时间,暂时使用的方案2,构架层面应该使用方案3,统一网关鉴权。
配置FeignClient的configuration解决跨微服务token共享鉴权问题
调用端声明
package org.jeecg.modules.ccb.api;
import org.jeecg.common.constant.ServiceNameConstants;
import org.jeecg.modules.ccb.api.fallback.CcbHelloFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(value = ServiceNameConstants.SERVICE_TAXATION,configuration = FeignHelloConfiguration.class,fallbackFactory = CcbHelloFallback.class)
public interface TaxationApi {
/**
* taxation hello 微服务接口
* @param
* @return
*/
@GetMapping(value = "/api/tax/hello")
String callHello();
}
Feign配置类
重点是:
String token1 = request.getHeader(“token”);
requestTemplate.header(“token”, new String[]{token1});
package org.jeecg.modules.ccb.api;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.alibaba.fastjson.support.springfox.SwaggerJsonSerializer;
import feign.Contract;
import feign.RequestInterceptor;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.jeecg.common.config.TenantContext;
import org.jeecg.common.config.mqtoken.UserTokenContext;
import org.jeecg.common.util.PathMatcherUtil;
import org.jeecg.config.sign.util.HttpUtils;
import org.jeecg.config.sign.util.SignUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
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.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.SortedMap;
public class FeignHelloConfiguration {
private static final Logger log = LoggerFactory.getLogger(FeignHelloConfiguration.class);
public static final String X_ACCESS_TOKEN = "X-Access-Token";
public static final String X_SIGN = "X-Sign";
public static final String X_TIMESTAMP = "X-TIMESTAMP";
public static final String TENANT_ID = "tenant-id";
public FeignHelloConfiguration() {
}
@Bean
public RequestInterceptor requestInterceptor() {
return (requestTemplate) -> {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String token;
String queryLine;
String signUrls;
if (null != attributes) {
HttpServletRequest request = attributes.getRequest();
//token传递
String token1 = request.getHeader("token");
requestTemplate.header("token", new String[]{token1});
log.debug("Feign request: {}", request.getRequestURI());
token = request.getHeader("X-Access-Token");
if (token == null || "".equals(token)) {
token = request.getParameter("token");
}
log.info("Feign Login Request token: {}", token);
requestTemplate.header("X-Access-Token", new String[]{token});
queryLine = request.getHeader("tenant-id");
if (queryLine == null || "".equals(queryLine)) {
queryLine = request.getParameter("tenant-id");
}
log.info("Feign Login Request tenantId: {}", queryLine);
requestTemplate.header("tenant-id", new String[]{queryLine});
} else {
signUrls = UserTokenContext.getToken();
log.info("Feign no Login token: {}", signUrls);
requestTemplate.header("X-Access-Token", new String[]{signUrls});
token = TenantContext.getTenant();
log.info("Feign no Login tenantId: {}", token);
requestTemplate.header("tenant-id", new String[]{token});
}
};
}
@Bean
feign.Logger.Level feignLoggerLevel() {
return feign.Logger.Level.FULL;
}
@Bean
@Primary
@Scope("prototype")
public Encoder multipartFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
@Bean
public Encoder feignEncoder() {
return new SpringEncoder(this.feignHttpMessageConverter());
}
@Bean
public Decoder feignDecoder() {
return new SpringDecoder(this.feignHttpMessageConverter());
}
private ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
HttpMessageConverters httpMessageConverters = new HttpMessageConverters(new HttpMessageConverter[]{this.getFastJsonConverter()});
return () -> {
return httpMessageConverters;
};
}
private FastJsonHttpMessageConverter getFastJsonConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList();
MediaType mediaTypeJson = MediaType.valueOf("application/json");
supportedMediaTypes.add(mediaTypeJson);
converter.setSupportedMediaTypes(supportedMediaTypes);
FastJsonConfig config = new FastJsonConfig();
config.getSerializeConfig().put(JSON.class, new SwaggerJsonSerializer());
config.setSerializerFeatures(new SerializerFeature[]{SerializerFeature.DisableCircularReferenceDetect});
converter.setFastJsonConfig(config);
return converter;
}
}
被调用端拦截器
重点是获取刚刚传递过来的token值
request.getHeader(“token”);
/**
* 小程序等用户请求,拦截器
*/
@Component
public class TaxAppInterceptor implements HandlerInterceptor {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Value("${jwt.secret}")
private String jwtSecret;
/**
* 查数据库登录验证拦截
*
* @param request
* @param response
* @param o
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
String requestURI = request.getRequestURI();
String token = request.getHeader("token");