六边形架构下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,提问作者정민영
相关产品推荐
相关产品推荐

