解决方案:微服务网关实现简单鉴权


关键词

  • JWT(xxxxx.yyyyy.zzzzz)
  • JJWT工具
  • 网关过滤器解析TOKEN

在网关系统中比较适合进行权限校验
在这里插入图片描述

在这里插入图片描述

一、微服务架构下统一认证思路

  • 基于Session的认证方式
    在分布式的环境下,基于session的认证会出现一个问题,每个应用服务都需要在session中存储用户身份信息,通过负载均衡将本地的请求分配到另一个应用服务需要将session信息带过去,否则会重新认证。我们可以使用Session共享、Session黏贴等方案。

    Session方案也有缺点,比如基于cookie,移动端不能有效使用等

  • 基于Token的认证方式
    优点:服务端不用存储认证数据;易维护扩展性强;客户端可以把token 存在任意地方;可以实现web和app统一认证机制

    缺点:token由于自包含信息,因此一般数据量较大,而且每次请求都需要传递,因此比较占带宽。另外,token的签名验签操作也会给cpu带来额外的处理负担

二、基于Token的认证方式(JWT)

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

一个JWT实际上就是一个字符串,它由三部分组成,头部载荷签名。每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz

2.1 头部(Header):

作用:在头部指明了签名算法

例如:{“typ”:“JWT”,“alg”:“HS256”}

将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分

2.2 载荷(playload):

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

例如:{“sub”:“1234567890”,“name”:“John Doe”,“admin”:true}

将其进行base64加密,得到Jwt的第二部分

2.3 签名(signature):

作用:jwt的第三部分是一个签证信息,这个签证信息由三部分组成

  • header (base64后的)
  • payload (base64后的)
  • secret

例如:HMACSHA256( base64UrlEncode(header) + “.” + base64UrlEncode(payload)+ secret )

三、使用JJWT工具 实现 token的签发与验证

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

3.1 创建token

1)在项目中的pom.xml中添加依赖

<dependencies>
        <!--JJWT依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>
</dependencies>

2)创建测试类,代码如下

JwtBuilder builder= Jwts.builder()
        .setId("55555")   //设置唯一编号
        .setSubject("大饼")//设置主题  可以是JSON数据
        .setIssuedAt(new Date())//设置签发日期               
			  .signWith(SignatureAlgorithm.HS256,"dabing");//设置签名 使用HS256算法,并设置SecretKey(字符串)
			  
        //构建 并返回一个字符串 
        System.out.println( builder.compact() );

运行打印结果:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5NTI3Iiwic3ViIjoi5YWD5pWsIiwiaWF0IjoxNjEzOTgwMjIyfQ.Uj-6H3nOcUWerJb9PEsq32OSQCPhKtqmE2bJtnV-E1Q

再次运行,会发现每次运行的结果是不一样的,因为我们的载荷中包含了时间。

3.2 解析token

创建token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。

    String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5NTI3Iiwic3ViIjoi5YWD5pWsIiwiaWF0IjoxNjEzOTgwMjIyfQ.Uj-6H3nOcUWerJb9PEsq32OSQCPhKtqmE2bJtnV-E1Q";
    Claims claims = Jwts.parser().setSigningKey("dabing").parseClaimsJws(compactJwt).getBody();
    System.out.println(claims);

运行打印效果:

{jti=55555, sub=大饼, iat=1613980222}
3.3 设置过期时间

.setExpiration(date) //用于设置过期时间 ,参数为Date类型数据

有很多时候,我们并不希望签发的token是永久生效的,所以我们可以为token添加一个过期时间。

3.4 自定义claims

上面只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以定义自定义claims。

创建测试类,并设置测试方法:

.claim(“roles”,“admin”) //设置角色

四、微服务认证代码实现

4.1 整体实现思路

在这里插入图片描述用户登录之后要签发Token,用户访问任何非登录的资源时都需要携带Token,并且在网关进行Token验证。

1. 用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录
2. 用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户
3. 用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN 
4. 网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误
4.2 编码实现

4.2.1 依赖:

<!--鉴权-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

4.2.2 创建JWTUtil类

/**
 * jwt校验工具类
 */
public class JwtUtil {
    //有效期为
    public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000  一个小时
    //设置秘钥明文
    public static final String JWT_KEY = "dabing";
    /**
     * 生成加密后的秘钥 secretKey
     *
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        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();
    }
}

4.2.3 在网关微服务中编写过滤器

/**
 * 鉴权过滤器 验证token
 */
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
    
    private static final String AUTHORIZE_TOKEN = "token";
    
    @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(AUTHORIZE_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

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