Skip to content

Clerk 验证 ​

tio-boot 整合 Clerk 进行 Token 验证

本文档介绍如何将 Clerk SDK for Java 与 tio-boot 框架集成,实现对 token 的验证功能。主要内容包括 Clerk 简介、Token 格式说明以及整合步骤和代码示例。


1. Clerk 简介 ​

Clerk 是一款用于处理身份验证和授权的工具包,支持对 JSON Web Token (JWT) 进行验证。通过 Clerk 后台提供的 JWT 公钥,可以对客户端传入的 token 进行解析和验证,从而确保用户身份的真实性。整合 Clerk 后,可以使用标准方法获取 token 中包含的用户信息,例如 sub 字段(通常表示用户标识符)。


2. Token 格式 ​

在整合 Clerk 时,Token 通常采用 JWT 格式。常见的字段包括:

  • sub:JWT 标准声明,表示用户的唯一标识符(user id)。
  • 其他自定义字段:例如 user_id 或其他业务相关数据。

验证过程中,建议使用 getSubject() 方法获取用户 ID,或直接获取 token 中其他自定义的字段。


3. 整合步骤 ​

3.1 添加依赖 ​

在项目的 Maven 配置中添加 Clerk SDK 和 Jackson 核心库的依赖,示例配置如下:

xml
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.15.2</version>
</dependency>

<dependency>
  <groupId>com.clerk</groupId>
  <artifactId>backend-api</artifactId>
  <version>1.5.0</version>
</dependency>

3.2 配置 Token 验证工具类 ​

创建工具类 ClerkVerifyTokenUtils,使用 Clerk 后台提供的 JWT 公钥来配置验证选项。代码示例如下:

java
package nexus.io.open.chat.utils;

import java.net.URL;

import com.clerk.backend_api.helpers.jwks.TokenVerificationException;
import com.clerk.backend_api.helpers.jwks.VerifyToken;
import com.clerk.backend_api.helpers.jwks.VerifyTokenOptions;
import com.clerk.backend_api.helpers.jwks.VerifyTokenOptions.Builder;
import nexus.io.tio.utils.environment.EnvUtils;
import nexus.io.tio.utils.hutool.FileUtil;
import nexus.io.tio.utils.hutool.ResourceUtil;

import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ClerkVerifyTokenUtils {
  public static VerifyTokenOptions options = null;
  static {
    URL resource = null;
    if (EnvUtils.isProd()) {
      resource = ResourceUtil.getResource("CLERK_JWT_KEY_prod.key");
    } else {
      resource = ResourceUtil.getResource("CLERK_JWT_KEY_test.key");
    }

    if (resource == null) {
      log.error("Failed to read CLERK_JWT_KEY.key");
    }
    StringBuilder readString = FileUtil.readString(resource);
    Builder builder = VerifyTokenOptions.jwtKey(readString.toString());
    options = builder.build();
  }

  public static Claims verifyToken(String token) throws TokenVerificationException {
    // 配置验证选项,比如使用 Clerk 后台提供的 JWT 公钥
    return VerifyToken.verifyToken(token, options);
  }

}

该工具类中,通过 ResourceUtil 和 FileUtil 获取存放 JWT 公钥的文件,并构造出验证选项。调用 VerifyToken.verifyToken 方法可以验证 token 并返回对应的 Claims 对象。

3.3 在 AuthInterceptor 中集成 Token 验证 ​

在 tio-boot 的 AuthInterceptor 中,通过解析 HTTP 请求头中的 authorization 字段获取 token,并利用 ClerkVerifyTokenUtils.verifyToken 方法进行验证。验证成功后,提取 token 中的用户标识符(使用 getSubject() 方法)并保存到上下文中。

以下是完整代码示例:

java
package nexus.io.open.chat.intecerptor;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;

import com.clerk.backend_api.helpers.jwks.TokenVerificationException;
import com.jfinal.kit.StrKit;
import nexus.io.constants.ServerConfigKeys;
import nexus.io.jfinal.aop.Aop;
import nexus.io.model.body.RespBodyVo;
import nexus.io.open.chat.notification.TokenNotificationUtils;
import nexus.io.open.chat.services.NewUserService;
import nexus.io.open.chat.utils.ClerkVerifyTokenUtils;
import nexus.io.tio.boot.admin.services.TokenPredicate;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.common.HttpResponseStatus;
import nexus.io.tio.http.common.RequestLine;
import nexus.io.tio.http.common.utils.HttpIpUtils;
import nexus.io.tio.http.server.intf.HttpRequestInterceptor;
import nexus.io.tio.utils.environment.EnvUtils;
import nexus.io.tio.utils.hutool.StrUtil;
import nexus.io.tio.utils.network.IpUtils;
import nexus.io.tio.utils.notification.NotifactionWarmModel;
import nexus.io.tio.utils.notification.NotificationTemplate;

