什么是 OAuth 2.0 和 JWT,为什么在 Kotlin 中需要它们?
\n\n在现代 Web 开发中,数据安全和访问控制是关键任务之一。如果您用 Kotlin 编写服务器端应用程序,迟早会面临保护 API 或通过第三方服务(Google、GitHub、VK)实现登录的需求。为此,存在两个标准——OAuth 2.0(授权协议)和 JWT(JSON Web Token——令牌格式)。
\n\n在本文中,我们将分析 OAuth 2.0 和 JWT 的工作原理,并使用流行的库在 Kotlin 中实现一个简单的授权。本材料面向初学者,因此所有复杂术语都将用通俗易懂的语言解释。
\n\n基本概念:OAuth 2.0 和 JWT
\n\n什么是 OAuth 2.0?
\nOAuth 2.0 是一种协议,它允许一个服务(例如您的应用程序)在不传递登录名和密码的情况下,获得对用户在另一个服务(例如其在 Google 中的个人资料)上资源的有限访问权限。想象一下,您想通过 Google 帐户登录网站——这就是 OAuth 2.0。
\n\nOAuth 2.0 的主要参与者:
\n- \n
- Resource Owner — 用户,数据所有者。 \n
- Client — 您的应用程序,希望获得访问权限。 \n
- Authorization Server — 颁发授权的服务器(例如 Google)。 \n
- Resource Server — 存储用户数据的服务器(例如 Google API)。 \n
什么是 JWT?
\nJWT 是一种紧凑且自包含的令牌格式,包含有关用户的信息(例如 ID、角色、有效期)。令牌使用数字签名进行签名,因此无法伪造。JWT 由三部分组成,以点分隔:
\n\nheader.payload.signature\n\n解码后的 JWT 示例:
\n{\n "sub": "1234567890",\n "name": "伊万·彼得罗夫",\n "iat": 1516239022,\n "exp": 1516242622\n}\n\nJWT 的主要优点是服务器无需在数据库中存储会话。所有信息都已包含在令牌内部。
\n\n使用 Spring Boot 在 Kotlin 中实现 JWT 授权
\n\n让我们看一个实际示例。我们将使用 Spring Boot 和 Spring Security 在 Kotlin 中创建一个 REST API。我们的应用程序将在成功身份验证后颁发 JWT 令牌。
\n\n步骤 1. 添加依赖项
\n将以下依赖项添加到 build.gradle.kts:
dependencies {\n implementation("org.springframework.boot:spring-boot-starter-security")\n implementation("org.springframework.boot:spring-boot-starter-web")\n implementation("io.jsonwebtoken:jjwt-api:0.12.5")\n runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.5")\n runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.5")\n}\n\n步骤 2. 创建 JWT 服务
\n创建一个 JwtService 类,用于生成和验证令牌:
import io.jsonwebtoken.Claims\nimport io.jsonwebtoken.Jwts\nimport io.jsonwebtoken.security.Keys\nimport org.springframework.stereotype.Service\nimport java.util.*\n\n@Service\nclass JwtService {\n private val secretKey = "mySecretKeyForJwtToken12345678901234567890" // 至少 32 个字符\n private val expirationMs = 3600000L // 1 小时\n\n fun generateToken(username: String): String {\n val claims: Map = mapOf(\n "sub" to username,\n "iat" to Date(),\n "exp" to Date(System.currentTimeMillis() + expirationMs)\n )\n return Jwts.builder()\n .claims(claims)\n .signWith(Keys.hmacShaKeyFor(secretKey.toByteArray()))\n .compact()\n }\n\n fun extractUsername(token: String): String? {\n return try {\n val claims = parseToken(token)\n claims.subject\n } catch (e: Exception) {\n nul