我们在写SpringCloud的网关代码的时候,一定要注意,网关服务尽量简单高效,否则将拖垮整个微服务集群,如果要写逻辑,就要详细测试每一行代码的运行效率,尽量保持在1-2ms内,压力大时,增加网关服务的数量,注意服务间尽量能做到无状态。
网关代码里的逻辑一般都是对http的验证,不同的服务会有不同的验证规则,我们就以token为例,完成一个网关服务的token验证的demo。
1、在nacos-client-demo项目中,添加一个登录接口,当调用登录接口时,返回一个token。
@Autowired
private JwtConfig jwtConfig;
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(@RequestParam(value = "userid") String userid, @RequestParam(value = "username") String username) {
return TokenTool.token(userid,username, Duration.ofSeconds(jwtConfig.getTtl()*60L));
}
2、生成token中是由userId,userName两个参数组成,并且有30分钟过期时间。
package com.mj.nacosclient.utils;
import cn.hutool.jwt.JWT;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class TokenTool {
// 密钥
private final static String SECRETKEY = "mySecretKey";
public static String token(String userId, String userName, Duration duration) {
Map<String, Object> map = new HashMap<>();
map.put("userId", userId);
map.put("userName", userName);
JWT jwt = JWT.create();
map.forEach(jwt::setPayload);
jwt.setExpiresAt(new Date(System.currentTimeMillis() + duration.getSeconds()));
jwt.setKey(SECRETKEY.getBytes(StandardCharsets.UTF_8));
String token = jwt.sign();
return token;
}
public static String parse(String token) {
JWT jwt = JWT.create().parse(token);
return jwt.getPayload("userId").toString();
}
}
3、添加token过期时间在application.yaml中。
mj:
jwt:
ttl: 30
4、通过一个Configuration类解析出application.yml中的配置。
package com.mj.nacosclient.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
@Data
@Configuration
@ConfigurationProperties(prefix = "mj.jwt")
public class JwtConfig {
private long ttl;
}
5、在网关服务gateway-demo中,再添加一个全局Filter,用来获取每次请求时的token。
package com.mj.gateway.filter;
import com.mj.gateway.utils.TokenTool;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().toString();
//如果是登录接口,直接放行
if (path.endsWith("login")) {
return chain.filter(exchange);
}
//从header里获取token
String token = null;
List<String> tokens = request.getHeaders().get("token");
if (tokens != null && !tokens.isEmpty()) {
token = tokens.get(0);
}
//解析出token中的userId
try {
String userId = TokenTool.parse(token);
System.out.println(userId);
} catch (Exception e) {
ServerHttpResponse response = exchange.getResponse();
//设置返回码为401
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//返回http请求,不再往下传递了
return response.setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
TokenTool这个工具类和nacos-client-demo中是同一个,复制一份即可。
如果是login接口,那么直接放行,其余的接口,需要从header中获取token并且解析出userId打印日志,没有token则返回401。
6、调用login接口获取到token:http://127.0.0.1:8888/nacos-client-demo/api/login?userid=123&username=zhangsan
7、借助于curl命令,模拟一个带token的http请求:
curl -H "token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6InpoYW5nc2FuIiwidXNlcklkIjoiMTIzIiwiZXhwIjoxN zQwNzU1NDE2fQ.Xqpgk_lqhpxIIvxSo70mb3LQuozREIOituLyZKKYYaA" http://127.0.0.1:8888/nacos-client-demo/api/call
观察日志输出:
8、但是只解析出来还不够呢,我们还要把解析出来的userId传递给下游的每一个接口使用,所以我们再修改一下AuthGlobalFilter,把解析出来的userId放到请求头中。
package com.mj.gateway.filter;
import com.mj.gateway.utils.TokenTool;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().toString();
//如果是登录接口,直接放行
if (path.endsWith("login")) {
return chain.filter(exchange);
}
//从header里获取token
String token = null;
List<String> tokens = request.getHeaders().get("token");
if (tokens != null && !tokens.isEmpty()) {
token = tokens.get(0);
}
//解析出token中的userId
String userId=null;
try {
userId = TokenTool.parse(token);
System.out.println(userId);
} catch (Exception e) {
ServerHttpResponse response = exchange.getResponse();
//设置返回码为401
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//返回http请求,不再往下传递了
return response.setComplete();
}
String userInfo=userId;
//这时候返回值是一个新的exchange
ServerWebExchange swe = exchange.mutate().request(builder -> builder.header("userid", userInfo)).build();
return chain.filter(swe);
}
@Override
public int getOrder() {
return 0;
}
}
这里加完header之后的exchange是一个新的exchange,传递到下游的要用这个新的。
9、在nacos-client-demo中添加一个talk接口,获取gateway传递过来的userId。
@RequestMapping(value = "/talk", method = RequestMethod.GET)
public String talk(@RequestHeader(value = "userid", required = true) String userid) {
return "hi," + userid;
}
10、使用curl命令调用一下talk接口,并且传入token,观察能不能解析出token中的userId并且传递到talk接口中。
curl -H "token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6InpoYW5nc2FuIiwidXNlcklkIjoiMTIzIiwiZXhwIjoxN zQwNzU1NDE2fQ.Xqpgk_lqhpxIIvxSo70mb3LQuozREIOituLyZKKYYaA" http://127.0.0.1:8888/nacos-client-demo/api/talk
hi,123