SpringCloud系列教程(九):token验证

我们在写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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值