一、Gateway鉴权实现方案
网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 网关来做,这样既提高业务灵活性又不缺安全性。
RBAC(Role-Based Access Control)基于角色访问控制,目前使用最为广泛的权限模型。相信大家对这种权限模型已经比较了解了。此模型有三个用户、角色和权限,在传统的权限模型用户直接关联加了角色,解耦了用户和权限,使得权限系统有了更清晰的职责划分和更高的灵活度
1、添加依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2、实现代码
@Configuration
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Autowired
JwtTokenUtil jwtTokenUtil;
@Autowired(required = false)
JedisUtil jedisUtil;
private String cachePrefix = "km-gateway-";
@Value("${spring.redis.expired}")
private Integer expiredSecond;//600000,10m
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders httpHeaders = request.getHeaders();
exchange.getRequest().getURI();
String requestUri = request.getPath().pathWithinApplication().value();
String token = null;
if (httpHeaders != null && httpHeaders.containsKey("token") && !httpHeaders.get("token").isEmpty()) {
token = httpHeaders.get("token").get(0);
}
// AuthenticateRequest
if (StringUtil.isBlank(token)) {
// String message = "You current request uri do not have permission or auth.";
// return getVoidMono(exchange, message);
return chain.filter(exchange);
}
String userAccountId = jwtTokenUtil.getUserAccountIdFromToken(token);
boolean hasPermission = checkPermission(userAccountId, requestUri);
String username = jwtTokenUtil.getUsernameFromToken(token);
String redisSetUrlKey = cachePrefix.concat("url-").concat(username);
// log.info("###### hasPermission.2=" + hasPermission);
if (hasPermission) {
jedisUtil.SetAndTime(redisSetUrlKey, expiredSecond, requestUri);
} else {
String message = "You current request uri do not have permission or auth.";
// log.warn(message);
return getVoidMono(exchange, message);
}
jwtTokenUtil.isValid(token);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
//根据角色权限进行权限控制
private boolean checkPermission(String userId, String requestUrl) {
return false;
}
private Mono<Void> getVoidMono(ServerWebExchange exchange, String body) {
exchange.getResponse().setStatusCode(HttpStatus.OK);
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return exchange.getResponse().writeWith(Flux.just(buffer));
}
}
@Configuration
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Autowired
JwtTokenUtil jwtTokenUtil;
@Autowired(required = false)
JedisUtil jedisUtil;
private String cachePrefix = "km-gateway-";
@Value("${spring.redis.expired}")
private Integer expiredSecond;//600000,10m
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders httpHeaders = request.getHeaders();
exchange.getRequest().getURI();
String requestUri = request.getPath().pathWithinApplication().value();
String token = null;
if (httpHeaders != null && httpHeaders.containsKey("token") && !httpHeaders.get("token").isEmpty()) {
token = httpHeaders.get("token").get(0);
}
// AuthenticateRequest
if (StringUtil.isBlank(token)) {
// String message = "You current request uri do not have permission or auth.";
// return getVoidMono(exchange, message);
return chain.filter(exchange);
}
String userAccountId = jwtTokenUtil.getUserAccountIdFromToken(token);
boolean hasPermission = checkPermission(userAccountId, requestUri);
String username = jwtTokenUtil.getUsernameFromToken(token);
String redisSetUrlKey = cachePrefix.concat("url-").concat(username);
// log.info("###### hasPermission.2=" + hasPermission);
if (hasPermission) {
jedisUtil.SetAndTime(redisSetUrlKey, expiredSecond, requestUri);
} else {
String message = "You current request uri do not have permission or auth.";
// log.warn(message);
return getVoidMono(exchange, message);
}
jwtTokenUtil.isValid(token);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
//根据角色权限进行权限控制
private boolean checkPermission(String userId, String requestUrl) {
return false;
}
private Mono<Void> getVoidMono(ServerWebExchange exchange, String body) {
exchange.getResponse().setStatusCode(HttpStatus.OK);
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return exchange.getResponse().writeWith(Flux.just(buffer));
}
}
二、Gateway跨域解决方案
在SpringCloud项目中,前后端分离目前很常见,在调试时会遇到前端页面通过不同域名或IP访问微服务的后台,此时,如果不加任何配置,前端页面的请求会被浏览器跨域限制拦截,所以,业务服务常常会添加跨域配置
1、配置类实现
@Configuration
public class GulimallCorsConfiguration {
/**
* 添加跨域过滤器
* @return
*/
@Bean
public CorsWebFilter corsWebFilter(){
//基于url跨域,选择reactive包下的
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 跨域配置信息
CorsConfiguration configuration = new CorsConfiguration();
// 允许跨域的头
configuration.addAllowedHeader("*");
// 允许跨域的请求方式
configuration.addAllowedMethod("*");
// 允许跨域的请求来源
configuration.addAllowedOrigin("*");
// 是否允许携带cookie跨域
configuration.setAllowCredentials(true);
// 任意url都要进行跨域配置
source.registerCorsConfiguration("/**", configuration);
return new CorsWebFilter(source);
}
}
@Configuration
public class GulimallCorsConfiguration {
/**
* 添加跨域过滤器
* @return
*/
@Bean
public CorsWebFilter corsWebFilter(){
//基于url跨域,选择reactive包下的
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 跨域配置信息
CorsConfiguration configuration = new CorsConfiguration();
// 允许跨域的头
configuration.addAllowedHeader("*");
// 允许跨域的请求方式
configuration.addAllowedMethod("*");
// 允许跨域的请求来源
configuration.addAllowedOrigin("*");
// 是否允许携带cookie跨域
configuration.setAllowCredentials(true);
// 任意url都要进行跨域配置
source.registerCorsConfiguration("/**", configuration);
return new CorsWebFilter(source);
}
}
注: SpringCloudGateWay中跨域配置不起作用,原因是SpringCloudGetway是 Springwebflux的而不是SpringWebMvc的,所以我们需要导入的包导入错了
2、配置文件配置
server:
port: 10010
spring:
application:
name: gatewayservice
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "https://www.xx.com" # 允许那些网站跨域访问
allowedMethods: "GET" # 允许那些Ajax方式的跨域请求
allowedHeaders: "*" # 允许请求头携带信息
allowCredentials: "*" # 允许携带cookie
maxAge: 360000 # 这次跨域有效期于相同的跨域请求不会再预检
server:
port: 10010
spring:
application:
name: gatewayservice
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "https://www.xx.com" # 允许那些网站跨域访问
allowedMethods: "GET" # 允许那些Ajax方式的跨域请求
allowedHeaders: "*" # 允许请求头携带信息
allowCredentials: "*" # 允许携带cookie
maxAge: 360000 # 这次跨域有效期于相同的跨域请求不会再预检