【从0到1设计一个网关】基于JWT实现用户鉴权

什么是JWT?

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为 JSON 对象。它通常用于在不同系统之间进行身份验证和授权,以及在各种应用中传递声明性信息。 JWT 由三部分组成,它们通过点号(.)分隔: Header(头部):包含了关于生成的 JWT 的元数据信息,例如算法和令牌类型。

Payload(负载):包含了实际的声明(claim)信息,这些声明是关于实体(通常是用户)和其他数据的信息。有三种类型的声明:注册声明、公共声明和私有声明。

Signature(签名):用于验证JWT的完整性,确保数据在传输过程中没有被篡改。签名是基于头部和负载,使用一个密钥(秘密或公开的)进行加密生成的。

JWT 的作用包括:

  • 身份验证:JWT可用于验证用户身份,确保请求来自经过身份验证的用户。用户登录后,可以生成JWT并将其存储在客户端,然后在后续请求中使用它来证明身份。

  • 授权:JWT可以包含用户的授权信息,以便在服务器端验证用户是否有权限执行某个操作或访问某个资源。

  • 信息交换:JWT可用于在不同系统之间安全地传递信息,例如在微服务架构中进行服务之间的通信。

为什么要在网关中使用JWT?

在网关中使用JWT有以下好处:

  • 单一入口点:网关是应用程序的单一入口点,所有请求都经过网关,因此在网关中验证和授权用户可以确保所有请求都受到相同的安全策略。

  • 解耦鉴权逻辑:将身份验证和授权逻辑从微服务中解耦,并集中管理在网关中,这可以简化微服务的代码和逻辑。

  • 减轻微服务负担:通过在网关中进行鉴权和授权,可以减轻后端微服务的负担,因为它们只需专注于提供业务逻辑,而不必处理身份验证和授权。

  • 请求路由:网关可以根据JWT中的信息路由请求到适当的微服务,以确保请求被发送到正确的服务。

快速入门

这里我直接基于jjwt这个依赖,来快速的完成JWT的使用入门。 首先是依赖引入。

java 复制代码
  <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j2.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j2.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>${log4j2.version}</version>
        </dependency>

        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>

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

之后我们编写一个简单的测试用例

java 复制代码
 @Test
    public void jwt() {
        // 定义一个密钥用于签名 JWT
        String security = "zhangblossom";

        // 创建一个 JWT Token
        String token = Jwts.builder()
                .setSubject("1314520") // 设置JWT的主题,通常为用户的唯一标识
                .setIssuedAt(new Date()) // 设置JWT的签发时间,通常为当前时间
                .signWith(SignatureAlgorithm.HS256, security) // 使用HS256算法签名JWT,使用密钥"security"
                .compact(); // 构建JWT并返回字符串表示

        // 打印生成的JWT Token
        System.out.println(token);

        // 解析JWT Token
        Jwt jwt = Jwts.parser().setSigningKey(security).parse(token);

        // 打印解析后的JWT对象
        System.out.println(jwt);

        // 从JWT中获取主题(Subject)信息
        String subject = ((DefaultClaims) jwt.getBody()).getSubject();

        // 打印JWT中的主题信息
        System.out.println(subject);
    }

JWT鉴权的架构流程图

(这里的内容来自于我早期项目Towelove) 项目介绍(文档并不完全,这个项目的的内容都在我的语雀中) 1、客户端携带令牌访问资源服务获取资源。 2、资源服务远程请求认证服务校验令牌的合法性 3、如果令牌合法资源服务向客户端返回资源。 这里存在一个问题: 就是校验令牌需要远程请求认证服务,客户端的每次访问都会远程校验,执行性能低。 如果能够让资源服务自己校验令牌的合法性将省去远程请求认证服务的成本,提高了性能。如下图: 令牌采用JWT格式即可解决上边的问题,用户认证通过后会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权。

接下来的代码,我们就要求用户首先请求login用户登录接口,用来得到我们的JWT的token信息,并且我们会将这个token信息写入到我们的请求头中。

