Spring Cloud Gateway——(三)微服务网关实现JWT鉴权

1. JWT 实现微服务鉴权

1.1 什么是JWT?

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

  • JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

    • 头部(Header)

      头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。

    • 载荷(playload)

      载荷就是存放有效信息的地方

    • 签证(signature)

      这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

1.2 JJWT签发与验证token

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。
官方文档:https://github.com/jwtk/jjwt

1.3 JJWT快速入门

  1. 在项目中添加依赖

    <dependency>
    	<groupId>io.jsonwebtoken</groupId>
    	<artifactId>jjwt</artifactId>
    	<version>0.9.0</version>
    </dependency>
    
  2. 创建测试类生成token

    JwtBuilder builder= Jwts.builder()
    	.setId("007") //设置唯一编号
    	.setSubject("小马")//设置主题 可以是JSON数据
    	.setIssuedAt(new Date())//设置签发日期
        .setExpiration(new Date(System.currentTimeMillis()+3600))//设置过期时间 
    	.signWith(SignatureAlgorithm.HS256,"xm");//设置签名 使用HS256算法,并设置SecretKey(字符串)
    //构建 并返回一个字符串
    System.out.println( builder.compact() );
    
  3. 解析token

    String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGk...之前生成的token...";
    Claims claims =Jwts.parser()
        .setSigningKey("xm") 
        .parseClaimsJws(compactJwt)
        .getBody();
    System.out.println(claims); 
    

1.4 在微服务中实现jjwt鉴权

(1)在网关服务和用户登录服务中添加依赖依赖

<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt</artifactId>
	<version>0.9.0</version>
</dependency>

(2)在服务中创建类: JwtUtil 用于jjwt token生成和解析

package com.xm.system.util;

import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;

/**
 * @title: JwtUtil  JWT工具类
 * @projectName: 
 * @description: TODO
 * @author: Zack_Tzh
 * @date: 2019/12/10  9:31
 */
public class JwtUtil {

    /**
     * 设置有效期为 60 * 60 秒
     */
    private static final Long JWT_TTL = 3600000L;
    /**
     * 设置秘钥明文
     */
    private static final String JWT_KEY= "NVIDIA";

    /**
     * 创建toke
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id,String subject,Long ttlMillis){
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        SecretKey secretKey = generalKey();

        JwtBuilder builder = Jwts.builder()
                //唯一的ID
                .setId(id)
                // 主题  可以是JSON数据
                .setSubject(subject)
                // 签发者
                .setIssuer("admin")
                // 签发时间
                .setIssuedAt(now)
                //使用HS256对称加密算法签名, 第二个参数为秘钥
                .signWith(signatureAlgorithm, secretKey)
                // 设置过期时间
                .setExpiration(expDate);
        return builder.compact();
    }
    /**
     * 解析JWT
     * @param compactJwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String  compactJwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws( compactJwt).getBody();
    }
    /**
     * 生成加密后的秘钥 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;
    }
}

(3)在用户登录的login方法时,用户登录成功 则 签发TOKEN

	/**
     * 登录
     * @param admin
     * @return
     */
    @PostMapping("/login")
    public Result login(@RequestBody Admin admin){
        boolean result = adminService.login(admin);//验证密码是否正确
        if (result){
            //密码是正确的
            //生成jwt令牌,返回到客户端
            Map<String,String> info = new HashMap<>();
            info.put("username",admin.getLoginName());
            //基于工具类生成jwt令牌
            String jwt = JwtUtil.createJWT(UUID.randomUUID().toString(), admin.getLoginName(), null);
            info.put("token",jwt);
            return new Result(true, StatusCodeEnum.OK,info);
        }else{
            //密码错误
            return new Result(false, StatusCodeEnum.ERROR);
        }
    }

(3)在网关创建过滤器,用于token验证

package com.xm.system.filter;

import com.xm.system.util.JwtUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @title: AuthorizeFilter
 * @projectName: xm
 * @description: TODO
 * @author: Zack_Tzh
 * @date: 2019/12/10  14:46
 */
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1. 获取请求
        ServerHttpRequest request = exchange.getRequest();
        //2. 则获取响应
        ServerHttpResponse response = exchange.getResponse();
        //3. 如果是登录请求则放行
        if (request.getURI().getPath().contains("/admin/login")) {
            return chain.filter(exchange);
        }
        //4. 获取请求头
        HttpHeaders headers = request.getHeaders();
        //5. 请求头中获取令牌
        String token = headers.getFirst("token");
        //6. 判断请求头中是否有令牌
        if (StringUtils.isEmpty(token)) {
            //7. 响应中放入返回的状态吗, 没有权限访问
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            //8. 返回
            return response.setComplete();
        }
        //9. 如果请求头中有令牌则解析令牌
        try {
            JwtUtil.parseJWT(token);
        } catch (Exception e) {
            e.printStackTrace();
            //10. 解析jwt令牌出错, 说明令牌过期或者伪造等不合法情况出现
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            //11. 返回
            return response.setComplete();
        }
        //12. 放行
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