springCloud+oauth2 自定义手动生成token

发布时间:2026/7/23 11:05:47

springCloud+oauth2 自定义手动生成token 1 提供给第三方厂商开放的API调用package com.shpl.scp.fin.controller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.shpl.scp.admin.api.dto.UserTokenDTO; import com.shpl.scp.admin.api.feign.RemoteTokenService; import com.shpl.scp.common.core.util.R; import com.shpl.scp.common.security.annotation.Inner; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.Map; /** * 云检系统Token接口 * * author Dionys * date 2025-12-09 */ Slf4j RestController RequiredArgsConstructor RequestMapping(/external/cloudpense) Tag(description cloudcheckToken, name 云检系统Token接口) SecurityRequirement(name HttpHeaders.AUTHORIZATION) public class FinApiCloudPenseTokenController { private final RemoteTokenService remoteTokenService; Inner(false) RequestMapping(/token) public RUserTokenDTO.Response token() { // 创建请求对象 UserTokenDTO.Request request new UserTokenDTO.Request(); request.setUsername(cmsadmin); // 只需要提供用户名 request.setAuthorities(List.of(ROLE_2000518542038544386,cms_cmsActivityApprovalHeader_view,cms_cmsCostCheckHeader_view)); // 设置用户角色ROLE_ID 固定写法权限字符串 // 调用Token生成服务 return remoteTokenService.generateToken(request); } Inner(false) RequestMapping(/tokenp) public RPage tokenp() { // 创建请求对象 MapString, Object params Map.of(current, 1, size, 10); // 调用Token生成服务 return remoteTokenService.getTokenPage(params); } }2 feign远程调用auth认证创建tokenFeignClient( contextId remoteTokenService, value scp-auth ) public interface RemoteTokenService { NoToken PostMapping({/token/page}) RPage getTokenPage(RequestBody MapString, Object params); NoToken DeleteMapping({/token/remove/{token}}) RBoolean removeTokenById(PathVariable(token) String token); NoToken GetMapping({/token/query-token}) RMapString, Object queryToken(RequestParam(token) String token, RequestParam(TENANT-ID) String tenantId); NoToken PostMapping({/token/generate-token}) RUserTokenDTO.Response generateToken(RequestBody UserTokenDTO.Request request); }3 生成token PostMapping(/token/generate-token)/* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.shpl.scp.auth.endpoint; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.TemporalAccessorUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.shpl.scp.admin.api.dto.UserTokenDTO; import com.shpl.scp.admin.api.entity.SysOauthClientDetails; import com.shpl.scp.admin.api.entity.SysTenant; import com.shpl.scp.admin.api.feign.RemoteClientDetailsService; import com.shpl.scp.admin.api.feign.RemoteTenantService; import com.shpl.scp.admin.api.vo.TokenVO; import com.shpl.scp.common.core.constant.CacheConstants; import com.shpl.scp.common.core.constant.CommonConstants; import com.shpl.scp.common.core.constant.SecurityConstants; import com.shpl.scp.common.core.constant.enums.UserTypeEnum; import com.shpl.scp.common.core.util.R; import com.shpl.scp.common.core.util.RetOps; import com.shpl.scp.common.core.util.SpringContextHolder; import com.shpl.scp.common.data.cache.RedisUtils; import com.shpl.scp.common.data.tenant.TenantContextHolder; import com.shpl.scp.common.security.annotation.Inner; import com.shpl.scp.common.security.service.ScpUser; import com.shpl.scp.common.security.util.OAuth2ErrorCodesExpand; import com.shpl.scp.common.security.util.OAuthClientException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.CacheManager; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.event.LogoutSuccessEvent; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.*; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import java.security.Principal; import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; /** * author scp * date 2019/2/1 删除token端点 */ Slf4j RestController RequestMapping RequiredArgsConstructor public class ScpTokenEndpoint { private final OAuth2AuthorizationService authorizationService; private final OAuth2TokenGenerator? extends OAuth2Token tokenGenerator; private final RemoteClientDetailsService clientDetailsService; private final RemoteTenantService tenantService; private final CacheManager cacheManager; /** * 认证页面 * * param modelAndView * param error 表单登录失败处理回调的错误信息 * return ModelAndView */ GetMapping(/token/login) public ModelAndView require(ModelAndView modelAndView, RequestParam(required false) String error) { modelAndView.setViewName(ftl/login); modelAndView.addObject(error, error); RListSysTenant tenantList tenantService.list(); modelAndView.addObject(tenantList, tenantList.getData()); return modelAndView; } /** * 授权码模式确认页面 * * return {link ModelAndView } */ GetMapping(/oauth2/confirm_access) public ModelAndView confirm(Principal principal, ModelAndView modelAndView, RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId, RequestParam(OAuth2ParameterNames.SCOPE) String scope, RequestParam(OAuth2ParameterNames.STATE) String state) { SysOauthClientDetails clientDetails RetOps.of(clientDetailsService.getClientDetailsById(clientId)) .getData() .orElseThrow(() - new OAuthClientException(clientId 不合法)); SetString authorizedScopes StringUtils.commaDelimitedListToSet(clientDetails.getScope()); modelAndView.addObject(clientId, clientId); modelAndView.addObject(state, state); modelAndView.addObject(scopeList, authorizedScopes); modelAndView.addObject(principalName, principal.getName()); modelAndView.setViewName(ftl/confirm); return modelAndView; } /** * 注销并删除令牌 * * param authHeader auth 标头 * return {link R }{link Boolean } */ DeleteMapping(/token/logout) public RString logout(RequestHeader(value HttpHeaders.AUTHORIZATION, required false) String authHeader) { if (StrUtil.isBlank(authHeader)) { return R.ok(); } String tokenValue authHeader.replace(OAuth2AccessToken.TokenType.BEARER.getValue(), StrUtil.EMPTY).trim(); return removeToken(tokenValue); } /** * 校验token * * param token 令牌 * return */ SneakyThrows GetMapping(/token/check_token) public ROAuth2AccessToken checkToken(String token, HttpServletResponse response, HttpServletRequest request) { ServletServerHttpResponse httpResponse new ServletServerHttpResponse(response); if (StrUtil.isBlank(token)) { httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED); return R.failed(OAuth2ErrorCodesExpand.TOKEN_MISSING); } OAuth2Authorization authorization authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN); // 如果令牌不存在 返回401 if (authorization null || authorization.getAccessToken() null) { httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED); return R.failed(OAuth2ErrorCodesExpand.INVALID_BEARER_TOKEN); } // 获取令牌 return R.ok(Objects.requireNonNull(authorization).getAccessToken().getToken()); } /** * 移除指定令牌 * * param token 需要移除的令牌 * return 操作结果成功返回true */ Inner DeleteMapping(/token/remove/{token}) public RString removeToken(PathVariable(token) String token) { OAuth2Authorization authorization authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN); if (authorization null) { return R.ok(); } OAuth2Authorization.TokenOAuth2AccessToken accessToken authorization.getAccessToken(); if (accessToken null || StrUtil.isBlank(accessToken.getToken().getTokenValue())) { return R.ok(); } // 清空用户信息 cacheManager.getCache(CacheConstants.USER_DETAILS).evict(authorization.getPrincipalName()); // 清空access token authorizationService.remove(authorization); // 处理自定义退出事件保存相关日志 SpringContextHolder.publishEvent(new LogoutSuccessEvent(new PreAuthenticatedAuthenticationToken( authorization.getPrincipalName(), authorization.getRegisteredClientId()))); // 获取授权类型用于区分是SSO登录还是普通密码登录 String grantType authorization.getAuthorizationGrantType().getValue(); return R.ok(grantType, 退出成功); } /** * 分页查询token列表 * * param params 查询参数包含分页参数(current,size)和可选用户名过滤条件(username) * return 分页结果包含token列表和总数 */ Inner PostMapping(/token/page) public RPageTokenVO tokenList(RequestBody MapString, Object params) { // 根据username参数获取对应数据 String username MapUtil.getStr(params, SecurityConstants.DETAILS_USERNAME); String usernameKey String.format(token::username::%s*::*::%s::*, username, TenantContextHolder.getTenantId()); String key String.format(token::username::*::*::%s::*, TenantContextHolder.getTenantId()); int current MapUtil.getInt(params, CommonConstants.CURRENT); int size MapUtil.getInt(params, CommonConstants.SIZE); // 根据是否有username参数选择不同的查询key String searchKey StrUtil.isNotBlank(username) ? usernameKey : key; SetString keys RedisUtils.keys(searchKey); // 分页处理 ListString pages keys.stream().skip((current - 1) * size).limit(size).toList(); PageTokenVO result new Page(current, size); ListTokenVO tokenVoList pages.stream().map(keyName - { // 从key名称解析信息: token::username::{username}::{tenantId}::{tokenId} String[] keyParts keyName.split(::); if (keyParts.length 6) { return null; } TokenVO tokenVo new TokenVO(); // 从key解析username String keyUsername keyParts[2]; tokenVo.setUsername(keyUsername); tokenVo.setClientId(keyParts[3]); tokenVo.setId(keyParts[5]); tokenVo.setAccessToken(keyParts[5]); // 获取TTL作为过期时间 Long ttl RedisUtils.getExpire(keyName); // TTL是秒数转换为过期时间 long expiresAtMillis System.currentTimeMillis() (ttl * 1000); String expiresAt TemporalAccessorUtil.format(java.time.Instant.ofEpochMilli(expiresAtMillis), DatePattern.NORM_DATETIME_PATTERN); tokenVo.setExpiresAt(expiresAt); return tokenVo; }).filter(Objects::nonNull).toList(); result.setRecords(tokenVoList); result.setTotal(keys.size()); return R.ok(result); } Inner GetMapping(/token/query-token) public R queryToken(String token) { OAuth2Authorization authorization authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN); return R.ok(authorization); } /** * 获取token - 自定义token * param request 请求参数 * return {link UserTokenDTO.Response} */ Inner // 这个注解表示只有内部系统可以调用 SneakyThrows PostMapping(/token/generate-token) public RUserTokenDTO.Response generateToken(RequestBody UserTokenDTO.Request request) { // 第1步创建一个客户端配置告诉系统这个Token的基本规则 RegisteredClient registeredClient RegisteredClient.withId(SecurityConstants.FROM) .clientId(SecurityConstants.FROM) .authorizationGrantType(AuthorizationGrantType.PASSWORD) .tokenSettings(TokenSettings.builder() .accessTokenTimeToLive(Duration.ofHours(24)) // Token有效期24小时 .refreshTokenTimeToLive(Duration.ofDays(7)) // 刷新Token有效期7天 .accessTokenFormat(OAuth2TokenFormat.REFERENCE) .build()) .build(); // 第2步构建用户信息对象 // 这里我们根据传入的用户名创建一个完整的用户对象 ScpUser pigUser new ScpUser( 110L, // 用户ID request.getUsername(), // 用户名 null, // 密码这里不需要 , , , , , // 其他用户信息暂时为空 1L, // 部门ID , // 其他信息 true, true, // 账户状态 UserTypeEnum.TOB.getStatus(), true, false, // 将权限字符串转换为系统认识的权限对象 request.getAuthorities().stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()) ); // 第3步创建认证对象 Authentication usernamePasswordAuthentication new UsernamePasswordAuthenticationToken(pigUser, StrUtil.EMPTY); // 第4步设置Token生成的上下文环境 DefaultOAuth2TokenContext.Builder tokenContextBuilder DefaultOAuth2TokenContext.builder() .registeredClient(registeredClient) .principal(usernamePasswordAuthentication) .authorizationServerContext(new AuthorizationServerContext() { Override public String getIssuer() { return http://ai.com; // Token发行者 } Override public AuthorizationServerSettings getAuthorizationServerSettings() { return AuthorizationServerSettings.builder().build(); } }); // 第5步开始构建授权信息 OAuth2Authorization.Builder authorizationBuilder OAuth2Authorization .withRegisteredClient(registeredClient) .principalName(usernamePasswordAuthentication.getName()); // 第6步生成访问Token主要的通行证 OAuth2TokenContext tokenContext tokenContextBuilder .tokenType(OAuth2TokenType.ACCESS_TOKEN) .authorizationGrantType(AuthorizationGrantType.PASSWORD) .authorizationGrant(new OAuth2ClientAuthenticationToken( registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null)) .build(); OAuth2Token generatedAccessToken this.tokenGenerator.generate(tokenContext); OAuth2AccessToken accessToken new OAuth2AccessToken( OAuth2AccessToken.TokenType.BEARER, generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(), generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes()); // 第7步保存访问Token信息 if (generatedAccessToken instanceof ClaimAccessor) { authorizationBuilder.id(accessToken.getTokenValue()) .token(accessToken, (metadata) - metadata.put( OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims())) .attribute(Principal.class.getName(), usernamePasswordAuthentication); } else { authorizationBuilder.id(accessToken.getTokenValue()).accessToken(accessToken); } // 第8步生成刷新Token用来获取新的访问Token OAuth2RefreshToken refreshToken; tokenContext tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build(); OAuth2Token generatedRefreshToken this.tokenGenerator.generate(tokenContext); refreshToken (OAuth2RefreshToken) generatedRefreshToken; authorizationBuilder.refreshToken(refreshToken); // 第9步保存完整的授权信息到数据库 OAuth2Authorization authorization authorizationBuilder .authorizationGrantType(AuthorizationGrantType.PASSWORD) .build(); this.authorizationService.save(authorization); // 第10步返回生成的Token return R.ok(new UserTokenDTO.Response( accessToken.getTokenValue(), refreshToken.getTokenValue())); } }

相关新闻