前言
在搭建项目的时候,需要网关去校验请求的合法性。这里通过gateway的filter中通过调用feign接口去验证token的方式实现。
gateway简介及与springboot的区别
todo
项目配置
微服务配置
接口配置
在微服务中,只展示controller的代码,其他的自己实现就可以。
@ApiOperationSupport(order = 90, author = "alex")
@ApiOperation(value = "根据token校验用户权限", notes = "根据token校验用户权限", response = Result.class)
@GetMapping(value = "/authToken")
@ApiImplicitParams({
@ApiImplicitParam(value = "token", name = "token")}
)
public Result<Boolean> authToken(@RequestParam(value = "token") String token) {
return Result.success(true) ;
}
feign接口
在这里根据spring.profiles.active动态去配置服务名,方便区分测试环境和正式环境。
@Component
@FeignClient(name = "alex-user-${spring.profiles.active:dev}", fallback = UserFallbackFactory.class, configuration = FeignConfig.class)
public interface UserApi {
@GetMapping(value = "/api/v1/user/authToken")
Result<Boolean> authToken(@RequestParam("token") String token);
}
feign异常处理类。
@Component
@Slf4j
public class UserFallbackFactory implements FallbackFactory<UserApi> {
@Override
public UserApi create(Throwable cause) {
throw new SystemException(ResultEnum.SYSTEM_NO_AVAILABLE, "user");
}
}
配置feign配置
@Configuration
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
//添加token
requestTemplate.header("Authorization", request.getHeader("Authorization"));
}
}
@Bean
public Logger.Level level() {
//仅记录请求方法、URL、响应状态代码以及执行时间,生成一般用这个
return Logger.Level.BASIC;
}
@Bean
public RequestContextListener requestContextListener(){
return new RequestContextListener();
}
}
gateway配置
在GatewayApplication中配置EnableFeignClients调用feign。
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@ComponentScan(basePackages = {"com.alex.gateway", "com.alex.common", "com.alex.api.user"})
@EnableFeignClients(basePackages = {"com.alex.api.user"})
public class GatewayApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(GatewayApplication.class, args);
AutowiredBean.setApplicationContext(run);
}
}
@Component
@Slf4j
@RequiredArgsConstructor
public class GatewayFilter implements GlobalFilter, Ordered {
private final GatewayAudience audience;
private static final PathMatcher antPathMatcher = new AntPathMatcher();
@SneakyThrows
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getPath().toString();
//白名单校验路径
if (audience.getWhiteList() != null && !audience.getWhiteList().isEmpty()) {
for (String white : audience.getWhiteList()) {
if (antPathMatcher.match(white, path)) {
return chain.filter(exchange);
}
}
}
log.info("当前请求地址:{}", path);
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
Map<String, Object> attributes1 = exchange.getAttributes();
//得到请求头信息authorization信息
String token = Optional.ofNullable(request)
.map(re -> re.getHeaders())
.map(header -> header.getFirst(audience.getTokenHeader()))
.orElse(null);
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
UserApi userApi = AutowiredBean.getBean(UserApi.class);
CompletableFuture<Result<Boolean>> completableFuture = CompletableFuture.supplyAsync(() -> {
// 复制主线程的 线程共享数据
RequestContextHolder.setRequestAttributes(attributes);
Result<Boolean> res = userApi.authToken(token);
return res;
}
);
Boolean result = Optional.ofNullable(completableFuture).map(item -> {
try {
return item.get().getData();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}).get();
if (result) {
return chain.filter(exchange);
}
return out(response);
}
/**
* @description: 拦截器的优先级,数字越小优先级越高
* @author: majf
* @return: int
*/
@Override
public int getOrder() {
return 0;
}
private Mono<Void> out(ServerHttpResponse response) {
JsonObject message = new JsonObject();
message.addProperty("success", false);
message.addProperty("code", 403);
message.addProperty("data", "请先登录!");
byte[] bits = message.toString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
//response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定编码,否则在浏览器中会中文乱码
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
}
public class AutowiredBean {
private static ApplicationContext applicationContext;
public static void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (AutowiredBean.applicationContext == null) {
AutowiredBean.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
总结
至此在网关中调用feign接口配置完成。
源码地址
Q&A
todo