springcloud+oauth2+sso单点登录+自定义获取用户token+第三方系统访问获取token
·
- springcloud+oauth2+sso单点登录实现
- 自定义获取某个用户token值和权限
- 第三方应用系统访问本应用系统接口获取指定用户名的token和权限
- 指定用户名和权限定义
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 R<UserTokenDTO.Response> token() { // 创建请求对象 UserTokenDTO.Request request = new UserTokenDTO.Request(); request.setUsername("FinCloudAPI"); // 只需要提供用户名 request.setAuthorities(List.of("ROLE_1", "fin_cloudpense")); // 设置用户角色(ROLE_ID 固定写法)权限字符串 // 调用Token生成服务 return remoteTokenService.generateToken(request); } @Inner(false) @RequestMapping("/tokenp") public R<Page> tokenp() { // 创建请求对象 Map<String, Object> params = Map.of("current", 1, "size", 10); // 调用Token生成服务 return remoteTokenService.getTokenPage(params); } } - 自定义一个用户名的token实现
/** * 获取token - 自定义token * @param request 请求参数 * @return {@link UserTokenDTO.Response} */ @Inner // 这个注解表示只有内部系统可以调用 @SneakyThrows @PostMapping("/token/generate-token") public R<UserTokenDTO.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())); }package com.shpl.scp.admin.api.dto; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.ArrayList; import java.util.List; @Data public class UserTokenDTO { // 请求信封:装载我们要发送的数据 @Data public static class Request { private String username; // 用户名(必填) private List<String> authorities = new ArrayList<>(); // 用户权限列表(可选) } // 响应信封:装载系统返回给我们的数据 @Data @NoArgsConstructor @AllArgsConstructor public static class Response { private String accessToken; // 访问令牌(主要的通行证) private String refreshToken; // 刷新令牌(用来获取新的访问令牌) } }/* * Copyright (c) 2018-2025, scp All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: scp */ package com.shpl.scp.common.security.service; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import org.springframework.security.core.userdetails.User; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * @author scp * @date 2020/4/16 扩展用户信息 */ public class ScpUser extends User implements OAuth2AuthenticatedPrincipal { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; /** * 扩展属性,方便存放oauth 上下文相关信息 */ private final Map<String, Object> attributes = new HashMap<>(); /** * 用户ID */ @Getter private Long id; /** * 部门ID */ @Getter private Long deptId; /** * 手机号 */ @Getter private String phone; /** * 头像 */ @Getter private String avatar; /** * 租户ID */ @Getter private Long tenantId; /** * 拓展字段:昵称 */ @Getter private String nickname; /** * 拓展字段:姓名 */ @Getter private String name; /** * 拓展字段:邮箱 */ @Getter private String email; @Getter private String userType; /** * Construct the <code>User</code> with the details required by * {@link DaoAuthenticationProvider}. * @param id 用户ID * @param deptId 部门ID * @param tenantId 租户ID * @param nickname 昵称 * @param name 姓名 * @param email 邮箱 the username presented to the * <code>DaoAuthenticationProvider</code> * @param password the password that should be presented to the * <code>DaoAuthenticationProvider</code> * @param enabled set to <code>true</code> if the user is enabled * @param accountNonExpired set to <code>true</code> if the account has not expired * @param credentialsNonExpired set to <code>true</code> if the credentials have not * expired * @param accountNonLocked set to <code>true</code> if the account is not locked * @param authorities the authorities that should be granted to the caller if they * presented the correct username and password and the user is enabled. Not null. * @throws IllegalArgumentException if a <code>null</code> value was passed either as * a parameter or as an element in the <code>GrantedAuthority</code> collection */ @JsonCreator public ScpUser(@JsonProperty("id") Long id, @JsonProperty("username") String username, @JsonProperty("deptId") Long deptId, @JsonProperty("phone") String phone, @JsonProperty("avatar") String avatar, @JsonProperty("nickname") String nickname, @JsonProperty("name") String name, @JsonProperty("email") String email, @JsonProperty("tenantId") Long tenantId, @JsonProperty("password") String password, @JsonProperty("enabled") boolean enabled, @JsonProperty("accountNonExpired") boolean accountNonExpired, @JsonProperty("userType") String userType, @JsonProperty("credentialsNonExpired") boolean credentialsNonExpired, @JsonProperty("accountNonLocked") boolean accountNonLocked, @JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) { super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); this.id = id; this.deptId = deptId; this.phone = phone; this.avatar = avatar; this.tenantId = tenantId; this.nickname = nickname; this.name = name; this.email = email; this.userType = userType; } @Override public Map<String, Object> getAttributes() { return this.attributes; } }package com.shpl.scp.common.security.service; import cn.hutool.core.collection.CollUtil; import com.shpl.scp.common.core.constant.SecurityConstants; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.lang.Nullable; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.util.Assert; import java.security.Principal; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; /** * @author scp * @date 2022/5/27 */ @RequiredArgsConstructor public class ScpRedisOAuth2AuthorizationService implements OAuth2AuthorizationService { private final static Long TIMEOUT = 10L; private static final String AUTHORIZATION = "token"; private final RedisTemplate<String, Object> redisTemplate; private static boolean isState(OAuth2Authorization authorization) { return Objects.nonNull(authorization.getAttribute("state")); } private static boolean isCode(OAuth2Authorization authorization) { OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization .getToken(OAuth2AuthorizationCode.class); return Objects.nonNull(authorizationCode); } private static boolean isRefreshToken(OAuth2Authorization authorization) { return Objects.nonNull(authorization.getRefreshToken()); } private static boolean isAccessToken(OAuth2Authorization authorization) { return Objects.nonNull(authorization.getAccessToken()); } @Override public void save(OAuth2Authorization authorization) { Assert.notNull(authorization, "authorization cannot be null"); if (isState(authorization)) { String token = authorization.getAttribute("state"); redisTemplate.setValueSerializer(RedisSerializer.java()); redisTemplate.opsForValue() .set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT, TimeUnit.MINUTES); } if (isCode(authorization)) { OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization .getToken(OAuth2AuthorizationCode.class); OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken(); long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(), authorizationCodeToken.getExpiresAt()); redisTemplate.setValueSerializer(RedisSerializer.java()); redisTemplate.opsForValue() .set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), authorization, between, TimeUnit.MINUTES); } if (isRefreshToken(authorization)) { OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt()); redisTemplate.setValueSerializer(RedisSerializer.java()); redisTemplate.opsForValue() .set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()), authorization, between, TimeUnit.SECONDS); } if (isAccessToken(authorization)) { OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()); redisTemplate.setValueSerializer(RedisSerializer.java()); redisTemplate.opsForValue() .set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), authorization, between, TimeUnit.SECONDS); UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = authorization .getAttribute(Principal.class.getName()); if (Objects.nonNull(usernamePasswordAuthenticationToken) && usernamePasswordAuthenticationToken.getPrincipal() instanceof ScpUser scpUser) { // 扩展记录 access-token 、username 的关系 token::username::admin::tenantId::xxx String tokenUsername = String.format("%s::%s::%s::%s::%s::%s", AUTHORIZATION, SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), authorization.getRegisteredClientId(), scpUser.getTenantId(), accessToken.getTokenValue()); redisTemplate.opsForValue().set(tokenUsername, accessToken.getTokenValue(), between, TimeUnit.SECONDS); } } } @Override public void remove(OAuth2Authorization authorization) { Assert.notNull(authorization, "authorization cannot be null"); List<String> keys = new ArrayList<>(); if (isState(authorization)) { String token = authorization.getAttribute("state"); keys.add(buildKey(OAuth2ParameterNames.STATE, token)); } if (isCode(authorization)) { OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization .getToken(OAuth2AuthorizationCode.class); OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken(); keys.add(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue())); } if (isRefreshToken(authorization)) { OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); keys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue())); } if (isAccessToken(authorization)) { OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue())); // 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx String key = String.format("%s::%s::%s::%s::*::%s", AUTHORIZATION, SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), authorization.getRegisteredClientId(), accessToken.getTokenValue()); Set<String> pattenKey = redisTemplate.keys(key); if (CollUtil.isNotEmpty(pattenKey)) { keys.addAll(pattenKey); } } redisTemplate.delete(keys); } @Override @Nullable public OAuth2Authorization findById(String id) { throw new UnsupportedOperationException(); } @Override @Nullable public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) { Assert.hasText(token, "token cannot be empty"); redisTemplate.setValueSerializer(RedisSerializer.java()); return (OAuth2Authorization) redisTemplate.opsForValue() .get(buildKey(Objects.requireNonNullElse(tokenType, OAuth2TokenType.ACCESS_TOKEN).getValue(), token)); } private String buildKey(String type, String id) { return String.format("%s::%s::%s", AUTHORIZATION, type, id); } /** * 根据用户名移除相关授权信息 * @param authentication 认证信息,包含用户名 */ public void removeByUsername(Authentication authentication) { // 根据 username查询对应access-token String authenticationName = authentication.getName(); // 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx String tokenUsernameKey = String.format("%s::%s::%s::*", AUTHORIZATION, SecurityConstants.DETAILS_USERNAME, authenticationName); Set<String> keys = redisTemplate.keys(tokenUsernameKey); if (CollUtil.isEmpty(keys)) { return; } List<Object> tokenList = redisTemplate.opsForValue().multiGet(keys); for (Object token : tokenList) { // 根据token 查询存储的 OAuth2Authorization OAuth2Authorization authorization = this.findByToken((String) token, OAuth2TokenType.ACCESS_TOKEN); if (Objects.isNull(authorization)) { continue; } // 根据 OAuth2Authorization 删除相关令牌 this.remove(authorization); } redisTemplate.delete(keys); } }
更多推荐




所有评论(0)