什么是微服务网关
Gateway用于寻址路由、认证授权、保护微服务安全而产生的一个中间系统。我们通常一个功能需要访问多个微服务来实现,这会导致调用混乱(ip+端口+url因素),所以加入网关来进行统一寻址和路由。
网关能做什么
1.统一处理跨域请求
2.认证和授权JWT
3.寻址和路由
4.限流保护微服务
如何搭建网关
(1)pom文件引入依赖
<dependencies>
<!--网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--hystrix限流-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka注册客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
(2)常用的yml配置
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
- id: gateway_route
uri: lb://users #lb表示使用负载均衡,users指的是启动的users微服务名称
predicates:
- Path=/api/users/** #访问路径
filters:
- StripPrefix=1 #访问路径截取前一位
- name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的facatory
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 10
application:
name: gateway-web
server:
port: 8001
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
management:
endpoint:
gateway:
enabled: true
web:
exposure:
include: true
(2)相关的配置代码
/***
* IP限流
* @return
*/
@Bean(name="ipKeyResolver")
public KeyResolver userKeyResolver() {
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
//获取远程客户端IP
String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
return Mono.just(hostName);
}
};
}
网关测试
当我们访问user微服务时(例如:127.0.0.1:10086/users/login),可以通过访问127.0.0.1:8001/api/users/login网关url间接访问用户服务
什么是JWT
JWT(javawebtoken)我们可以理解为令牌。以token形式存在,用户认证和授权,jwt包含三部分(头部,载荷,签名)头部存放的是加密类型和加密算法,载荷存放默认信息和要传递的信息,签名(头部+载荷+秘钥加密)用于保证传输的安全性
JWT工具包和依赖
<!--鉴权-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000 一个小时
//Jwt令牌信息
public static final String JWT_KEY = "itcast";
public static String createJWT(String id, String subject, Long ttlMillis) {
//指定算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//当前系统时间
long nowMillis = System.currentTimeMillis();
//令牌签发时间
Date now = new Date(nowMillis);
//如果令牌有效期为null,则默认设置有效期1小时
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
//令牌过期时间设置
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
//生成秘钥
SecretKey secretKey = generalKey();
//封装Jwt令牌信息
JwtBuilder builder = Jwts.builder()
.setId(id) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("admin") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) // 签名算法以及密匙
.setExpiration(expDate); // 设置过期时间
return builder.compact();
}
/**
* 生成加密 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getEncoder().encode(JwtUtil.JWT_KEY.getBytes());
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析令牌数据
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
网关整合JWT
自定义拦截器进行拦截 MyAuthorizeFilter,检测和校验jwt
package com.changgou.filter;
import com.changgou.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class MyAuthorizeFilter implements GlobalFilter, Ordered {
//令牌头名字
private static final String AUTHORIZE_TOKEN = "Authorization";
/**
* 全局过滤器
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取Request、Response对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//获取请求的URI
String path = request.getURI().getPath();
//如果是登录、goods等开放的微服务[这里的goods部分开放],则直接放行,这里不做完整演示,完整演示需要设计一套权限系统
if (path.startsWith("/api/user/login")) {
//放行
Mono<Void> filter = chain.filter(exchange);
return filter;
}
//获取头文件中的令牌信息
String tokent = request.getHeaders().getFirst(AUTHORIZE_TOKEN);
//如果头文件中没有,则从请求参数中获取
if (StringUtils.isEmpty(tokent)) {
tokent = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
}
//cookie中获取令牌
if (StringUtils.isEmpty(tokent)) {
HttpCookie first = request.getCookies().getFirst(AUTHORIZE_TOKEN);
if (first!=null){
tokent = first.getValue();
}
}
//如果为空,则输出错误代码
if (StringUtils.isEmpty(tokent)) {
//设置方法不允许被访问,405错误代码
response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
return response.setComplete();
}
//解析令牌数据
try {
Claims claims = JwtUtil.parseJWT(tokent);
//存放在头中
request.mutate().header(AUTHORIZE_TOKEN,claims.toString());
} catch (Exception e) {
e.printStackTrace();
//解析失败,响应401错误
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//放行
return chain.filter(exchange);
}
/**
* 过滤器执行顺序
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
编写Contorller测试JWT
@GetMapping("/login")
public Result login(@RequestParam("username")String username, @RequestParam("password")String password, HttpServletResponse response){
//判断用户名或者密码,不能为空
if (StringUtils.isEmpty(username)|| StringUtils.isEmpty(password)){
return new Result(false,StatusCode.LOGINERROR,"用户名或者密码不能为空",null);
}
//获取user对象
User user = userService.findById(username);
//校验用户名和密码
if (!StringUtils.isEmpty(user) && BCrypt.checkpw(password,user.getPassword())){
//jwt保持会话
Map<String,Object> content = new HashMap<>();
content.put("role","user");
content.put("username",user.getUsername());
content.put("level",user.getUserLevel());
//生成令牌
String jwt = JwtUtil.createJWT(UUID.randomUUID().toString(), JSON.toJSONString(content), null);
//设置cookie
Cookie cookie = new Cookie("Authorization",jwt);
cookie.setPath("/");
//cookie.setDomain("changgou"); //域谨慎设置,容易导致cookie获取不到问题
//添加cookie
response.addCookie(cookie);
return new Result(true,StatusCode.OK,"登录校验成功",user);
}
return new Result(false,StatusCode.LOGINERROR,"账号或者密码错误!");
}
JWT注意事项
(1)在存放cookie的时候要谨慎设置域和路径,容易导致拦截器获取不到cookie的问题,鉴权失败 (2)网关依赖需要排除springboot-start-web依赖,否则容易报spirngmvc的错误