import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AuthInterceptor implements HttpRequestInterceptor {

  TokenPredicate tokenPredicate = Aop.get(TokenPredicate.class);

  private Object body = null;

  public AuthInterceptor() {}

  public AuthInterceptor(Object body) {
    this.body = body;
  }

  @Override
  public HttpResponse doBeforeHandler(HttpRequest request, RequestLine requestLine, HttpResponse responseFromCache) {
    String authorization = request.getHeader("authorization");

    if (StrKit.isBlank(authorization)) {
      String content = "authorization can not be empty";
      sendError(request, null, content);
      return fail(content);
    }

    String[] split = authorization.split(" ");
    if (split.length < 2) {
      String content = "Failed to extra token";
      sendError(request, null, content);
      return fail(content);
    }

    String idToken = split[1];

    boolean test = tokenPredicate.test(idToken);
    if (!test) {
      Claims decodedToken = null;
      try {
        decodedToken = ClerkVerifyTokenUtils.verifyToken(idToken);
      } catch (TokenVerificationException e) {
        String content = "failed to validate token:" + idToken;
        sendError(request, e, content);
        return fail("Failed to validate token");
      }
      // 使用 getSubject() 获取 token 中的用户标识符
      String uid = decodedToken.getSubject();
      TioRequestContext.setUserId(uid);
      Aop.get(NewUserService.class).save(uid);
    }
    return null;
  }

  private void sendError(HttpRequest request, Exception e, String content) {
    NotifactionWarmModel model = new NotifactionWarmModel();
    String localIp = IpUtils.getLocalIp();
    model.setAppEnv(EnvUtils.env());
    model.setAppGroupName("tio-boot");
    model.setAppName(EnvUtils.get(ServerConfigKeys.APP_NAME));
    model.setWarningName("AuthInterceptor");
    model.setLevel("II");
    model.setContent(content);
    model.setDeviceName(localIp);
    String requestId = request.getChannelContext().getId();
    String realIp = HttpIpUtils.getRealIp(request);
    model.setRequestId(requestId);
    model.setUserIp(realIp);
    model.setHost(request.getHost());
    model.setRequestLine(request.getRequestLine().toString());
    model.setRequestBody(request.getBodyString());

    if (e != null) {
      StringWriter sw = new StringWriter();
      PrintWriter pw = new PrintWriter(sw);
      e.printStackTrace(pw);
      String stackTrace = sw.toString();
      model.setStackTrace(stackTrace);
    }

    model.setTime(new Date());

    String msg = NotificationTemplate.format(model);
    TokenNotificationUtils.sendAsync(msg);
  }

  private HttpResponse fail(String msg) {
    HttpResponse response = TioRequestContext.getResponse();
    response.setStatus(HttpResponseStatus.C401);
    if (body != null) {
      RespBodyVo respVo = RespBodyVo.fail(msg);
      respVo.setData(body);
      response.setJson(respVo);
    }
    return response;
  }

  @Override
  public void doAfterHandler(HttpRequest request, RequestLine requestLine, HttpResponse response, long cost) throws Exception {
    // 处理完请求后的操作
  }
}

代码说明:

  • 从请求头中提取 authorization 字段,并解析出 token(格式通常为 "Bearer token")。
  • 调用 TokenPredicate 进行初步校验;如果校验不通过,则进入 Clerk 验证流程。
  • 利用 ClerkVerifyTokenUtils.verifyToken 方法验证 token,验证通过后调用 getSubject() 方法获取用户 ID。
  • 将用户 ID 存入上下文中,并调用新用户服务保存用户信息。

总结 ​

本文档详细描述了如何在 tio-boot 框架中整合 Clerk 进行 token 验证。通过添加依赖、配置验证工具类以及在拦截器中处理 token 验证流程,可以实现一个高效且安全的认证方案。详细参考 Clerk SDK 的官方文档获取更多信息和最新更新。

希望本篇文档对在整合过程中有所帮助。

MIT Licensed