JavaToken实战指南:从原理到应用
好的,这是一份非常详细、实用、通俗易懂、权威且全面的 Java Token 全面指南。我们将从基础概念开始,逐步深入,涵盖原理、实战代码和完整的系统案例。
Java Token 全面指南:从原理到实战
目录
- 什么是 Token? 1.1 概念与类比 1.2 在 Java 环境中的核心作用 1.3 常见 Token 类型概述
- Java 源码解析:编译器视角下的 Token 2.1 词法分析 (Lexical Analysis) 基础 2.2 Java 语言的 Token 类型详解 2.3 实战:使用
StringTokenizer(基础解析) 2.4 实战:使用正则表达式Pattern和Matcher(灵活解析) 2.5 实战:手动实现简易词法分析器 (理解原理) - 身份认证与授权:安全领域的 Token (JWT 为例) 3.1 认证 (Authentication) 与授权 (Authorization) 基础 3.2 为什么需要 Token (Session 的局限) 3.3 JWT (JSON Web Token) 深入解析 3.3.1 结构 (Header, Payload, Signature) 3.3.2 签名算法与安全性 3.3.3 优点与适用场景 3.3.4 安全注意事项 3.4 实战:使用
java-jwt库创建和验证 JWT - API 访问控制:OAuth 2.0 中的 Token 4.1 OAuth 2.0 框架简介 4.2 Access Token 与 Refresh Token 4.2.1 作用与生命周期 4.2.2 最佳安全实践 4.3 实战:模拟 OAuth 2.0 授权服务器颁发 Token (简化版) 4.4 实战:资源服务器验证 Access Token (简化版)
- 令牌化 (Tokenization) 与敏感数据处理 5.1 令牌化概念 (非认证 Token) 5.2 应用场景:支付卡信息 (PCI DSS)、个人身份信息 (PII) 保护 5.3 与加密的区别 5.4 实战:简易支付卡令牌化系统示例
- 系统案例:综合应用 6.1 案例一:Java 源码关键词提取工具 6.2 案例二:基于 JWT 的 RESTful API 用户认证系统 6.3 案例三:简易 OAuth 2.0 受保护的资源访问系统
1. 什么是 Token?
1.1 概念与类比
想象一下你进入一个高档俱乐部。门卫不会每次都检查你的详细资料(姓名、住址、职业等),而是查看你的会员卡。这张会员卡就是一个 Token。它代表了你已经被验证过的身份和拥有的权限(比如进入特定区域)。在计算机世界中,Token 扮演着类似的角色:它是一种凭证或代表物,用于表示身份、权限、状态或特定数据片段。
1.2 在 Java 环境中的核心作用
Token 在 Java 应用的多个层面发挥着关键作用:
- 编译器层面: Java 源代码在编译前会被分解成更小的、有意义的单元,这些单元就是 Token(如关键字、标识符、运算符、字面量)。这是代码能被理解和执行的基础。
- 安全层面: 用于用户身份认证(Authentication)和访问授权(Authorization)。例如,JWT (JSON Web Token) 是 RESTful API 中广泛使用的认证 Token。
- API 交互层面: 在 OAuth 2.0 等授权框架中,Access Token 是客户端访问受保护资源的“钥匙”。
- 数据安全层面: Tokenization(令牌化)用于保护敏感数据(如信用卡号),用无意义的 Token 代替原始数据存储,原始数据则安全地存放在其他地方。
1.3 常见 Token 类型概述
- 词法 Token:
int,"Hello",+,myVariable,;(Java 编译过程使用) - 认证 Token: JWT, Opaque Token, SAML Token (用于验证用户身份)
- 授权 Token: OAuth 2.0 Access Token, Refresh Token (用于授予访问权限)
- 数据 Token: 代表原始敏感数据(如信用卡号)的随机字符串 (用于数据保护)
2. Java 源码解析:编译器视角下的 Token
当你在 IDE 中编写 Java 代码并点击运行时,Java 编译器 (javac) 首先会进行词法分析 (Lexical Analysis)。这一步将连续的字符流(你的源代码)分解成一系列有意义的词法单元 (Lexical Tokens) 或简称 Token。这是理解代码的第一步。
2.1 词法分析 (Lexical Analysis) 基础
词法分析器(也称为扫描器 Scanner)的工作:
- 读取: 逐个字符读取源代码。
- 分类: 根据 Java 语言规范,将字符序列组合成特定类型的 Token。
- 输出: 生成一个 Token 序列(流),供下一阶段的语法分析器 (Parser) 使用。
2.2 Java 语言的 Token 类型详解
Java 编译器识别的主要 Token 类型包括:
- 关键字: 具有特殊含义的保留字。如
public,class,static,void,if,else,for,while等。 - 标识符: 由程序员定义的名称,用于变量、方法、类、接口等。如
myVariable,calculateSum,String,UserRepository。规则:以字母、_或$开头,后可跟字母、数字、_或$。 - 字面量: 直接在代码中写出的常量值。
- 整数:
42,0x2A,0b101010 - 浮点数:
3.14,6.022e23 - 字符:
'A',' ','A' - 字符串:
"Hello, World!" - 布尔值:
true,false - null:
null
- 整数:
- 运算符: 用于执行操作的符号。如
+,-,*,/,%,=,==,!=,<,>,<=,>=,&&,||,!,++,--,?:,instanceof等。 - 分隔符: 用于分组代码或定义结构。如
(,),{,},[,],;,,,.。 - 注释:
// 单行注释,/* 多行注释 */,/** 文档注释 */(虽然编译器最终可能忽略其内容,但词法分析器需要识别它们作为特定 Token)。
2.3 实战:使用 StringTokenizer (基础解析)
java.util.StringTokenizer 是一个相对古老但简单的类,用于将字符串分解成 Token。它默认根据空格、制表符、换行符、回车符进行分割。
import java.util.StringTokenizer;
public class StringTokenizerExample {
public static void main(String[] args) {
String javaCode = "public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, Token!"); } }";
StringTokenizer tokenizer = new StringTokenizer(javaCode);
System.out.println("Tokens extracted by StringTokenizer:");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
System.out.println(token);
}
}
}
运行结果: 会按空格分割输出每个单词/符号。但请注意,它不会识别 Java 语法,只是简单地按空格分割。对于 String[] args 这样的部分,它会分割成 String[] 和 args。
2.4 实战:使用正则表达式 Pattern 和 Matcher (灵活解析)
正则表达式提供了更强大的模式匹配能力,可以用来定义更复杂的 Token 规则。
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class RegexTokenExample {
public static void main(String[] args) {
String javaCode = "int sum = 100 + 200; // Calculate total";
// 一个简单的正则,匹配:标识符、关键字、数字、运算符、注释
// 注意:这个正则非常简化,仅用于示例,不能覆盖所有Java语法
String regex = "(//.*)|("[^"]*")|(d+)|(w+)|([=+;])";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(javaCode);
System.out.println("Tokens extracted by Regex:");
while (matcher.find()) {
String token = matcher.group().trim();
if (!token.isEmpty()) { // 忽略空匹配
System.out.println("Found token: '" + token + "'");
}
}
}
}
运行结果: 可能会匹配出 int, sum, =, 100, +, 200, ;, // Calculate total。正则表达式提供了灵活性,但编写一个能完美处理所有 Java 语法的正则极其复杂。
2.5 实战:手动实现简易词法分析器 (理解原理)
为了更深入理解编译器如何工作,我们可以手动实现一个非常简易的词法分析器,只处理部分 Token 类型(如标识符、整数、运算符)。
public class SimpleLexer {
private final String input;
private int position = 0; // 当前位置
private char currentChar; // 当前字符
public SimpleLexer(String input) {
this.input = input;
this.currentChar = input.length() > 0 ? input.charAt(0) : ' ';
}
// 前进一个字符
private void advance() {
position++;
if (position < input.length()) {
currentChar = input.charAt(position);
} else {
currentChar = ' '; // 结束标记
}
}
// 跳过空白字符
private void skipWhitespace() {
while (currentChar != ' ' && Character.isWhitespace(currentChar)) {
advance();
}
}
// 获取下一个 Token
public Token getNextToken() {
while (currentChar != ' ') {
if (Character.isWhitespace(currentChar)) {
skipWhitespace();
continue;
}
if (Character.isLetter(currentChar)) { // 标识符或关键字
return parseIdentifierOrKeyword();
}
if (Character.isDigit(currentChar)) { // 整数
return parseInteger();
}
// 简单处理几个运算符
if (currentChar == '=') {
advance();
return new Token(TokenType.ASSIGN, "=");
}
if (currentChar == '+') {
advance();
return new Token(TokenType.PLUS, "+");
}
if (currentChar == ';') {
advance();
return new Token(TokenType.SEMICOLON, ";");
}
// 无法识别,抛出错误 (简化处理)
throw new RuntimeException("Unexpected character: " + currentChar);
}
return new Token(TokenType.EOF, "");
}
// 解析标识符或关键字
private Token parseIdentifierOrKeyword() {
StringBuilder result = new StringBuilder();
while (currentChar != ' ' && (Character.isLetterOrDigit(currentChar) || currentChar == '_')) {
result.append(currentChar);
advance();
}
String value = result.toString();
// 这里可以检查是否是关键字,简化处理都当作标识符
return new Token(TokenType.IDENTIFIER, value);
}
// 解析整数
private Token parseInteger() {
StringBuilder result = new StringBuilder();
while (currentChar != ' ' && Character.isDigit(currentChar)) {
result.append(currentChar);
advance();
}
return new Token(TokenType.INTEGER, result.toString());
}
// Token 类型枚举
public enum TokenType {
IDENTIFIER, INTEGER, ASSIGN, PLUS, SEMICOLON, EOF
}
// Token 类
public static class Token {
public final TokenType type;
public final String value;
public Token(TokenType type, String value) {
this.type = type;
this.value = value;
}
@Override
public String toString() {
return "Token(" + type + ", '" + value + "')";
}
}
public static void main(String[] args) {
String testInput = "x = 42 + y;";
SimpleLexer lexer = new SimpleLexer(testInput);
Token token;
do {
token = lexer.getNextToken();
System.out.println(token);
} while (token.type != TokenType.EOF);
}
}
运行结果:
Token(IDENTIFIER, 'x')
Token(ASSIGN, '=')
Token(INTEGER, '42')
Token(PLUS, '+')
Token(IDENTIFIER, 'y')
Token(SEMICOLON, ';')
Token(EOF, '')
这个简易分析器展示了词法分析的基本流程:跳过空白、按字符类型分支、组合字符序列形成 Token。真实的编译器词法分析器要复杂得多,处理所有 Token 类型和边界情况。
3. 身份认证与授权:安全领域的 Token (JWT 为例)
3.1 认证 (Authentication) 与授权 (Authorization) 基础
- 认证: 验证用户身份(你是谁?)。例如:用户名/密码登录、指纹识别。
- 授权: 验证用户是否有权限执行某项操作(你能做什么?)。例如:管理员可以删除用户,普通用户只能查看。
3.2 为什么需要 Token (Session 的局限)
传统的 Web 应用常使用 Session 进行状态管理:
- 用户登录,服务器验证凭据。
- 服务器创建 Session(存储用户信息),生成一个唯一的
Session ID。 - 服务器将
Session ID通过 Cookie 返回给浏览器。 - 后续请求,浏览器自动带上 Cookie(含
Session ID)。 - 服务器根据
Session ID查找 Session,获取用户状态。
Session 的局限性:
- 服务器存储: Session 数据通常存储在服务器内存或数据库中。用户量大时,存储开销大。
- 扩展性: 在分布式或微服务架构中,需要 Session 共享机制(如 Redis),增加了复杂性。
- 移动端/API 友好性: 原生 App 或 API 客户端处理 Cookie 不如浏览器方便。
- 跨域: Cookie 默认策略在跨域场景下受限。
Token 的优势 (如 JWT):
- 无状态: Token 本身包含所需信息(如用户 ID、角色)。服务器无需存储会话状态,只需验证 Token 有效性(签名)。
- 跨域/跨平台友好: Token 通常放在 HTTP Header (如
Authorization: Bearer) 中传递,易于各种客户端处理。 - 灵活性: 可以包含丰富的声明 (Claims)。
- 标准化: JWT 是开放标准 (RFC 7519),各种语言都有成熟库支持。
3.3 JWT (JSON Web Token) 深入解析
JWT 是一种紧凑的、URL 安全的 Token 格式,用于在各方之间安全地传输信息。
3.3.1 结构 (Header, Payload, Signature)
JWT 由三部分组成,用点 . 分隔:
Header.Payload.Signature
- Header (头部):
- 通常包含 Token 类型 (
"typ": "JWT") 和使用的签名算法 ("alg": "HS256"或"RS256"等)。 - Base64Url 编码。
- 示例:
{"alg": "HS256", "typ": "JWT"}
- 通常包含 Token 类型 (
- Payload (负载): 包含声明 (Claims)。声明是关于实体(通常是用户)和附加数据的语句。有三种类型的声明:
- Registered Claims: 预定义的、建议使用的声明。如
iss(签发者),exp(过期时间),sub(主题/用户 ID),aud(受众)。 - Public Claims: 可以自定义,但为了避免冲突应在 IANA JSON Web Token Registry 中定义,或者使用有抗冲突命名空间(如包含域名)的名称。
- Private Claims: 自定义声明,用于在同意使用它们的各方之间共享信息。
- Base64Url 编码。
- 示例:
{"sub": "1234567890", "name": "John Doe", "iat": 1516239022, "exp": 1516239022 + 3600}
- Registered Claims: 预定义的、建议使用的声明。如
- Signature (签名): 用于验证消息在传输过程中未被篡改,并且对于使用私钥签名的 Token,还可以验证 JWT 的发送方是否真实。
- 签名计算公式:
Signature = SigningAlgorithm(Base64UrlEncode(Header) + "." + Base64UrlEncode(Payload), Secret/PrivateKey) - 例如,对于
HS256(HMAC SHA-256):HMAC_SHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) - 最终 Token:
base64UrlEncode(Header) + "." + base64UrlEncode(Payload) + "." + base64UrlEncode(Signature)
- 签名计算公式:
3.3.2 签名算法与安全性
- 对称签名 (如 HS256): 使用同一个密钥 (
secret) 进行签名和验证。速度快,但密钥必须安全地在签发方和验证方共享。密钥泄露意味着任何人都可以签发有效 Token。适用于内部系统或单点验证。 - 非对称签名 (如 RS256): 使用私钥 (
private key) 签名,公钥 (public key) 验证。私钥由 Token 签发方严格保管,公钥可以分发给任何需要验证 Token 的方。安全性更高,是跨系统、跨组织的首选。速度比对称签名慢。 - 无签名:
"alg": "none"。极其危险,不应在生产环境中使用。 任何人都可以篡改 Payload 并声称是有效的无签名 JWT。
3.3.3 优点与适用场景
- 优点:
- 无状态、可扩展性好。
- 信息自包含,减少数据库查询。
- 跨语言、跨平台支持好。
- 可用于认证 (
id_token) 和授权 (access_token)。
- 适用场景:
- RESTful API / 微服务认证授权。
- 单点登录 (SSO)。
- 移动应用认证。
- 服务间安全通信。
3.3.4 安全注意事项
- 敏感信息: 不要将高度敏感信息(如密码)放入 Payload。Payload 是 Base64 编码,不是加密!任何人都可以解码查看内容。JWT 保证的是内容未被篡改(签名),而不是内容保密。如需保密,应对整个 JWT 进行加密 (JWE - JSON Web Encryption)。
- 密钥管理: 安全地存储密钥(对称)或私钥(非对称)。定期轮换密钥。
- Token 有效期: 设置合理的过期时间 (
expclaim)。使用 Refresh Token 机制获取新的 Access Token。 - Token 存储: 客户端安全地存储 Token (如 HttpOnly Cookie, Secure Storage)。防止 XSS 攻击窃取 Token。
- 令牌撤销: JWT 在过期前无法被服务器主动撤销。如果需要即时撤销能力,可能需要结合其他机制(如较短的过期时间、Token 黑名单、或使用 Opaque Token)。
- 算法选择: 避免使用弱算法 (
none,HS256如果密钥强度不足)。优先使用RS256,ES256等非对称算法。 - 验证所有字段: 验证时务必检查签名、
iss(签发者)、aud(受众)、exp(过期时间)、nbf(生效时间) 等关键声明。
3.4 实战:使用 java-jwt 库创建和验证 JWT
我们将使用 com.auth0:java-jwt 库进行演示。
步骤 1:添加依赖
com.auth0
java-jwt
4.4.0
步骤 2:创建 JWT (签发)
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
public class JwtCreateExample {
public static void main(String[] args) {
// 1. 定义密钥 (HS256 对称算法示例,生产环境请使用强密钥并安全存储!)
String secret = "mySuperSecretKey"; // 实际应用中应使用长且复杂的密钥,并从安全配置读取
Algorithm algorithm = Algorithm.HMAC256(secret);
// 2. 计算过期时间 (例如 1 小时后)
long expiresAtMillis = System.currentTimeMillis() + (60 * 60 * 1000); // 1 hour
Date expiresAt = new Date(expiresAtMillis);
// 3. 创建 JWT Builder 并设置声明
String token = JWT.create()
.withIssuer("your-issuer") // iss - 签发者
.withSubject("user123") // sub - 主题 (通常是用户ID)
.withAudience("your-audience") // aud - 受众 (接收Token的系统)
.withExpiresAt(expiresAt) // exp - 过期时间
.withIssuedAt(new Date()) // iat - 签发时间
.withClaim("name", "John Doe") // 自定义声明
.withClaim("role", "admin") // 自定义声明
.sign(algorithm); // 使用算法签名
System.out.println("Generated JWT:");
System.out.println(token);
}
}
运行结果: 输出一个类似 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaXNzIjoieW91ci1pc3N1ZXIiLCJhdWQiOiJ5b3VyLWF1ZGllbmNlIiwiZXhwIjoxN... 的字符串。
步骤 3:验证 JWT (验证)
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
public class JwtVerifyExample {
public static void main(String[] args) {
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // 替换为上一步生成的Token
String secret = "mySuperSecretKey"; // 必须与创建时使用的密钥一致
Algorithm algorithm = Algorithm.HMAC256(secret);
try {
// 1. 创建 Verifier 实例,指定算法、签发者、受众
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("your-issuer") // 必须匹配
.withAudience("your-audience") // 必须匹配
.build();
// 2. 验证 Token
DecodedJWT decodedJWT = verifier.verify(token);
// 3. 如果验证通过,提取信息
System.out.println("Token is valid!");
System.out.println("Subject (User ID): " + decodedJWT.getSubject());
System.out.println("Name: " + decodedJWT.getClaim("name").asString());
System.out.println("Role: " + decodedJWT.getClaim("role").asString());
System.out.println("Expires at: " + decodedJWT.getExpiresAt());
} catch (JWTVerificationException exception) {
// 验证失败 (签名无效、过期、iss/aud不匹配等)
System.err.println("Token verification failed: " + exception.getMessage());
}
}
}
运行结果: 如果 Token 有效且匹配 issuer 和 audience,将输出 Token 中包含的用户信息。如果无效(如修改 Token 字符串、过期、密钥错误),将捕获 JWTVerificationException 并打印错误信息。
4. API 访问控制:OAuth 2.0 中的 Token
OAuth 2.0 是一个授权框架,允许第三方应用在用户授权后,代表用户访问用户在另一个服务上的资源,而无需分享用户的密码。
4.1 OAuth 2.0 框架简介
主要角色:
- 资源所有者 (Resource Owner): 通常是用户,拥有被访问资源的所有权。
- 客户端 (Client): 想要访问用户资源的第三方应用 (如 Web App, Mobile App)。
- 资源服务器 (Resource Server): 托管受保护资源的服务器 (如 API 服务器)。
- 授权服务器 (Authorization Server): 在认证用户并获得授权后,向客户端颁发 Token 的服务器。
授权流程 (以授权码流程为例):
- 客户端引导用户到授权服务器登录并授权。
- 用户同意授权。
- 授权服务器将授权码 (
authorization_code) 返回给客户端 (通常通过重定向)。 - 客户端使用授权码 + 客户端凭证向授权服务器请求 Access Token。
- 授权服务器验证授权码和客户端凭证,颁发 Access Token (和可选的 Refresh Token)。
- 客户端使用 Access Token 向资源服务器请求资源。
- 资源服务器验证 Access Token,返回受保护资源。
4.2 Access Token 与 Refresh Token
4.2.1 作用与生命周期
- Access Token:
- 作用: 客户端使用它访问受保护资源。它代表用户授予客户端的权限范围和有效期。
- 生命周期: 短寿命 (几分钟到几小时)。一旦过期或泄露,危害时间有限。
- 格式: 可以是 JWT (包含信息) 或 Opaque Token (只是一个随机字符串,验证需回查授权服务器)。
- Refresh Token:
- 作用: 当 Access Token 过期时,客户端可以使用 Refresh Token 向授权服务器请求一个新的 Access Token (可能同时得到新的 Refresh Token)。它代表了用户授予客户端的长期授权。
- 生命周期: 长寿命 (几天、几周、几个月,甚至更长,但应可撤销)。它比 Access Token 更敏感!
- 格式: 通常是 Opaque Token。
- 存储: 必须由客户端安全存储 (如服务器端 Web App 的数据库、移动设备的 Secure Enclave)。绝不能暴露给浏览器 JavaScript (防止 XSS 窃取)。
4.2.2 最佳安全实践
- Access Token:
- 使用 HTTPS 传输。
- 设置短有效期。
- 避免存储敏感信息 (如果使用 JWT)。
- 客户端安全存储 (如移动端 Secure Storage, Web 应用 Session Storage 或内存)。
- Refresh Token:
- 使用 HTTPS 传输。
- 绑定到特定的客户端和用户 (防止被盗后在其他地方使用)。
- 提供撤销机制。
- 仅在安全的后端通道交换 (避免暴露给前端)。
- 客户端必须极其安全地存储。
- 轮换策略:每次使用 Refresh Token 获取新 Access Token 时,授权服务器可以颁发一个新的 Refresh Token 并使旧的失效。
4.3 实战:模拟 OAuth 2.0 授权服务器颁发 Token (简化版)
这是一个极度简化的模拟,仅用于演示核心概念。真实系统涉及数据库、用户认证、授权码存储、HTTPS 等。
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class MockAuthorizationServer {
private static final Map authorizationCodeStore = new HashMap<>(); // 模拟存储授权码和用户ID的映射
private static final Map refreshTokenStore = new HashMap<>(); // 模拟存储Refresh Token和用户ID的映射
private static final String SECRET = "authServerSecret"; // 授权服务器密钥 (用于签名JWT)
// 步骤1:用户认证后生成授权码 (模拟)
public static String generateAuthorizationCode(String userId) {
String authCode = UUID.randomUUID().toString(); // 生成随机授权码
authorizationCodeStore.put(authCode, userId); // 存储授权码关联的用户ID
return authCode;
}
// 步骤2:客户端用授权码换取Token (模拟/token 端点)
public static Map exchangeCodeForTokens(String authCode, String clientId, String clientSecret) {
// 1. 验证授权码 (简化:检查是否存在)
String userId = authorizationCodeStore.get(authCode);
if (userId == null) {
throw new RuntimeException("Invalid authorization code");
}
// 2. (简化) 这里应验证 clientId 和 clientSecret
authorizationCodeStore.remove(authCode); // 授权码一次性使用,移除
// 3. 生成 Access Token (JWT)
Algorithm algorithm = Algorithm.HMAC256(SECRET);
long accessExpiresAt = System.currentTimeMillis() + (10 * 60 * 1000); // 10分钟过期
String accessToken = JWT.create()
.withSubject(userId)
.withIssuer("MockAuthServer")
.withExpiresAt(new Date(accessExpiresAt))
.withClaim("scope", "read write") // 模拟权限范围
.sign(algorithm);
// 4. 生成 Refresh Token (随机字符串)
String refreshToken = UUID.randomUUID().toString();
refreshTokenStore.put(refreshToken, userId); // 存储Refresh Token关联的用户ID
// 5. 返回 Token
Map tokens = new HashMap<>();
tokens.put("access_token", accessToken);
tokens.put("token_type", "Bearer");
tokens.put("expires_in", "600"); // 10分钟(600秒)
tokens.put("refresh_token", refreshToken);
return tokens;
}
// 步骤3:使用Refresh Token获取新Access Token (模拟/token 端点)
public static Map refreshAccessToken(String refreshToken) {
// 1. 验证Refresh Token
String userId = refreshTokenStore.get(refreshToken);
if (userId == null) {
throw new RuntimeException("Invalid refresh token");
}
// 2. 生成新的 Access Token
Algorithm algorithm = Algorithm.HMAC256(SECRET);
long accessExpiresAt = System.currentTimeMillis() + (10 * 60 * 1000); // 新Token有效期10分钟
String newAccessToken = JWT.create()
.withSubject(userId)
.withIssuer("MockAuthServer")
.withExpiresAt(new Date(accessExpiresAt))
.withClaim("scope", "read write")
.sign(algorithm);
// 3. (可选) 可以返回新的Refresh Token并使旧的失效 (此处简化,返回旧的Refresh Token)
Map tokens = new HashMap<>();
tokens.put("access_token", newAccessToken);
tokens.put("token_type", "Bearer");
tokens.put("expires_in", "600");
tokens.put("refresh_token", refreshToken); // 返回同一个Refresh Token
return tokens;
}
public static void main(String[] args) {
// 模拟流程
String userId = "user123";
// 1. 用户登录授权后,生成授权码
String authCode = generateAuthorizationCode(userId);
System.out.println("Generated Auth Code: " + authCode);
// 2. 客户端用授权码换取Token
Map tokens = exchangeCodeForTokens(authCode, "clientId", "clientSecret");
System.out.println("Access Token: " + tokens.get("access_token"));
System.out.println("Refresh Token: " + tokens.get("refresh_token"));
// 3. 模拟 Access Token 过期后,用 Refresh Token 获取新的
Map newTokens = refreshAccessToken(tokens.get("refresh_token"));
System.out.println("New Access Token: " + newTokens.get("access_token"));
}
}
运行结果: 模拟生成授权码、换取 Access Token 和 Refresh Token、以及使用 Refresh Token 刷新 Access Token 的过程。
4.4 实战:资源服务器验证 Access Token (简化版)
资源服务器收到携带 Access Token 的请求后,需要验证其有效性。
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
public class MockResourceServer {
private static final String AUTH_SERVER_SECRET = "authServerSecret"; // 必须与授权服务器一致
public static boolean verifyAccessToken(String accessToken) {
try {
Algorithm algorithm = Algorithm.HMAC256(AUTH_SERVER_SECRET);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("MockAuthServer") // 验证签发者
.build();
DecodedJWT decodedJWT = verifier.verify(accessToken);
// 如果验证通过,可以进一步检查 scope 等声明是否符合请求的资源
System.out.println("Token verified for user: " + decodedJWT.getSubject());
return true;
} catch (JWTVerificationException exception) {
System.err.println("Token verification failed: " + exception.getMessage());
return false;
}
}
public static void main(String[] args) {
// 假设这是从请求头 Authorization: Bearer 中提取的Token
String accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // 替换为有效的Access Token字符串
if (verifyAccessToken(accessToken)) {
System.out.println("Access granted to protected resource!");
// 这里处理受保护的资源逻辑
} else {
System.out.println("Access denied!");
}
}
}
运行结果: 验证 Token 的有效性,并根据结果模拟是否允许访问受保护资源。
5. 令牌化 (Tokenization) 与敏感数据处理
令牌化 (Tokenization) 是一种数据安全技术,用于保护敏感信息。它用 Token 替代原始敏感数据。Token 本身没有内在价值或意义,无法被反向推导出原始数据。
5.1 令牌化概念 (非认证 Token)
- 核心思想: 将敏感数据 (如信用卡号
4111 1111 1111 1111) 发送给一个安全的 令牌化系统。 - 令牌化系统:
- 安全地存储原始数据 (通常在一个高度安全的数据库或硬件安全模块 HSM 中)。
- 生成一个随机的、唯一的 Token (如
tok_7a8b9c0d1e2f3g4h)。 - 将这个 Token 返回给请求方。
- 应用程序: 存储和使用这个 Token (
tok_7a8b9c0d1e2f3g4h) 代替原始信用卡号进行后续处理(如交易请求、存储订单信息)。 - 需要原始数据时: 只有授权系统可以向令牌化系统提交 Token 来取回原始数据。
5.2 应用场景:支付卡信息 (PCI DSS)、个人身份信息 (PII) 保护
- PCI DSS 合规: 支付卡行业数据安全标准要求保护持卡人数据。使用令牌化可以显著减少需要符合 PCI DSS 要求的系统范围,因为只有令牌化系统存储原始卡号。
- 保护 PII: 如身份证号、护照号、社保号、健康信息等。应用系统存储 Token 而非原始数据,降低数据泄露风险。
- 测试数据脱敏: 为开发测试环境提供符合生产数据的 Token 化版本,保护生产数据隐私。
5.3 与加密的区别
- 加密:
- 使用密钥和算法将原始数据转换为密文。
- 密文可以通过密钥解密回原始数据。
- 加密密钥的安全管理是关键。
- 加密后的数据长度通常与原始数据相关。
- 令牌化:
- 用随机生成的 Token 替换原始数据。
- Token 本身没有意义,也无法通过 Token 本身推导出原始数据(除非访问令牌化系统)。
- 原始数据安全地存储在令牌化系统中。
- Token 的格式和长度可以自定义(如保留最后4位卡号
tok_xxxx1111),与原始数据无关。 - 优势: 即使 Token 数据库被泄露,攻击者也无法获得原始敏感数据(只要令牌化系统的核心数据存储未被攻破)。密钥管理负担主要在令牌化系统。
5.4 实战:简易支付卡令牌化系统示例
这是一个高度简化的内存实现,用于演示概念。真实系统需要持久化存储、HSM、高可用、严格访问控制等。
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class SimpleTokenizationSystem {
// 模拟安全存储:Token -> 原始卡号
private static final Map tokenVault = new HashMap<>();
// 模拟反向查找:原始卡号 -> Token (简化,实际可能需要加盐哈希存储卡号)
private static final Map cardToTokenMap = new HashMap<>();
// 令牌化:传入卡号,返回Token
public static String tokenize(String cardNumber) {
// 检查是否已存在Token (去重)
if (cardToTokenMap.containsKey(cardNumber)) {
return cardToTokenMap.get(cardNumber);
}
// 生成唯一Token
String token = "tok_" + UUID.randomUUID().toString().replace("-", "");
// 安全存储映射关系 (模拟)
tokenVault.put(token, cardNumber);
cardToTokenMap.put(cardNumber, token);
return token;
}
// 去令牌化:传入Token,返回原始卡号 (需要权限)
public static String detokenize(String token) {
// 验证Token是否存在
if (!tokenVault.containsKey(token)) {
throw new RuntimeException("Invalid token");
}
// 返回原始卡号 (模拟权限检查通过)
return tokenVault.get(token);
}
// 模拟应用使用Token
public static void main(String[] args) {
String sensitiveCardNumber = "4111111111111111";
// 1. 令牌化:应用将卡号发送给令牌化系统,得到Token
String token = tokenize(sensitiveCardNumber);
System.out.println("Original Card: " + sensitiveCardNumber);
System.out.println("Token: " + token);
// 2. 应用存储和使用Token (例如存储在订单表中)
System.out.println("Application stores and uses token: " + token);
// 3. 后续需要处理支付时(有权限),应用将Token发送给令牌化系统获取原始卡号
try {
String retrievedCardNumber = detokenize(token);
System.out.println("Detokenized Card (for processing): " + retrievedCardNumber);
System.out.println("Match Original? " + sensitiveCardNumber.equals(retrievedCardNumber));
} catch (Exception e) {
System.err.println("Error detokenizing: " + e.getMessage());
}
}
}
运行结果:
Original Card: 4111111111111111
Token: tok_6b29fc456c1b46e0a528f0c8e6f6c7d1
Application stores and uses token: tok_6b29fc456c1b46e0a528f0c8e6f6c7d1
Detokenized Card (for processing): 4111111111111111
Match Original? true
这个示例展示了令牌化的基本流程:敏感数据被 Token 替换存储,仅在需要时由授权系统还原。
6. 系统案例:综合应用
6.1 案例一:Java 源码关键词提取工具
目标: 读取一个 Java 源文件,提取出所有关键字 Token,并统计每个关键字出现的次数。 技术点: 文件读取、词法分析 (使用 Pattern 和 Matcher 识别关键字)。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JavaKeywordExtractor {
// Java 关键字列表 (根据Java版本可能略有不同)
private static final Set JAVA_KEYWORDS = Set.of(
"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const",
"continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float",
"for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native",
"new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super",
"switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while"
);
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: JavaKeywordExtractor ");
System.exit(1);
}
String filePath = args[0];
try {
// 读取Java文件内容
String javaSource = Files.readString(Paths.get(filePath));
// 使用正则匹配单词边界 () 来标识可能的标识符/关键字
Pattern wordPattern = Pattern.compile("w+");
Matcher matcher = wordPattern.matcher(javaSource);
// 统计关键字出现次数
Map keywordCount = new HashMap<>();
while (matcher.find()) {
String word = matcher.group();
// 检查是否是关键字
if (JAVA_KEYWORDS.contains(word)) {
keywordCount.put(word, keywordCount.getOrDefault(word, 0) + 1);
}
}
// 输出结果
System.out.println("Keyword occurrences in '" + filePath + "':");
keywordCount.entrySet().stream()
.sorted(Map.Entry.comparingByKey()) // 按关键字字母排序
.forEach(entry -> System.out.printf("%-15s: %d%n", entry.getKey(), entry.getValue()));
} catch (IOException e) {
System.err.println("Error reading file: " + e.getMessage());
}
}
}
使用方法: 编译后,在命令行运行 java JavaKeywordExtractor path/to/YourJavaFile.java。 输出: 列出该 Java 文件中出现的所有关键字及其出现次数。
6.2 案例二:基于 JWT 的 RESTful API 用户认证系统
目标: 实现一个简单的 Spring Boot RESTful API,使用 JWT 进行用户登录认证和后续请求的授权。 技术点: Spring Boot, Spring Security, JWT (java-jwt), 用户存储 (简化内存)。
步骤 1:项目结构 (Maven) & 依赖 (pom.xml)
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-security
com.auth0
java-jwt
4.4.0
步骤 2:用户模型与内存存储 (User 和 UserRepository)
// User.java
public class User {
private String username;
private String password; // 注意:真实应用应存储哈希加盐后的密码!
private String role;
// Constructor, Getters, Setters
public User(String username, String password, String role) {
this.username = username;
this.password = password;
this.role = role;
}
// ... getters and setters ...
}
// UserRepository.java (内存存储)
import org.springframework.stereotype.Repository;
import java.util.HashMap;
import java.util.Map;
@Repository
public class UserRepository {
private final Map users = new HashMap<>();
public UserRepository() {
// 初始化用户 (密码明文存储仅用于演示!生产环境必须哈希加盐)
users.put("user", new User("user", "password", "USER"));
users.put("admin", new User("admin", "adminpass", "ADMIN"));
}
public User findByUsername(String username) {
return users.get(username);
}
}
步骤 3:JWT 工具类 (JwtUtil)
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class JwtUtil {
@Value("${jwt.secret}") // 从application.properties读取
private String secret;
@Value("${jwt.expiration}") // 过期时间(毫秒)
private long expiration;
public String generateToken(String username, String role) {
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
.withSubject(username)
.withClaim("role", role)
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + expiration))
.sign(algorithm);
}
public DecodedJWT verifyToken(String token) {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).build();
return verifier.verify(token); // 如果无效会抛出异常
}
public String getUsernameFromToken(DecodedJWT jwt) {
return jwt.getSubject();
}
public String getRoleFromToken(DecodedJWT jwt) {
return jwt.getClaim("role").asString();
}
}
步骤 4:登录认证 (AuthController 和 AuthenticationRequest)
// AuthenticationRequest.java
public class AuthenticationRequest {
private String username;
private String password;
// Getters, Setters
}
// AuthController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserRepository userRepository; // 简化,实际应用应使用 UserDetailsService
@PostMapping("/login")
public ResponseEntity> login(@RequestBody AuthenticationRequest request) {
try {
// 1. 使用Spring Security进行认证
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);
// 2. 认证成功后,加载用户详情 (这里简化,直接查询)
User user = userRepository.findByUsername(request.getUsername());
if (user == null) {
throw new RuntimeException("User not found");
}
// 3. 生成JWT Token
String token = jwtUtil.generateToken(user.getUsername(), user.getRole());
// 4. 返回Token
return ResponseEntity.ok(Map.of("token", token));
} catch (Exception e) {
return ResponseEntity.status(401).body("Authentication failed: " + e.getMessage());
}
}
}
步骤 5:配置 Spring Security (SecurityConfig)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserRepository userRepository; // 简化,实际应使用 UserDetailsService
@Autowired
private JwtUtil jwtUtil;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 内存用户配置 (简化,生产环境用数据库)
auth.inMemoryAuthentication()
.withUser("user").password("{noop}password").roles("USER") // {noop}表示明文(仅演示)
.and()
.withUser("admin").password("{noop}adminpass").roles("ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态,用Token
.and()
.authorizeRequests()
.antMatchers("/login").permitAll() // 登录端点开放
.antMatchers("/admin/**").hasRole("ADMIN") // /admin 需要ADMIN角色
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN") // /user 需要USER或ADMIN
.anyRequest().authenticated() // 其他请求需要认证
.and()
.addFilterBefore(new JwtTokenFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class); // JWT过滤器
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean(); // 暴露AuthenticationManager Bean
}
// 简化PasswordEncoder (仅演示,生产环境必须用BCrypt等)
@Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString(); // 明文存储,危险!
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.toString().equals(encodedPassword);
}
};
}
}
步骤 6:JWT 请求过滤器 (JwtTokenFilter)
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtTokenFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
public JwtTokenFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 1. 从请求头获取Token
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response); // 没有Token,继续过滤链 (可能被后续拦截)
return;
}
String token = authorizationHeader.substring(7); // 去掉 "Bearer "
try {
// 2. 验证Token
DecodedJWT decodedJWT = jwtUtil.verifyToken(token);
String username = jwtUtil.getUsernameFromToken(decodedJWT);
String role = jwtUtil.getRoleFromToken(decodedJWT);
// 3. 创建Authentication对象 (简化,实际应从数据库加载用户权限)
// 这里假设用户存在且有效
UserDetails userDetails = org.springframework.security.core.userdetails.User.builder()
.username(username)
.password("") // 密码在Token中已不再需要
.roles(role)
.build();
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
// 4. 设置认证信息到SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (JWTVerificationException e) {
// Token无效,清除上下文
SecurityContextHolder.clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid JWT Token");
return;
}
filterChain.doFilter(request, response);
}
}
步骤 7:受保护资源端点 (UserController, AdminController)
// UserController.java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/hello")
public String userHello() {
return "Hello, User!";
}
}
// AdminController.java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/admin")
public class AdminController {
@GetMapping("/hello")
public String adminHello() {
return "Hello, Admin!";
}
}
步骤 8:application.properties
jwt.secret=mySuperSecretKeyForJWT123! # 强密钥
jwt.expiration=3600000 # 1小时 (毫秒)
server.port=8080
测试流程:
- 登录: POST
http://localhost:8080/loginwith Body{"username": "user", "password": "password"}- Response:
{"token": "eyJhbGciOiJIUzI1NiIs..."}
- Response:
- 访问 User 资源: GET
http://localhost:8080/user/hellowith HeaderAuthorization: Bearer- Response:
"Hello, User!"
- Response:
- 访问 Admin 资源 (用 user Token): GET
http://localhost:8080/admin/hellowith HeaderAuthorization: Bearer- Response:
403 Forbidden(因为 user 角色无权访问 admin 端点)
- Response:
- 用 admin 用户登录: POST
http://localhost:8080/loginwith Body{"username": "admin", "password": "adminpass"} - 访问 Admin 资源 (用 admin Token): GET
http://localhost:8080/admin/hellowith HeaderAuthorization: Bearer- Response:
"Hello, Admin!"
- Response:
6.3 案例三:简易 OAuth 2.0 受保护的资源访问系统
目标: 模拟一个简化的 OAuth 2.0 流程,包含资源所有者、客户端、授权服务器和资源服务器。使用前面 MockAuthorizationServer 和 MockResourceServer 的逻辑进行整合。 说明: 这是一个控制台模拟程序,将步骤打印出来。真实应用需要网络通信、UI 等。
public class OAuth2Simulation {
public static void main(String[] args) {
// 角色初始化
String resourceOwner = "user123"; // 资源所有者
String clientId = "myClientApp";
String clientSecret = "clientSecret123";
String protectedResource = "user_profile_data";
// 1. 资源所有者操作:在客户端发起请求,客户端引导到授权服务器
System.out.println("Step 1: Client redirects Resource Owner to Authorization Server for login and consent.");
// 2. 授权服务器操作:认证用户,获得授权,生成授权码
System.out.println("Step 2: Resource Owner logs in and grants permission on Authorization Server.");
String authCode = MockAuthorizationServer.generateAuthorizationCode(resourceOwner);
System.out.println("Authorization Server generates Auth Code: " + authCode + " and redirects back to Client.");
// 3. 客户端操作:使用授权码向授权服务器请求 Token
System.out.println("Step 3: Client exchanges Auth Code for Access Token and Refresh Token.");
Map tokens = MockAuthorizationServer.exchangeCodeForTokens(authCode, clientId, clientSecret);
String accessToken = tokens.get("access_token");
String refreshToken = tokens.get("refresh_token");
System.out.println("Client receives Access Token: " + accessToken);
System.out.println("Client receives Refresh Token: " + refreshToken);
// 4. 客户端操作:使用 Access Token 访问资源服务器
System.out.println("Step 4: Client accesses Protected Resource on Resource Server with Access Token.");
boolean accessGranted = MockResourceServer.verifyAccessToken(accessToken);
if (accessGranted) {
System.out.println("Resource Server grants access. Resource: " + protectedResource + " is provided.");
} else {
System.out.println("Resource Server denies access.");
}
// 5. 模拟 Access Token 过期
System.out.println("
--- Simulating Access Token Expiration ---");
// 6. 客户端操作:使用 Refresh Token 获取新的 Access Token
System.out.println("Step 6: Client uses Refresh Token to get new Access Token.");
Map newTokens = MockAuthorizationServer.refreshAccessToken(refreshToken);
String newAccessToken = newTokens.get("access_token");
System.out.println("Client receives new Access Token: " + newAccessToken);
// 7. 客户端操作:使用新的 Access Token 再次访问资源服务器
System.out.println("Step 7: Client accesses Protected Resource again with new Access Token.");
accessGranted = MockResourceServer.verifyAccessToken(newAccessToken);
if (accessGranted) {
System.out.println("Resource Server grants access. Resource: " + protectedResource + " is provided.");
} else {
System.out.println("Resource Server denies access.");
}
}
}
运行结果: 在控制台输出模拟的 OAuth 2.0 授权码流程步骤,包括授权码获取、Token 交换、资源访问、Token 刷新和再次访问。
这份指南涵盖了 Java Token 的多个关键方面,从编译器基础的词法 Token 到安全领域的认证授权 Token (JWT, OAuth),再到数据保护的令牌化。每个部分都包含通俗的解释、原理分析和可直接运行的实战代码示例。最后的系统案例展示了 Token 技术的综合应用。希望这份全面的指南能帮助你深入理解和应用 Java Token 技术。






