在后端验证 JWT¶
本节将介绍如何解码 JWT 令牌以获取当前登录的用户。
说明
使用 NestJS 开发?@authgear/nestjs SDK 提供了现成的模块、守卫(guard)和装饰器,您无需手动搭建 JWT 校验逻辑。
开始之前,请确保在管理台的应用设置中启用了将 JWT 作为访问令牌签发选项。

在管理台的应用设置中启用此选项
在应用中开启将 JWT 作为访问令牌签发选项后,Authany 会将 JWT 作为访问令牌签发。传入的 HTTP 请求应在 Authorization 请求头中携带该访问令牌。无需设置反向代理,您的后端服务器就可以使用 Authany 的 JWKS 来验证请求,并从 JWT 访问令牌中解码用户信息。
JWT 访问令牌的载荷¶
访问令牌中的声明请参见此参考文档:JWT 访问令牌。如需了解如何往 JWT 中添加声明,请参阅为 JWT 访问令牌或 ID 令牌添加自定义字段。
查找 JSON Web Key Sets(JWKS)端点¶
这个发现端点(Discovery endpoint)是一个 JSON 文档,包含您应用的 OpenID Connect 配置,其中包括授权端点、令牌端点和 JWKS 端点。
https://<YOUR_AUTHGEAR_ENDPOINT>/.well-known/openid-configuration
JSON Web Key Sets(JWKS)端点可以在配置中的 jwks_uri 字段中找到。
OpenID Connect 配置 JSON 示例
以下是具体样式的示例。
{
"issuer": "https://project-id.authanyid.com",
"authorization_endpoint": "https://project-id.authanyid.com/oauth2/authorize",
"jwks_uri": "https://project-id.authanyid.com/oauth2/jwks", // the JWKS endpoint
...
}
从访问令牌中解码用户信息¶
请按照以下步骤示例来验证并解码 JWT 令牌。
第 1 步:安装依赖包
第 2 步:查找 JSON Web Key Sets(JWKS)端点
定义一个函数,从 OpenID Connect 配置中查找 JWKS 端点。将您的 Authany 端点用作 base_address
import json
from contextlib import closing
from urllib.request import urlopen
base_address = "https://<your_app_endpoint>"
def fetch_jwks_uri(base_address):
doc_url = base_address + "/.well-known/openid-configuration"
with closing(urlopen(doc_url)) as f:
doc = json.load(f)
jwks_uri = doc["jwks_uri"]
if not jwks_uri:
raise Exception('Failed to fetch jwks uri.')
return jwks_uri
第 3 步:从 Authorization 请求头中获取 JWT 令牌
定义一个函数,从传入请求的 Authorization 请求头中提取访问令牌。它应该形如 Authorization: Bearer <access_token>。
def parse_header(authz_header):
parts = authz_header.split(" ")
if len(parts) != 2:
return
scheme = parts[0]
if scheme.lower() != "bearer":
return
return parts[1]
第 4 步:验证并解码 JWT 令牌
这里我们展示一个使用 Flask 框架保护路径的示例。您可能需要根据自己的技术栈调整部分代码。
from flask import request
import jwt
from jwt import PyJWKClient
@app.route("/hello")
def hello():
authz_header = request.headers.get("Authorization")
if not authz_header:
return {
"message": "authz header not found"
}
# get jwt token from Authorization header
token = parse_header(authz_header)
if token:
try:
# fetch jwks_uri from the Authgear Discovery Endpoint
jwks_uri = fetch_jwks_uri(base_address)
# Reuse PyJWKClient for better performance
jwks_client = PyJWKClient(jwks_uri)
signing_key = jwks_client.get_signing_key_from_jwt(token)
user_data = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
audience=base_address,
options={"verify_exp": True},
)
return {
"message": "Hello!",
"user_data": user_data
}
except:
return {
"message": "JWT decode failed"
}
else:
return {
"message": "no token"
}
第 1 步:安装依赖
第 2 步:查找 JWKS 端点
使用以下方法获取 JWKS URI(您需要用这个 URI 从 JWT 中提取公钥)。
const appUrl = ""; //place your authgear app endpoint here
const getJwksUri = async (appUrl) => {
const config_endpoint = appUrl + "/.well-known/openid-configuration";
const data = await axios.get(config_endpoint);
return data.data.jwks_uri;
}
第 3 步:从请求头中提取 JWT
在 Express 应用中使用以下代码,从 Bearer [token] 格式的 Authorization 请求头中仅提取令牌部分:
const express = require("express");
const axios = require("axios");
const node_jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const app = express();
const port = 3002;
app.get('/', async (req, res) => {
const requestHeader = req.headers;
if (requestHeader.authorization == undefined) {
res.send("Invalid header");
return;
}
const authorizationHeader = requestHeader.authorization.split(" ");
const access_token = authorizationHeader[1];
}
第 4 步:解码访问令牌
接下来,解码访问令牌,以便从结果中提取 JWT 的 kid。您需要用这个 kid 来获取公钥。使用以下代码解码 JWT:
第 5 步:获取 JWT 签名密钥并验证 JWT
使用以下代码提取 JWT 公钥,然后用这些密钥验证 JWT:
const jwks_uri = await getJwksUri(appUrl);
const client = jwksClient({
strictSsl: true,
jwksUri: jwks_uri
});
const signing_key = await client.getSigningKey(decoded_access_token.header.kid);
try {
const verify = node_jwt.verify(access_token, signing_key.publicKey, { algorithms: ['RS256'] });
res.send(JSON.stringify(verify))
}
catch(error) {
res.send(error);
}
完成以上所有步骤后,您的 Express 应用大致如下:
const express = require("express");
const axios = require("axios");
const node_jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const app = express();
const port = 3002;
const appUrl = "https://demo-1-ea.authanyid.com";
const getJwksUri = async (appUrl) => {
const config_endpoint = appUrl + "/.well-known/openid-configuration";
const data = await axios.get(config_endpoint);
return data.data.jwks_uri;
}
app.get('/', async (req, res) => {
const requestHeader = req.headers;
if (requestHeader.authorization == undefined) {
res.send("Invalid header");
return;
}
const authorizationHeader = requestHeader.authorization.split(" ");
const access_token = authorizationHeader[1];
const decoded_access_token = node_jwt.decode(access_token, {complete: true});
const jwks_uri = await getJwksUri(appUrl);
const client = jwksClient({
strictSsl: true,
jwksUri: jwks_uri
});
const signing_key = await client.getSigningKey(decoded_access_token.header.kid);
try {
const verify = node_jwt.verify(access_token, signing_key.publicKey, { algorithms: ['RS256'] });
res.send(JSON.stringify(verify))
}
catch(error) {
res.send(error);
}
});
app.listen(port, () => {
console.log(`server started on port ${port}`);
});
将您的 Authany 端点用作 base_address
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/lestrrat-go/jwx/v3/jwk"
"github.com/lestrrat-go/jwx/v3/jwt"
)
var (
baseAddress = "https://<your_app_endpoint>"
)
type OIDCDiscoveryDocument struct {
JWKSURI string `json:"jwks_uri"`
}
func FetchOIDCDiscoveryDocument(endpoint string) (*OIDCDiscoveryDocument, error) {
resp, err := http.DefaultClient.Get(endpoint)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(
"failed to fetch discovery document: unexpected status code: %d",
resp.StatusCode,
)
}
var document OIDCDiscoveryDocument
err = json.NewDecoder(resp.Body).Decode(&document)
if err != nil {
return nil, err
}
return &document, nil
}
func FetchJWK(baseAddress string) (jwk.Set, error) {
doc, err := FetchOIDCDiscoveryDocument(
baseAddress + "/.well-known/openid-configuration",
)
if err != nil {
return nil, err
}
set, err := jwk.Fetch(context.Background(), doc.JWKSURI)
return set, err
}
// DecodeUser parse request Authorization header and obtain user id and claims
func DecodeUser(r *http.Request) (string, bool, bool, error) {
// fetch jwks_uri from Authgear
// you can cache the value of jwks using jwk.Cache to have better performance
set, err := FetchJWK(baseAddress)
if err != nil {
return "", false, false, fmt.Errorf("failed to fetch JWK: %s", err)
}
// parse jwt token
token, err := jwt.ParseRequest(
r,
// This may not work out of the box depending on the jwk.Set.
// Please read about requirements for "kid" and "alg" (and possibly
// "WithDefaultKey") when using jwk.Set in the jwt.WithKeySet documentation.
jwt.WithKeySet(set),
)
if err != nil {
return "", false, false, fmt.Errorf("invalid token: %s", err)
}
// validate jwt token
err = jwt.Validate(token,
jwt.WithClock(jwt.ClockFunc(
func() time.Time { return time.Now().UTC() },
)),
jwt.WithAudience(baseAddress),
)
if err != nil {
return "", false, false, fmt.Errorf("invalid token: %s", err)
}
var verified bool
var anonymous bool
// ignore errors -- if the claim does not exist, it doesn't matter
_ = token.Get("https://authgear.com/claims/user/is_verified", &verified)
_ = token.Get("https://authgear.com/claims/user/is_anonymous", &anonymous)
// In jwx v3, Subject() returns (value, ok).
sub, _ := token.Subject()
return sub, verified, anonymous, nil
}
func handler(w http.ResponseWriter, r *http.Request) {
// decode user example
userid, isUserVerified, isAnonymousUser, err := DecodeUser(r)
// ... your handler logic
}
以下示例使用 Spring Boot。
第 1 步:安装依赖
在您的 build.gradle 文件中添加以下依赖:
dependencies {
implementation("com.nimbusds:nimbus-jose-jwt:10.2")
implementation("org.json:json:20250107")
}
然后在控制器文件顶部添加以下导入:
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
第 2 步:获取 JWKS 端点
实现以下方法来获取 JWKS URI:
private static String fetchJwksUri(String baseAddress) throws Exception {
String docUrl = baseAddress + "/.well-known/openid-configuration";
HttpURLConnection conn = (HttpURLConnection) new URL(docUrl).openConnection();
conn.setRequestMethod("GET");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
String jwksUri = new org.json.JSONObject(response.toString()).getString("jwks_uri");
if (jwksUri == null || jwksUri.isEmpty()) {
throw new Exception("Failed to fetch JWKS URI.");
}
return jwksUri;
}
}
第 3 步:获取签名密钥
使用以下方法从 JWK 中获取签名密钥:
private static RSAKey getSigningKeyFromJwks(String jwksUri, String token) throws Exception {
JWKSet jwkSet = JWKSet.load(new URL(jwksUri));
List<com.nimbusds.jose.jwk.JWK> keys = jwkSet.getKeys();
SignedJWT signedJWT = SignedJWT.parse(token);
String keyId = signedJWT.getHeader().getKeyID();
return keys.stream()
.filter(jwk -> jwk.getKeyID().equals(keyId))
.findFirst()
.map(jwk -> (RSAKey) jwk)
.orElse(null);
}
第 4 步:验证 JWT
为了演示如何验证 JWT,我们将在 Spring Boot 应用中实现一个 validateJWT 端点。该端点会从 Bearer Authorization 请求头中读取访问令牌。
它会调用第 1 步和第 2 步中的 fetchJwksUri() 和 getSigningKeyFromJwks(),获取解析 JWT 所需的 JWK URI 和签名密钥。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
@SpringBootApplication
@RestController
public class DemoApplication {
//paste implementation for fetchJwksUri() method below this line.
//paste implemetation of getSigningKeyFromJwks() method below this line.
private static final String BASE_ADDRESS = ""; //place your authgear app endpoint here
@GetMapping("/validateJwt")
public Object validateJwt(@RequestHeader("Authorization") String authorizationHeader) {
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
return new ResponseMessage("authorization header not found");
}
String token = authorizationHeader.substring(7); // Extract token
try {
// Fetch JWKS URI dynamically
String jwksUri = fetchJwksUri(BASE_ADDRESS);
// Get signing key from JWKS
RSAKey signingKey = getSigningKeyFromJwks(jwksUri, token);
if (signingKey == null) {
return new ResponseMessage("JWT decode failed: Signing key not found");
}
// Validate and decode JWT
SignedJWT signedJWT = SignedJWT.parse(token);
JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();
return new ResponseMessage("Hello!", claimsSet.toJSONObject());
} catch (Exception e) {
return new ResponseMessage("JWT decode failed: " + e.getMessage());
}
}
private static String fetchJwksUri(String baseAddress) throws Exception {
String docUrl = baseAddress + "/.well-known/openid-configuration";
HttpURLConnection conn = (HttpURLConnection) new URL(docUrl).openConnection();
conn.setRequestMethod("GET");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
String jwksUri = new org.json.JSONObject(response.toString()).getString("jwks_uri");
if (jwksUri == null || jwksUri.isEmpty()) {
throw new Exception("Failed to fetch JWKS URI.");
}
return jwksUri;
}
}
private static RSAKey getSigningKeyFromJwks(String jwksUri, String token) throws Exception {
JWKSet jwkSet = JWKSet.load(new URL(jwksUri));
List<com.nimbusds.jose.jwk.JWK> keys = jwkSet.getKeys();
SignedJWT signedJWT = SignedJWT.parse(token);
String keyId = signedJWT.getHeader().getKeyID();
return keys.stream()
.filter(jwk -> jwk.getKeyID().equals(keyId))
.findFirst()
.map(jwk -> (RSAKey) jwk)
.orElse(null);
}
static class ResponseMessage {
public String message;
public Object user_data;
public ResponseMessage(String message) {
this.message = message;
}
public ResponseMessage(String message, Object user_data) {
this.message = message;
this.user_data = user_data;
}
}
}
第 1 步:安装依赖包
首先,运行以下命令安装所需的依赖:
第 2 步:查找 JWKS 端点
使用以下代码创建一个函数,从您的 Authany 应用端点中查找 JWKS 端点:
<?php
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
use Firebase\JWT\Key;
use GuzzleHttp\Client;
$appUrl = ""; //place your authgear app endpoint here
function getJwksUri($appUrl) {
$configEndpoint = $appUrl . "/.well-known/openid-configuration";
$httpClient = new Client();
$response = $httpClient->request('GET', $configEndpoint);
$responseObject = json_decode($response->getBody());
return $responseObject->jwks_uri;
}
第 3 步:获取签名密钥
在您的应用中添加以下代码来获取 JWT 签名密钥:
$jwksUri = getJwksUri($appUrl);
$httpClient = new Client();
$jwksUriResponse = $httpClient->request('GET', $jwksUri);
$keysObject = json_decode($jwksUriResponse->getBody());
$jwks = (array) ($keysObject->keys)[0];
$parsedKey = JWK::parseKey($jwks, "RS256");
$signingKey = $parsedKey->getKeyMaterial();
第 4 步:从请求头中提取 JWT
使用以下代码从 HTTP 请求中提取访问令牌:
if (!isset($_SERVER['HTTP_AUTHORIZATION']))
throw new Exception("Invalid authorization header");
$authorizationHeader = $_SERVER['HTTP_AUTHORIZATION'];
$jwt = (explode(" ", $authorizationHeader))[1];
第 5 步:验证并解码 JWT
最后,用签名密钥解码 JWT。
第 1 步:安装 NuGet 包
然后在 program.cs 文件顶部添加以下导入:
第 2 步:配置 JWT 身份验证
这会让 ASP.NET Core 使用 JWT Bearer 令牌进行身份验证
var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration;
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x => // here we configure what to validate in JWT tokens
{
// .NET will automatically fetch JWKS keys from {authority}/.well-known/openid-configuration
x.Authority = ""; // place your authgear app endpoint here,
x.RequireHttpsMetadata = false; // Allow HTTP for development
x.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = x.Authority,
ValidateAudience = false, // set to true if you validate audience
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
};
});
第 3 步:添加授权
第 4 步:配置中间件管道
顺序很重要!身份验证必须在授权之前
第 5 步:创建受保护的端点
以下示例使用 Minimal API 模型
app.MapGet("/", (HttpContext context) =>
{
var user = context.User;
Console.WriteLine($"Authorization header: {context.Request.Headers.Authorization}");
Console.WriteLine($"User authenticated: {user.Identity?.IsAuthenticated}");
if (user.Identity?.IsAuthenticated == true)
{
var claims = user.Claims.ToDictionary(c => c.Type, c => c.Value);
return Results.Ok(claims);
}
return Results.Json(new { error = "Unauthorized" }, statusCode: 401);
}).RequireAuthorization(); // require auth from this endpoint
对于基于 Controller 的 API,只需在控制器类或各个 action 方法上添加 [Authorize] 即可对其进行保护
检查 JWT 的有效性¶
OIDC ID 令牌中的 auth_time 声明表示用户完成身份验证的时间。请从令牌中提取 auth_time 声明,它应表示原始身份验证发生的时间(以秒为单位)。如果当前时间与 auth_time 之差超过您设定的阈值(例如 5 分钟),则发起重新验证身份流程。
关于如何验证 ID 令牌的签名,以及验证其中的 auth_time 声明,请参见这里的示例。
从 Cookie 中解码用户信息¶
在应用服务器中验证 JWT _目前_仅适用于基于令牌的身份验证。
说明
对于基于 Cookie 的身份验证,目前还不支持在 Cookie 中使用 JWT。您可以在此追踪相关议题。