服务之间鉴权,只能从网关访问,禁止直接访问服务
前言
在开发过程中,网关是一个很重要的角色,在网关中可以添加各种过滤器,过滤请求,保证请求参数安全,限流等等。如果请求绕过了网关,那就等于绕过了重重关卡,直捣黄龙
在分布式架构的系统中,每个服务都有自己的一套API提供给别的服务调用,如何保证每个服务相互之间安全调用?
思路
- 1、所有接口都要通过认证才能访问,有时候又有需求,不需要认证就可以访问。
- 2、在网关中给所有请求加上一个请求密钥
- 3、在服务添加过滤器,验证密钥
本文介绍如何限制请求绕过网关,直接访问服务
1、所有服务统一加上过滤器,验证标识头(secretKey)
/**
* @Description //TODO $全局过滤
* @Date 21:35
* @Author yzcheng90@qq.com
**/
@Slf4j
public class GlobalInterceptor implements HandlerInterceptor {
@Getter
@Setter
private RedisUUID redisUUID;
@Getter
@Setter
private AuthIgnoreConfig authIgnoreConfig;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object obj) throws Exception {
long exist = authIgnoreConfig.getIgnoreUrls().stream().filter(url-> url.trim().equals(request.getRequestURI())).count();
if(exist != 0){
return true;
}
String secretKey = request.getHeader(SecurityConstants.SECRET_KEY);
if(StrUtil.isNotBlank(secretKey)){
String key = (String) redisUUID.get(SecurityConstants.SECRET_KEY);
if(!StrUtil.isBlank(key) && secretKey.equals(key)){
return true;
}
}
response.setContentType("application/json; charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(JSONUtil.toJsonStr(R.error("illegal request")));
return false;
}
}
2、让过滤器生效
/**
* @Description //TODO $
* @Date 21:46
* @Author yzcheng90@qq.com
**/
@Configurable
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private RedisUUID redisUUID;
@Autowired
private WebApplicationContext applicationContext;
@Override
public void addInterceptors(InterceptorRegistry registry) {
GlobalInterceptor interceptor = new GlobalInterceptor();
interceptor.setRedisUUID(redisUUID);
interceptor.setAuthIgnoreConfig(applicationContext.getBean(AuthIgnoreConfig.class));
registry.addInterceptor(interceptor).addPathPatterns("/**");
}
}
3、生成唯一KEY 保存到redis 过期时间根据情况设置,这里设置为5分钟
/**
* @author czx
* @title: RedisUUID
* @projectName ms
* @description: TODO 配置唯一ID保存到redis
* @date 2019/7/2614:33
*/
@Component
public class RedisUUID {
@Autowired
private RedisTemplate redisTemplate;
// 过期时间
private final static long expiration = 1000 * 60 * 5;
// 过期前1分钟
private final static long lastTime = 1000 * 60;
public String create(String key){
if(StrUtil.isBlank(key)){
return null;
}
String secretKey;
if(redisTemplate.hasKey(key)){
if(redisTemplate.boundHashOps(key).getExpire() < lastTime){
redisTemplate.opsForValue().set(key,SecureUtil.md5(UUID.randomUUID().toString()),expiration,TimeUnit.SECONDS);
}
secretKey = (String) redisTemplate.opsForValue().get(key);
}else{
secretKey = SecureUtil.md5(UUID.randomUUID().toString());
redisTemplate.opsForValue().set(key,secretKey,expiration,TimeUnit.SECONDS);
}
return secretKey;
}
public Object get(String key){
return redisTemplate.opsForValue().get(key);
}
}
4、在网关统全局过滤器中设置所有请求都加上 验证标实头
/**
* @Description //TODO 全局过滤
* @Date 22:20
* @Author yzcheng90@qq.com
**/
@Component
public class RequestGlobalFilter implements GlobalFilter, Ordered {
@Autowired
private RedisUUID redisUUID;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String secretKey = redisUUID.create(SecurityConstants.SECRET_KEY);
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(httpHeaders -> httpHeaders.remove(SecurityConstants.SECRET_KEY))
.build();
ServerHttpRequest newRequest = request.mutate().header(SecurityConstants.SECRET_KEY,secretKey).build();
return chain.filter(exchange.mutate().request(newRequest.mutate().build()).build());
}
@Override
public int getOrder() {
return -999;
}
}
具体代码:传送门 : https://github.com/yzcheng90/ms