You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

六边形架构下JWT[access/refresh]签发位置合理性及实现方案咨询

基于六边形架构的Spring Boot登录JWT实现方案

1. 应用层签发JWT是否合理?

在六边形架构中,应用层负责封装完整的业务用例逻辑,登录成功后签发JWT属于登录用例的核心环节——生成用户身份凭证以完成登录闭环,因此这个设计是合理的。但需严格遵循六边形架构的依赖倒置原则:应用层仅定义JWT操作的抽象端口(Port),不直接依赖具体的JWT实现库,具体实现由基础设施层的适配器(Adapter)完成,确保应用层不被技术细节绑定。

2. 实现方案及代码示例

步骤1:定义JWT输出端口(应用层)

先在应用层抽象出JWT操作的端口,隔离业务逻辑与技术实现:

// 应用层抽象端口
interface JwtPort {
    fun issueAccessToken(user: User): String
    fun issueRefreshToken(user: User): String
    // 可选:添加刷新令牌持久化方法
    fun saveRefreshToken(userId: String, refreshToken: String)
}

// 领域模型(根据实际业务调整)
data class User(
    val userId: String,
    val username: String,
    val name: String
)

步骤2:更新登录服务及返回结果

扩展登录服务的依赖与返回值,注入JwtPort完成令牌签发:

// 更新返回结果类,新增令牌字段
data class ViewUserQuery(
    data class Query(val username: String, val password: String),
    data class Result(
        val userId: String,
        val username: String,
        val name: String,
        val accessToken: String,
        val refreshToken: String
    )
)

@Service
class SignInUserService(
    private val port: SignInUserPort,
    private val jwtPort: JwtPort // 注入JWT抽象端口
) : ViewUserQuery {
    override fun signIn(query: ViewUserQuery.Query): ViewUserQuery.Result {
        val findUser = port.load(query.username, query.password) ?: throw CustomException(
            errorCode = ErrorCode.FailedSignIn,
            detailMsg = "Please check your username and password."
        )

        // 签发令牌并存储刷新令牌
        val accessToken = jwtPort.issueAccessToken(findUser)
        val refreshToken = jwtPort.issueRefreshToken(findUser)
        jwtPort.saveRefreshToken(findUser.userId!!, refreshToken)

        return ViewUserQuery.Result(
            userId = findUser.userId!!,
            username = findUser.username!!,
            name = findUser.name!!,
            accessToken = accessToken,
            refreshToken = refreshToken
        )
    }
}

步骤3:实现JWT适配器(基础设施层)

在基础设施层实现JwtPort,使用具体JWT库(如JJWT)完成令牌生成:

import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys
import org.springframework.stereotype.Component
import java.util.*

@Component
class JwtAdapter : JwtPort {
    // 建议从配置文件读取密钥与过期时间
    private val secretKey = Keys.hmacShaKeyFor("your-strong-secret-key-at-least-32-chars".toByteArray())
    private val accessTokenExpiry = 3600000 // 1小时(毫秒)
    private val refreshTokenExpiry = 86400000 * 7 // 7天(毫秒)

    override fun issueAccessToken(user: User): String {
        return Jwts.builder()
            .subject(user.userId)
            .claim("username", user.username)
            .claim("name", user.name)
            .expiration(Date(System.currentTimeMillis() + accessTokenExpiry))
            .signWith(secretKey)
            .compact()
    }

    override fun issueRefreshToken(user: User): String {
        return Jwts.builder()
            .subject(user.userId)
            .expiration(Date(System.currentTimeMillis() + refreshTokenExpiry))
            .signWith(secretKey)
            .compact()
    }

    override fun saveRefreshToken(userId: String, refreshToken: String) {
        // 示例:用Redis存储刷新令牌,设置与令牌一致的过期时间
        // redisTemplate.opsForValue().set("refresh_token:$userId", refreshToken, refreshTokenExpiry, TimeUnit.MILLISECONDS)
    }
}

3. 若认为应用层签发不合理的最优方案

如果坚持将JWT签发与应用层解耦,最优方案是将令牌生成逻辑放在输入适配器层(如REST Controller),但需注意:

  • 应用层仅返回登录成功的用户核心信息,输入适配器负责调用JWT工具生成令牌并返回给客户端
  • 这种方式会拆分登录用例的完整性,不符合六边形架构中应用层封装完整业务逻辑的原则
  • 即使采用此方案,仍需通过抽象端口定义JWT操作,避免适配器层直接依赖具体库

综上,应用层通过抽象端口调用JWT服务是符合六边形架构设计的最优方案,既保证了业务用例的完整性,又遵循了依赖倒置原则,让核心业务逻辑与技术实现解耦。

内容的提问来源于stack exchange,提问作者정민영

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 17:15:42