SpringBoot中基于 OAuth2 + Spring Security 的用户认证上下文工具库(UC Token Util)Starter 实践

📅 2026/7/24 2:00:00
SpringBoot中基于 OAuth2 + Spring Security 的用户认证上下文工具库(UC Token Util)Starter 实践
SpringBoot中基于 OAuth2 Spring Security 的用户认证上下文工具库UC Token UtilStarter 实践一、涉及的技术知识点1.1 OAuth2 JWT 认证体系知识点说明OAuth2 协议标准的授权框架支持 password/client_credentials/refresh_token 等授权模式JWTJSON Web Token自包含令牌payload 中携带用户信息无需服务端查库验证Access Token / Refresh Token短效访问令牌 长效刷新令牌平衡安全性与用户体验Token 扩展字段additional_infoJWT payload 中自定义的业务字段memberId、userId 等RSA 非对称签名认证中心用私钥签名各微服务用公钥验签无需共享密钥1.2 Spring Security OAuth2知识点说明SecurityContextHolder持有当前线程的安全上下文Authentication 对象OAuth2AuthenticationDetailsOAuth2 认证详情包含 token 值和解码后的信息TokenStoreToken 存储接口支持从 Token 中读取 AuthenticationAuthentication.getDetails()获取认证附加信息additional_info map1.3 Spring Boot 自动配置知识点说明spring.factories自动配置入口声明ApplicationContextAware获取 Spring 容器上下文读取 Environment 配置Environment.getProperty()从配置文件中动态读取属性静态方法 Spring Bean通过ApplicationContextAware将 Spring 上下文注入到静态字段支持静态工具方法1.4 Token 解码技术知识点说明com.auth0.jwt.JWT.decode()解析 JWT 不验签仅读取 payload用于刷新 token 场景DecodedJWT.getClaim()从解码后的 JWT 中按 key 获取自定义 claimOAuth2AuthenticationDetails.getDecodedDetails()Spring Security 框架已解码的 Token 详情1.5 设计模式模式应用工具类模式UcTokenUtil提供静态方法无需实例化即可获取当前用户信息模板方法DefaultGenAccessTokenBody.getRequestBody()定义生成 Token 的模板子类可覆写loadAdditional/loadUserRoles策略模式不同场景target-token、gen-token、refresh-token走不同的认证中心接口外观模式对外只暴露getMemberId()、getUserId()等简单方法屏蔽底层 Security 复杂性注博客https://blog.csdn.net/badao_liumang_qizhi二、封装 Starter 的流程2.1 项目结构example-uc-token-starter/ ├── pom.xml └── src/main/ ├── java/com/example/uc/ │ ├── UcTokenUtil.java // 核心工具类对外API │ ├── autoconfigure/ │ │ └── UcTokenUtilAutoConfiguration.java // 自动配置 │ ├── config/ │ │ └── UcHeaderInterceptor.java // HTTP请求头拦截器 │ ├── dto/ │ │ ├── UcAccessTokenResult.java // 认证中心响应 │ │ ├── GenTokenParamDto.java // 生成Token入参 │ │ └── TargetTokenParamDto.java // 跳转Token入参 │ ├── enums/ │ │ └── TokenExtensionEnums.java // Token扩展字段枚举 │ └── role/ │ └── DefaultGenAccessTokenBody.java // Token生成模板可扩展 └── resources/META-INF/ └── spring.factories2.2 核心设计思路读取当前用户信息— 从 Spring Security Context 中获取已验证的 Token解析 additional_info服务间 Token 获取— 提供 targetAccessToken / genAccessToken / refreshAccessToken 方法用于后端服务之间的认证配置外置— 认证中心地址、clientId、clientPassword 通过 application.yml 配置零侵入— 引入依赖后自动注册 Bean业务代码直接调用静态方法三、通用示例代码3.1 spring.factories# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.uc.autoconfigure.UcTokenUtilAutoConfiguration3.2 UcTokenUtilAutoConfiguration自动配置packagecom.example.uc.autoconfigure;importcom.example.uc.UcTokenUtil;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/** * UC Token工具自动配置类. */ConfigurationpublicclassUcTokenUtilAutoConfiguration{BeanpublicUcTokenUtilucTokenUtil(){returnnewUcTokenUtil();}}3.3 TokenExtensionEnumsToken 扩展字段枚举packagecom.example.uc.enums;/** * JWT Token中自定义扩展字段枚举. * 对应认证中心签发Token时写入的additional_info. */publicenumTokenExtensionEnums{USER_ID(user_id,用户ID),NAME(user_name_cn,用户名称),MEMBER_ID(member_id,会员ID),ACCOUNT_ID(uc_account_id,UC账户ID),AVATAR_ADDRESS(avatar_address,头像地址),POSITION_ID(position_id,岗位ID),PLATFORM_FROM(platform_from,来源平台),PLATFORM_TO(platform_to,目标平台);privatefinalStringkey;privatefinalStringdesc;TokenExtensionEnums(Stringkey,Stringdesc){this.keykey;this.descdesc;}publicStringgetKey(){returnkey;}publicStringgetDesc(){returndesc;}}3.4 UcTokenUtil核心工具类packagecom.example.uc;importcom.example.uc.enums.TokenExtensionEnums;importjava.util.Collections;importjava.util.Map;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationContextAware;importorg.springframework.core.env.Environment;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.context.SecurityContextHolder;importorg.springframework.security.oauth2.provider.OAuth2Authentication;importorg.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;importorg.springframework.security.oauth2.provider.token.TokenStore;importorg.springframework.util.Assert;importorg.springframework.util.StringUtils;/** * 用户认证上下文工具类. * 从当前请求的JWT Token中提取用户信息. * 需配合Spring Security OAuth2资源服务器使用. */publicclassUcTokenUtilimplementsApplicationContextAware{privatestaticfinalLoggerlogLoggerFactory.getLogger(UcTokenUtil.class);privatestaticStringauthCenterUrl;privatestaticStringclientId;privatestaticStringclientPassword;privatestaticApplicationContextcontext;OverridepublicvoidsetApplicationContext(ApplicationContextapplicationContext){contextapplicationContext;Environmentenvcontext.getEnvironment();authCenterUrlenv.getProperty(app.uc.auth-center.url,);clientIdenv.getProperty(app.uc.client-id,);clientPasswordenv.getProperty(app.uc.client-password,);}// 对外静态 API /** * 获取当前登录用户的会员ID. */publicstaticIntegergetMemberId(){returngetIntegerField(TokenExtensionEnums.MEMBER_ID);}/** * 获取当前登录用户的用户ID. */publicstaticIntegergetUserId(){returngetIntegerField(TokenExtensionEnums.USER_ID);}/** * 获取当前登录用户的UC账户ID. */publicstaticIntegergetUcAccountId(){returngetIntegerField(TokenExtensionEnums.ACCOUNT_ID);}/** * 获取当前登录用户的名称. */publicstaticStringgetName(){returngetStringField(TokenExtensionEnums.NAME);}/** * 获取当前登录用户的来源平台. */publicstaticStringgetPlatformFrom(){returngetStringField(TokenExtensionEnums.PLATFORM_FROM);}/** * 获取Token中任意扩展字段. */publicstaticObjectgetAdditionalInfo(Stringkey){returngetAdditionalInfoMap().get(key);}// 内部实现 privatestaticIntegergetIntegerField(TokenExtensionEnumsfield){ObjectvaluegetAdditionalInfoMap().get(field.getKey());if(value!null){returnInteger.valueOf(value.toString());}returnnull;}privatestaticStringgetStringField(TokenExtensionEnumsfield){ObjectvaluegetAdditionalInfoMap().get(field.getKey());returnvalue!null?value.toString():null;}/** * 从Spring Security上下文获取Token的additional_info. * * 解析流程: * 1. SecurityContextHolder获取当前Authentication * 2. 判断details类型: * - OAuth2AuthenticationDetails: 标准OAuth2流程从decodedDetails获取 * - HashMap: 内部服务调用传递的上下文信息 * 3. 如果decodedDetails为空通过TokenStore重新解析Token */privatestaticMapString,ObjectgetAdditionalInfoMap(){Assert.notNull(context,请配置为Spring Bean);AuthenticationauthenticationSecurityContextHolder.getContext().getAuthentication();if(StringUtils.isEmpty(authentication)){log.debug(当前认证信息为空);returnCollections.emptyMap();}Objectdetailsauthentication.getDetails();MapString,ObjectadditionalInfoCollections.emptyMap();if(detailsinstanceofOAuth2AuthenticationDetails){// 标准OAuth2请求从已解码的Token详情中获取OAuth2AuthenticationDetailsoAuth2Details(OAuth2AuthenticationDetails)details;if(oAuth2Details.getDecodedDetails()!null){return(MapString,Object)oAuth2Details.getDecodedDetails();}// 降级方案通过TokenStore重新读取TokenStoretokenStorecontext.getBean(TokenStore.class);OAuth2AuthenticationoAuth2AuthenticationtokenStore.readAuthentication(oAuth2Details.getTokenValue());additionalInfo(MapString,Object)oAuth2Authentication.getDetails();}elseif(detailsinstanceofjava.util.HashMap){// 内部Feign调用传递的上下文MapadditionalInfo(MapString,Object)details;}log.trace(Token扩展信息: {},additionalInfo);returnadditionalInfo;}}3.5 DefaultGenAccessTokenBodyToken 生成模板packagecom.example.uc.role;importcom.example.uc.dto.GenTokenParamDto;importjava.util.Collections;importjava.util.Map;importjava.util.Set;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/** * Token生成请求体模板. * 子类可覆写loadAdditional/loadUserRoles实现自定义Token内容. */publicclassDefaultGenAccessTokenBody{privatestaticfinalLoggerlogLoggerFactory.getLogger(DefaultGenAccessTokenBody.class);/** * 组装生成Token的请求参数. * 模板方法固定流程子类可扩展additional和roles. */publicfinalGenTokenParamDtogetRequestBody(StringclientId,StringclientPassword,IntegerucAccountId,StringplatformFrom){GenTokenParamDtoparamDtonewGenTokenParamDto();paramDto.setClientId(clientId);paramDto.setClientPassword(clientPassword);paramDto.setUcAccountId(ucAccountId);paramDto.setAdditional(loadAdditional(ucAccountId));paramDto.setRoles(loadUserRoles(ucAccountId));returnparamDto;}/** * 加载Token附加信息子类可覆写. * 例如从数据库查询用户所属会员ID、头像等信息写入Token. */protectedMapString,StringloadAdditional(IntegerucAccountId){returnCollections.emptyMap();}/** * 加载用户角色集合子类可覆写. * 例如从数据库查询用户角色列表写入Token. */protectedSetStringloadUserRoles(IntegerucAccountId){returnCollections.emptySet();}}3.6 pom.xmlStarter 侧projectgroupIdcom.example/groupIdartifactIdexample-uc-token-starter/artifactIdversion1.0.0/versionpackagingjar/packagingdependenciesdependencygroupIdorg.springframework.security.oauth/groupIdartifactIdspring-security-oauth2/artifactIdscopeprovided/scope/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactIdscopeprovided/scope/dependencydependencygroupIdcom.auth0/groupIdartifactIdjava-jwt/artifactIdversion3.18.2/version/dependencydependencygroupIdorg.slf4j/groupIdartifactIdslf4j-api/artifactIdscopeprovided/scope/dependency/dependencies/project四、引入方使用4.1 添加依赖dependencygroupIdcom.example/groupIdartifactIdexample-uc-token-starter/artifactIdversion1.0.0/version/dependency4.2 配置文件app:uc:auth-center:url:https://auth.example.comclient-id:my-service-clientclient-password:secret1234.3 业务代码中使用RestControllerpublicclassOrderController{GetMapping(/api/orders)publicResultlistOrders(){// 直接通过静态方法获取当前登录用户信息IntegermemberIdUcTokenUtil.getMemberId();IntegeruserIdUcTokenUtil.getUserId();StringuserNameUcTokenUtil.getName();// 业务逻辑...returnorderService.listByMemberId(memberId);}}4.4 服务间 Token 获取后端调后端ServicepublicclassInternalApiService{ResourceprivateUcTokenUtilucTokenUtil;/** * 获取目标平台的访问Token用于跨平台调用. */publicStringgetTargetPlatformToken(StringuserId,StringplatformId){UcAccessTokenResult.UcAccessTokentokenucTokenUtil.targetAccessToken(userId,platformId);returntoken!null?token.getAccessToken():null;}/** * 刷新即将过期的Token. */publicStringrefreshToken(DefaultGenAccessTokenBodybody,StringoldToken){UcAccessTokenResult.UcAccessTokentokenucTokenUtil.refreshAccessToken(body,oldToken);returntoken!null?token.getAccessToken():null;}}4.5 自定义 Token 内容扩展模板方法ComponentpublicclassCustomGenAccessTokenBodyextendsDefaultGenAccessTokenBody{ResourceprivateUserRepositoryuserRepository;OverrideprotectedMapString,StringloadAdditional(IntegerucAccountId){// 从数据库查用户额外信息写入TokenUserEntityuseruserRepository.findByUcAccountId(ucAccountId);MapString,StringadditionalnewHashMap();additional.put(member_id,user.getMemberId().toString());additional.put(avatar_address,user.getAvatar());returnadditional;}OverrideprotectedSetStringloadUserRoles(IntegerucAccountId){returnuserRepository.findRolesByAccountId(ucAccountId);}}五、关键设计总结设计要点实现方式收益零侵入获取用户信息静态方法 SecurityContextHolder业务代码一行调用无需注入任何 Bean多场景 Token 操作targetToken/genToken/refreshToken支持跨平台跳转、后端生成、无感续期Token 内容可扩展DefaultGenAccessTokenBody 模板方法子类覆写 loadAdditional 自定义 claim配置外置Environment.getProperty不同环境不同认证中心地址兼容多种认证来源OAuth2AuthenticationDetails / HashMap 双分支既支持标准 OAuth2 请求也支持 Feign 内部传递降级容错decodedDetails 为空时通过 TokenStore 重读覆盖 Spring Security 版本差异导致的兼容问题依赖最小化核心依赖 provided scope不强制引入额外传递性依赖