网关这边主要两个运用:
关键配置流程:
1、pom.xml(spring boot 2.0.2 RELEASE/spring cloud Finchley.RELEASE)
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-gateway
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
2、url白名单配置
/**
* url匹配工具
*
* @author tums
*/
public class UrlResolver {
private final static PathMatcher MATCHER = new AntPathMatcher();
/**
* 验证url是否匹配,支持精确匹配和模糊匹配
*
* @param patternPaths
* @param requestPath
* @return
*/
public static boolean check(List patternPaths, String requestPath) {
for (String i : patternPaths) {
if (i.endsWith("*")) {
i = i.substring(0, i.length() - 1);
if (MATCHER.matchStart(requestPath, i)) {
return true;
}
}
if (MATCHER.match(i, requestPath)) {
return true;
}
}
return false;
}
}
/**
* 请求地址白名单,无需校验token
*
* @author tums
*/
@Configuration
public class UrlWhileList implements InitializingBean {
private final static List URL_LIST = new ArrayList();
@Override
public void afterPropertiesSet() throws Exception {
//后台-获取图形验证码
URL_LIST.add("/xxx1-service/v1/validateCode");
//APP登录注册
URL_LIST.add("/xxx2-service/v1/token/app/login/*");
URL_LIST.add("/xxx3-service/v1/token/app/register/*");
//网页登录注册
URL_LIST.add("/xxx4-service/v1/token/mweb/login/*");
URL_LIST.add("/xxx5-service/v1/token/mweb/register/*");
//获取短信验证码
URL_LIST.add("/xxx6-service/v1/message/login");
......
}
public static List getUrlList() {
return URL_LIST;
}
}
3、自定义异常配置,为了让进出网关的服务返回统一的状态码和异常信息
/**
* 自定义异常配置
*
* @author tums
* @date 2018/12/3 21:06
*/
@Configuration
public class ExceptionConfig {
/**
* 自定义异常处理[@@]注册Bean时依赖的Bean,会从容器中直接获取,所以直接注入即可
*/
@Primary
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
JsonExceptionHandler jsonExceptionHandler = new JsonExceptionHandler();
jsonExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
jsonExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
jsonExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
return jsonExceptionHandler;
}
}
/**
* 自定义异常处理
*
* @author tums
* @date 2018/12/3 21:04
*/
public class JsonExceptionHandler implements ErrorWebExceptionHandler {
/**
* MessageReader
*/
private List> messageReaders = Collections.emptyList();
/**
* MessageWriter
*/
private List> messageWriters = Collections.emptyList();
/**
* ViewResolvers
*/
private List viewResolvers = Collections.emptyList();
/**
* 存储处理异常后的信息
*/
private ThreadLocal
4、鉴权配置(jwt-token)
/**
* JWT 鉴权机制
* 注:每次请求头和响应头都会存储当前有效的token,白名单接口除外
* 1、JWT-Filter对登录/注册不鉴权,成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样)
* 2、当该用户这次请求JWTToken值还在生命周期内,且该token对应cache中的k存在,则会通过重新PUT的方式k、v都为Token值,缓存中的token值生命周期时间重新计算(这时候k、v值一样)
* 3、当该用户这次请求JWTToken值还在生命周期内,但该token对应cache中的k不存在,返回用户信息已失效,请重新登录。
* 4、当该用户这次请求jwt生成的token值已经超时,但该token对应cache中的k还是存在,则表示该用户一直在操作只是JWT的token失效了,程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值,该缓存生命周期重新计算
* 5、当该用户这次请求jwt生成的token值已经超时,且该token对应cache中的k不存在,则表示该用户账户空闲超时,返回用户信息已失效,请重新登录。
*
* @author tums
* @date 2018/11/10 20:10
*/
@Component
public class TokenFilter implements GlobalFilter, Ordered {
/**
* header中token的key
*/
private static final String LOGIN_TOKEN = "LOGIN_TOKEN";
private final static int TEST_TOKEN_EXPIRES_MILLISECONDS = 1000 * 24 * 3600;
/**
* 当前登录用户ID
*/
private static final String LOGIN_ID = "LOGIN_ID";
@Resource
private RedisService redisService;
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String url = request.getURI().getPath();
//白名单
if (UrlResolver.check(UrlWhileList.getUrlList(), url)) {
return chain.filter(exchange);
}
HttpHeaders httpHeaders = request.getHeaders();
List tokens = httpHeaders.get(LOGIN_TOKEN);
Assert.isTrue(!CollectionUtils.isEmpty(tokens), LOGIN_TOKEN + " 不能为空");
String token = tokens.get(0);
Claims claims = null;
try {
claims = JwtTokenHelper.parseJWT(token);
} catch (RuntimeException e) {
throw new TokenExpiredException("token过期,请重新登录");
}
Assert.isTrue(!(claims == null || claims.isEmpty()), LOGIN_TOKEN + " 无效");
String id = claims.getId();
Assert.isTrue(!StringUtils.isEmpty(id), LOGIN_TOKEN + " 无效");
Date expiration = claims.getExpiration();
Date today = new Date();
long expiresMilliseconds = expiration.getTime() - claims.getNotBefore().getTime();
//token 未过期,判断redis 缓存是否过期
if (today.getTime() <= expiration.getTime()) {
String cacheToken = redisService.getToken(token);
if (StringUtils.isEmpty(cacheToken)) {
throw new TokenExpiredException("token过期,请重新登录");
}
//重新刷新有效期k=y
redisService.setToken(token, expiresMilliseconds);
//token 已经过期,判断redis 缓存是否过期
} else {
String cacheToken = redisService.getToken(token);
if (StringUtils.isEmpty(cacheToken)) {
throw new TokenExpiredException("token过期,请重新登录");
}
//redis 未过期
token = JwtTokenHelper.createJWT(id, expiresMilliseconds);
redisService.setToken(token, expiresMilliseconds);
}
//响应Header 中增加可用的token
exchange.getResponse().getHeaders().add(LOGIN_TOKEN, token);
//请求链路中增加token 主键
ServerHttpRequest serverHttpRequest = request.mutate().header(LOGIN_ID, id).build();
ServerWebExchange serverWebExchange = exchange.mutate().request(serverHttpRequest).build();
return chain.filter(serverWebExchange);
}
@Override
public int getOrder() {
return 0;
}
}