之后,当我们请求其他需要使用到token信息的接口的时候,我们就可以从请求头中获取到当前的用户信息了。

鉴权过滤器代码实现

首先,从上面我们已经知道,login方法用于获取token,因此在使用login方法的时候是不需要校验请求中是否存在token的,直接放行即可。 而其他的需要鉴权的请求,比如get-info就需要鉴权并解析token获取用户信息。

java 复制代码
@Slf4j
@FilterAspect(id= AUTH_FILTER_ID,
        name = AUTH_FILTER_NAME,
        order =AUTH_FILTER_ORDER )
public class AuthFilter implements Filter {
    /**
     * 加密密钥
     */
    private static final String SECRET_KEY = "zhangblossom";

    /**
     * cookie键  从对应的cookie中获取到这个键 存储的就是我们的token信息
     */
    private static final String COOKIE_NAME = "blossomgateway-jwt";

    @Override
    public void doFilter(GatewayContext ctx) throws Exception {
        //检查是否需要用户鉴权
        if (ctx.getRule().getFilterConfig(AUTH_FILTER_ID) == null) {
            return;
        }

        String token = ctx.getRequest().getCookie(COOKIE_NAME).value();
        if (StringUtils.isBlank(token)) {
            throw new ResponseException(ResponseCode.UNAUTHORIZED);
        }

        try {
            //解析用户id
            long userId = parseUserId(token);
            //把用户id传给下游
            ctx.getRequest().setUserId(userId);
        } catch (Exception e) {
            throw new ResponseException(ResponseCode.UNAUTHORIZED);
        }

    }

    /**
     * 根据token解析用户id
     * @param token
     * @return
     */
    private long parseUserId(String token) {
        Jwt jwt = Jwts.parser().setSigningKey(SECRET_KEY).parse(token);
        return Long.parseLong(((DefaultClaims)jwt.getBody()).getSubject());
    }
}

上面的代码还是比较简单就可以直接写出来的。让我们调用请求的时候,会解析判断当前路径是否需要走鉴权,而这个配置就来自于我们的配置中心。 如果不需要,就不会出发这个过滤器,直接return了。 下面我们来演示一下login请求。 这里由于我们的login请求并不需要进行鉴权,直接就放行了。 再次我们会对response响应体设定其中的cookie,信息就是我们的之前设定好的cookie-name。 到此为止,我们的cookie就已经存放成功了。

接下来我们就需要考虑获取用户信息了,那么此时我们就需要进行鉴权了。

当我们设定的路径需要进行鉴权的时候,就会从cookie中获取到设定好的token信息。 之后会进行转发

而当我们篡改了token信息之后,就会报错。 到此,我们就基于JWT实现了简单的用户鉴权功能。

相关推荐
姜学迁1 小时前
Rust-枚举
开发语言·后端·rust
爱学习的小健2 小时前
MQTT--Java整合EMQX
后端
北极小狐2 小时前
Java vs JavaScript:类型系统的艺术 - 从 Object 到 any,从静态到动态
后端
【D'accumulation】2 小时前
令牌主动失效机制范例(利用redis)注释分析
java·spring boot·redis·后端
2401_854391082 小时前
高效开发:SpringBoot网上租赁系统实现细节
java·spring boot·后端
Cikiss2 小时前
微服务实战——SpringCache 整合 Redis
java·redis·后端·微服务
Cikiss2 小时前
微服务实战——平台属性
java·数据库·后端·微服务
OEC小胖胖3 小时前
Spring Boot + MyBatis 项目中常用注解详解(万字长篇解读)
java·spring boot·后端·spring·mybatis·web
2401_857617623 小时前
SpringBoot校园资料平台:开发与部署指南
java·spring boot·后端
计算机学姐3 小时前
基于SpringBoot+Vue的在线投票系统
java·vue.js·spring boot·后端·学习·intellij-idea·mybatis