GitHub轻松阅读微服务实战项目流程详解第二天:API网关的设计与实现
Posted Roninaxious
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GitHub轻松阅读微服务实战项目流程详解第二天:API网关的设计与实现相关的知识,希望对你有一定的参考价值。
Two Day
github地址:https://github.com/Zealon159/light-reading-cloud
该网关层面使用Spring GateWay进行实现。
1.配置文件精解
(1)bootstrap.yml文件
spring:
application:
# 服务逻辑名称
name: light-reading-cloud-gateway
cloud:
nacos:
# 配置中心
config:
server-addr: xxxxxx
file-extension: yml
refresh: true
shared-dataids: light-reading-cloud-gateway.yml #Data ID
namespace: 4d109a4d-f34d-4e86-9e39-c2d36db24b00 #命名空间
# 注册中心
discovery:
server-addr: xxxxx
namespace: 4d109a4d-f34d-4e86-9e39-c2d36db24b00
(2)nacos中关于gateway的配置信息
server:
port: 8010
spring:
application:
# 服务逻辑名称
name: light-reading-cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启gateway的注册和发现
lowerCaseServiceId: true #将请求路径中的服务名小写,因为服务注册时,Nacos将其转换成大写了
routes: #路由匹配
- id: book-center-rpc
uri: lb://light-reading-cloud-book #服务地址
predicates: #断言,匹配路径和请求方式
- Path=/book/**
- Method=GET
filters:
# 降级配置
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallback #失败了直接跳转到该路据,这个接口是用户失败后快速返回的
# 限流配置
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 3 # 每秒允许处理的请求数量
redis-rate-limiter.burstCapacity: 5 # 每秒最大处理的请求数量
key-resolver: "#{@ipKeyResolver}" # 限流策略,对应策略的Bean,在gateway配置了该IP的限流策略
- id: homepage-rpc
uri: lb://light-reading-cloud-homepage
predicates:
- Path=/index/**
filters:
# 降级配置
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallback
# 限流配置
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 3
redis-rate-limiter.burstCapacity: 5
key-resolver: "#{@ipKeyResolver}"
- id: account-center-rpc
uri: lb://light-reading-cloud-account
predicates:
- Path=/account/**
filters:
# 降级配置
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallback
# 限流配置
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 3
redis-rate-limiter.burstCapacity: 5
key-resolver: "#{@ipKeyResolver}"
hystrix:
threadpool:
default:
coreSize: 20 #并发执行的最大线程数,默认10
maxQueueSize: 1000 #BlockingQueue的最大队列数,默认值-1
queueSizeRejectionThreshold: 400
ribbon:
eager-load:
enabled: true #开启Ribbon的饥饿模式, 用于点对点直连问题
clients: light-reading-cloud-account,light-reading-cloud-book,light-reading-cloud-homepage
(3)applicaton.properties白名单配置
system.properties=/account/user/register,/account/user/login
这个本地application.properties是配置的白名单信息,后面会在配置类中进行加载。
2.代码详解
(1)IP限流
@Configuration
public class RedisRateLimiterConfig {
/**
* 按客户端IP限流
* Lambda表达式或者匿名函数都可以快速实现
* @return
*/
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
}
这个ipKeyResolver方法是在gateway配置文件中引用的
(2)白名单
@Data
@Component
public class SystemPropertiesConfig {
/** 请求白名单,引用application.preperties中的属性 */
@Value("${system.properties}")
private String whitelist;
}
(3)Jwt工具类验证token的有效性
Token比较适用于微服务的安全认证,JWT是一种安全认证规范,token中存储了用户信息,只有在服务端才能根据密钥进行解密。
public class JwtUtil {
/**
* 身份认证
* @param jwt 令牌
* @return 成功状态返回200,其它均为失败
*/
public static Result<User> validationToken(String jwt) {
try {
//解析JWT字符串中的数据,并进行最基础的验证
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(jwt)
.getBody();
//如果解析成功,将其封装到User返回
User user = new User();
user.setUuid(claims.get("uuid").toString());
user.setLoginName(claims.get("loginName").toString());
user.setNickName(claims.get("nickName").toString());
if (claims.get("phoneNumber") != null) {
user.setPhoneNumber(claims.get("phoneNumber").toString());
}
user.setId(Integer.parseInt(claims.get("id").toString()));
user.setHeadImgUrl(claims.get("headImgUrl").toString());
return ResultUtil.success(user);
} catch (ExpiredJwtException e) {
// 已过期令牌
return ResultUtil.authExpired();
} catch (SignatureException e) {
// 伪造令牌
return ResultUtil.unAuthorized();
} catch (Exception e) {
// 系统错误
return ResultUtil.unAuthorized();
}
}
}
(4)网关层面快速失败返回接口
public class FallbackController {
@GetMapping("/fallback")
public Result fallback() {
return ResultUtil.fail();
}
}
(5)微服务安全认证流程
常用的认证方式主要有三种:Session、HTTP Basic Authentication 和 Token。
- session 是认证中最常用的一种方式,也是最简单的。用户登录后将信息存储在后端,客户端则通过 Cookie 中的 SessionId 来标识对应的用户。
- HTTP Basic Authentication 也就是 HTTP 基本认证,它是 HTTP 1.0 提出的一种认证机制。HTTP 基本认证的原理是客户端在请求时会在请求头中增加 Authorization,Authorization 是用户名和密码用 Base64 加密后的内容。服务端获取 Authorization Header 中的用户名与密码进行验证。
- Token 中会存储用户的信息,然后通过加密算法进行加密,只有服务端才能解密,服务端拿到 Token 后进行解密获取用户信息。
Token更适用于微服务的安全认证,本项目采用了token这种认证方式,并基于JWT的安全认证规范。
jwt是一种认证规范,它允许我们通过jwt在用户和服务器之间传递可靠的信息。在通信过程中进行身份认证
Ⅰ.用户进行登录时,将用户名和密码提交给认证服务器,服务器会验证用户提交信息的合法性,如果验证成功,则会返回一个token,客户端将token保存起来
Ⅱ.用户再次请求服务器时,一般会将token放到请求头中。当请求达到网关后,会在网关中对token进行校验;如果校验成功,网关会将其转发到后端服务中,转发时会将用户信息一并传递过去,这样后端服务就不用再进行校验了。
(1)网关是唯一的入口,所以微服务之间的请求就不需要再进行认证。
(2)有些请求是不需要进行认证的,所以我们加入了白名单进行处理。
(3)jwt的认证过程主要是加密,而加密会耗费CPU的运算资源。如果请求量过大,可以将token缓存起来,这样可以提高网关服务器CPU的性能。
(6)身份认证过滤器实现
对于认证过滤器实现类GlobalFilter和Ordered接口
1.GlobalFilter是gateway中的一个全局过滤器
2.我们知道gateway中的核心由一个过滤器链组成,这个过滤器链中的一个个过滤器是由Ordered进行排序的,数值越小越靠前执行。
3.ServerWebExchange是gateway中的一个网络交换器,它的内部封装了HTTP请求信息和响应信息,我们可以在Filter中根据这个ServerWebExchange对请求信息或者响应信息进行拦截,然后进行响应的操作。
4.consumer是Java8提供了一个消费性函数式接口,对此我们可以通过lambda快速向请求头中加入解析后的用户信息
5.一般情况下,请求和响应中的信息是不能修改的,但gateway为我们提供类一个mutate方法,专门用来修改请求和响应信息。由于我们通过ServerWebExchange进行数据交换的,所以我们可以先将信息追加到ServerHttpRequest中,然后再将ServerHttpRequest封装到ServerWebExchange中。
/**
* 身份认证过滤器
* @author: Ronin
* @since: 2021/10/5
* @email:
* 统一认证的实现方式是自定义实现全局过滤器,在过滤器里面可以处理白名单放行、认证校验、动态处理请求参数等
*/
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Autowired //白名单配置类
private SystemPropertiesConfig systemPropertiesConfig;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 白名单Path,获取白名单,获取配置文件中的白名单
Set<String> whiteList = this.getWhiteList();
//获取请求信息中的请求路径
String path = exchange.getRequest().getPath().toString();
// 主页接口、图书接口正则匹配,如果匹配这两个表示不用登录就可以访问
boolean indexMatch = Pattern.matches("/index[^\\\\s]*", path);
boolean bookMatch = Pattern.matches("/book/[^\\\\s]*", path);
// 白名单接口、开放接口放行
if (whiteList.contains(path) || bookMatch || indexMatch) {
return chain.filter(exchange);
}
String[] segments = path.split("/");
if (!whiteList.contains(segments[1])) {
// 认证--从请求头中获取token,通过jwt进行校验
String token = exchange.getRequest().getHeaders().getFirst("token");
Result<User> result = JwtUtil.validationToken(token);
if (result.getCode() == HttpCodeEnum.OK.getCode()) {
// 认证通过
User user = result.getData();
// 追加请求头用户信息
Consumer<HttpHeaders> httpHeaders = httpHeader -> {
httpHeader.set("userId",user.getId().toString());
httpHeader.set("nickName",user.getNickName());
};
ServerHttpRequest serverHttpRequest = exchange.getRequest()
.mutate()
.headers(httpHeaders)
.build();
exchange.mutate().request(serverHttpRequest).build();
return chain.filter(exchange);
}
// 认证过期、失败,均返回401
ServerHttpResponse response = exchange.getResponse();
//将对象转换为byte - json字符串
byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 指定编码,否则在浏览器中会中文乱码
response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0; //数值越小,该过滤器越向前执行
}
/**
* 请求白名单
* @return
*/
private Set<String> getWhiteList(){
String whitelists = this.systemPropertiesConfig.getWhitelist();
if (StringUtils.isEmpty(whitelists)) {
return new HashSet<>();
}
Set<String> whiteList = new HashSet<>();
String[] whiteArray = whitelists.split(",");
for (int i = 0; i < whiteArray.length; i++) {
whiteList.add(whiteArray[i]);
}
return whiteList;
}
}
以上是关于GitHub轻松阅读微服务实战项目流程详解第二天:API网关的设计与实现的主要内容,如果未能解决你的问题,请参考以下文章
GitHub轻松阅读微服务实战项目流程详解第四天:账户服务的设计与实现