news 2026/8/28 18:25:39

Spring Authorization Server 1.4.0 使用及详细配置 搭配Spring Boot3.4.0 + Spring Security6.4.1

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Authorization Server 1.4.0 使用及详细配置 搭配Spring Boot3.4.0 + Spring Security6.4.1

Spring Authorization Server 介绍------------------------------Spring Authorization Server 是一个提供OAuth 2.1和OpenID Connect 1.0规范以及其他相关规范的实现的框架。它构建在Spring Security之上,为构建 OpenID Connect 1.0 身份提供商和 OAuth2 授权服务器产品提供安全、轻量级和可定制的基础。### 框架提供五种授权模式* Authorization Code(授权码模式)* Client Credentials(客户端模式)* Refresh Token(令牌刷新)* Device Code(设备码模式)* Token Exchange(token交换)本篇文章主要讲解常用的授权码模式、客户端模式、令牌刷新这三种模式,以及如何自己扩展授权模式(以账号密码模式为例)### 框架提供两种token格式1. Self-contained (JWT) (信息透明)2. Reference (Opaque) (信息不透明)本篇文章会讲解两种token的原理,如何使用不同格式的token,如何自定义token内容的扩展,以及如何自己自定义token生成器(以用短字符串自定义不透明token为例)入门–### 创建一个新项目 添加依赖使用目前最新版Spring Boot 3.4.0 要求JDK版本大于等于17 org.springframework.boot spring-boot-starter-parent 3.4.0 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-security org.springframework.security spring-security-oauth2-authorization-server ### 创建配置类,添加基本配置 package chick.authorization.security; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.MediaType; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.UUID; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = OAuth2AuthorizationServerConfigurer.authorizationServer(); http .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) .with(authorizationServerConfigurer, Customizer.withDefaults()) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ) .exceptionHandling((exceptions) -> exceptions .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint(“/login”), new MediaTypeRequestMatcher(MediaType.TEXT_HTML) ) ); http .getConfigurer(OAuth2AuthorizationServerConfigurer.class) .oidc(Customizer.withDefaults()); return http.build(); } @Bean @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()); return http.build(); } // 用于检索用户进行身份验证 @Bean public UserDetailsService userDetailsService() { UserDetails userDetails = User.withUsername(“admin”) .password(passwordEncoder().encode(“123123”)) .roles(“admin”) .build(); return new InMemoryUserDetailsManager(userDetails); } // 用于密码加密 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 用于管理客户端 @Bean public RegisteredClientRepository registeredClientRepository() { TokenSettings tokenSettings = TokenSettings.builder() .accessTokenTimeToLive(Duration.ofHours(1)) // 设置访问令牌有效期为1小时 .refreshTokenTimeToLive(Duration.ofDays(30)) // 设置刷新令牌有效期为30天 //.accessTokenFormat(OAuth2TokenFormat.REFERENCE) // 这个设置是开启不透明token .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) // 使用透明token .build(); RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId(“chick”) .clientSecret(passwordEncoder().encode(“123456”)) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .redirectUri(“https://www.baidu.com”) .postLogoutRedirectUri(“http://127.0.0.1:8000/”) .scope(OidcScopes.OPENID) .scope(OidcScopes.PROFILE) .tokenSettings(tokenSettings) .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) .build(); return new InMemoryRegisteredClientRepository(oidcClient); } // 用于签署访问令牌 @Bean public JWKSource jwkSource() { KeyPair keyPair = generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); RSAKey rsaKey = new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); JWKSet jwkSet = new JWKSet(rsaKey); return new ImmutableJWKSet<>(jwkSet); } // 启动时生成的密钥,用于创建上面的JWKSource private static KeyPair generateRsaKey() { KeyPair keyPair; try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(“RSA”); keyPairGenerator.initialize(2048); keyPair = keyPairGenerator.generateKeyPair(); } catch (Exception ex) { throw new IllegalStateException(ex); } return keyPair; } // 用于解码签名访问令牌 @Bean public JwtDecoder jwtDecoder(JWKSource jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } // 用于配置 Spring Authorization Server @Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().build(); } } 目前已解锁* Authorization Code(授权码模式)* Client Credentials(客户端模式)* Refresh Token(令牌刷新)### 测试授权码模式浏览器访问:http://127.0.0.1:8000/oauth2/authorizeresponse_type=code&client_id=chick&scope=openid&redirect_uri=https://www.baidu.com会重定向到登录页输入上面UserDetailsService中设置的用户名密码 登录会重定向到百度并 获取到code使用code获取token等信息 POST /oauth2/token HTTP/1.1 Authorization: Basic Base64Encode(client_id:client_secret) Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&redirect_uri=https://www.baidu.com&code=9ZAclrji…### 测试令牌刷新模式使用上一步返回的refresh_token POST /oauth2/token HTTP/1.1 Authorization: Basic Base64Encode(client_id:client_secret) Content-Type: application/x-www-form-urlencoded grant_type=refresh_token&code=9ZAclrji…### 测试客户端授权模式有多种方式,这里介绍常用的client_secret_basic和client_secret_post POST /oauth2/token HTTP/1.1 Authorization: Basic Base64Encode(client_id:client_secret) Content-Type: application/x-www-form-urlencoded grant_type=client_credentialsPOST /oauth2/token HTTP/1.1 Content-Type: application/x-www-form-urlencoded grant_type=client_credentials&client_id=chick&client_secret=123456&scope=openid参数都是对应创建客户端时的设置的### 总结通过项目引入的依赖以及一些简单的配置,即可完成授权服务的基础搭建,但是目前所完成的部分仅适用于自己进行功能的基本测试,无法应用到真正的项目中,真正做到定制化还远远不够,还存在例如目前客户端、用户、登录信息,密钥等都还保存在内存中的问题,应用重启后密钥会刷新导致之前的token无法验签,重启后登录信息会消失导致用户需要重新登录、客户端和用户是写死的,无法动态的加载等,如果我们想在token中加我我们自己自定义的数据该怎么做?不透明token和refresh太长了,如果我们想将他变短些,例如使用uuid来替代他该怎么做,这就需要一些进阶的玩法。进阶–### 客户端持久化首先翻开源码找到客户端需要的数据库表,在数据库中创建该表#### 添加数据库相关依赖 mysql mysql-connector-java 8.0.28 com.alibaba druid-spring-boot-starter 1.2.21 org.springframework.boot spring-boot-starter-jdbc #### 配置数据库 server: port: 8000 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver username: root password: xxxxxx url: jdbc:mysql://ip:port/数据库名?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&allowPublicKeyRetrieval=true&autoReconnect=true #### 修改配置-RegisteredClientRepository // 注入JdbcTemplate private final JdbcTemplate jdbcTemplate; public SecurityConfig(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Bean public RegisteredClientRepository registeredClientRepository() { return new JdbcRegisteredClientRepository(jdbcTemplate); } #### 将之前的客户端插入到数据库中 INSERT INTO oauth2_registered_client (id, client_id, client_id_issued_at, client_secret, client_secret_expires_at, client_name, client_authentication_methods, authorization_grant_types, redirect_uris, post_logout_redirect_uris, scopes, client_settings, token_settings) VALUES (‘79e31552-9ffb-42f3-8aa6-ff83d0860215’, ‘chick’, ‘2024-12-04 14:23:41’, ‘$2a101010ftuHVREYeyjJvzzbkS67.uxo.JfU2SJ7OAeZMC7swZPsFfc69/tJm’, null, ‘79e31552-9ffb-42f3-8aa6-ff83d0860215’, ‘client_secret_post,client_secret_jwt,client_secret_basic’, ‘refresh_token,client_credentials,authorization_code’, ‘https://www.baidu.com’, ‘http://127.0.0.1:8000/’, ‘openid,profile’, ‘{“@class”:“java.util.CollectionsKaTeX parse error: Expected 'EOF', got '}' at position 112: …n-consent":true}̲', '{"@class":"…UnmodifiableMap”,“settings.token.reuse-refresh-tokens”:true,“settings.token.x509-certificate-bound-access-tokens”:false,“settings.token.id-token-signature-algorithm”:[“org.springframework.security.oauth2.jose.jws.SignatureAlgorithm”,“RS256”],“settings.token.access-token-time-to-live”:[“java.time.Duration”,3600.000000000],“settings.token.access-token-format”:{“@class”:“org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat”,“value”:“self-contained”},“settings.token.refresh-token-time-to-live”:[“java.time.Duration”,2592000.000000000],“settings.token.authorization-code-time-to-live”:[“java.time.Duration”,300.000000000],“settings.token.device-code-time-to-live”:[“java.time.Duration”,300.000000000]}’) 至此客户端的持久化实现完毕,可以按照上面的方法测试三种授权模式#### 自定义持久化可以自己实现RegisteredClientRepository接口,按自己的逻辑实现方法即可 public interface RegisteredClientRepository { /** * Saves the registered client. * *

* IMPORTANT: Sensitive information should be encoded externally from the * implementation, e.g. {@link RegisteredClient#getClientSecret()} * @param registeredClient the {@link RegisteredClient}/ void save(RegisteredClient registeredClient); /* * Returns the registered client identified by the provided {@code id}, or * {@code null} if not found. * @param id the registration identifier * @return the {@link RegisteredClient} if found, otherwise {@code null}/ @Nullable RegisteredClient findById(String id); /* * Returns the registered client identified by the provided {@code clientId}, or * {@code null} if not found. * @param clientId the client identifier * @return the {@link RegisteredClient} if found, otherwise {@code null}/ @Nullable RegisteredClient findByClientId(String clientId); } #### 总结客户端的持久化比较简单,可以直接使用官方提供的JdbcRegisteredClientRepository(),也可以自己实现RegisteredClientRepository,按照自己的逻辑实现客户端保存和查询功能,只需要将实现类作为bean注入到ioc容器中即可### 用户持久化#### 使用JdbcDaoImpl(不推荐,一般也不会用这个)#### 创建表找到框架自带的数据库表脚本#### 修改配置-UserDetailsService private final JdbcTemplate jdbcTemplate; public SecurityConfig(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Bean public UserDetailsService userDetailsService() { JdbcDaoImpl jdbcDao = new JdbcDaoImpl(); jdbcDao.setJdbcTemplate(jdbcTemplate); return jdbcDao; } #### 将之前的用户据插入到数据库中 INSERT INTO users (username, password, enabled) VALUES (‘admin’, ‘$2a101010TdhQiv3We.BvTayb.KXoUOvI19xgHnw8fmKcE6kLuBD.LhehczevG’, 1); INSERT INTO authorities (username, authority) VALUES (‘admin’, ‘user’); 至此用户的持久化实现完毕,可以按照上面的方法测试三种授权模式#### 自定义持久化(推荐)##### 实现UserDetails自定义自己的用户属性 package chick.authorization.security; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.Set; @JsonIgnoreProperties(ignoreUnknown = true) public class ChickUserDetails implements UserDetails { private String username; private String password; private String other; private Collection<? extends GrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public String getOther() { return other; } public void setOther(String other) { this.other = other; } public void setAuthorities(Set authorities) { this.authorities = authorities; } } ##### 实现UserDetailsService package chick.authorization.security; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; import java.util.*; @Service public class ChickUserDetailsService implements UserDetailsService { private final JdbcTemplate jdbcTemplate; public ChickUserDetailsService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 查询逻辑 通过用户名查询用户信息,按照自己的逻辑写 String query = "SELECT username, password FROM users WHERE username = ?"; ChickUserDetails chickUserDetails = jdbcTemplate.queryForObject(query, (rs, rowNum) -> { ChickUserDetails user = new ChickUserDetails(); user.setUsername(rs.getString("username")); user.setPassword(rs.getString("password")); return user; }, username); if (ObjectUtils.isEmpty(chickUserDetails)){ throw new UsernameNotFoundException(username + " not found"); } String queryAuthority = "SELECT authority FROM authorities WHERE username = '" + username + "'"; List authorities = jdbcTemplate.query(queryAuthority, (rs, rowNum) -> rs.getString("authority")); Set simpleGrantedAuthorities = new HashSet<>(); authorities.forEach(authority -> { SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority); simpleGrantedAuthorities.add(simpleGrantedAuthority); }); chickUserDetails.setAuthorities(simpleGrantedAuthorities); return chickUserDetails; } } ##### 去除之前配置文件中的UserDetailsService // @Bean // public UserDetailsService userDetailsService() { // JdbcDaoImpl jdbcDao = new JdbcDaoImpl(); // jdbcDao.setJdbcTemplate(jdbcTemplate); // //UserDetails userDetails = User.withUsername("admin") // // .password(passwordEncoder().encode("123123")) // // .roles("admin") // // .build(); // //return new InMemoryUserDetailsManager(userDetails); // return jdbcDao; // } 完成并测试#### 总结使用自己实现UserDetailsService的bean更具有灵活性,也可以结合redis等缓存技术给系统提速### token等登录信息持久化#### 创建表找到框架中的脚本 在数据库中执行创建表![](https://i-blog.csdnimg.cn/img_convert/37838671fdc9b2017005ba806dd40970.png)#### 配置 // 注入依赖 private final JdbcTemplate jdbcTemplate; private final UserDetailsService userDetailsService; private final PasswordEncoder passwordEncoder; private final RegisteredClientRepository registeredClientRepository; public SecurityConfig(JdbcTemplate jdbcTemplate, UserDetailsService userDetailsService, @Lazy PasswordEncoder passwordEncoder, @Lazy RegisteredClientRepository registeredClientRepository) { this.jdbcTemplate = jdbcTemplate; this.userDetailsService = userDetailsService; this.passwordEncoder = passwordEncoder; this.registeredClientRepository = registeredClientRepository; } // 将OAuth2AuthorizationServiceBean注入容器 @Bean public OAuth2AuthorizationService oAuth2AuthorizationService() { // return new InMemoryOAuth2AuthorizationService(); 使用内存 return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository); // 使用数据库 } 完成并测试,登录成功后可以发现token的信息保存到数据库中了![](https://i-blog.csdnimg.cn/img_convert/eca5769fd26992d70d2ece6285b9bdd0.png)### Opaque Token(不透明Token)#### 更改配置不透明token是返回一个没有意义的id,然后通过id再去获取用户的token,使用不透明id只需要将客户端的TokenSettings的accessTokenFormat设置为OAuth2TokenFormat.REFERENCE![](https://i-blog.csdnimg.cn/img_convert/f2bebf8f991e52c309064def22408b9f.png)如果已实现持久化,可以直接更改数据库中的配置#### 测试登录![](https://i-blog.csdnimg.cn/img_convert/625507e177cc5f9f78e06040b4721c2a.png)#### 解析token POST /oauth2/introspect HTTP/1.1 Authorization: Basic Base64Encode(client_id:client_secret) Content-Type: application/x-www-form-urlencoded token=YxxTPrfmz__15X9UYH....... ![](https://i-blog.csdnimg.cn/img_convert/af19403dfb4a8b566ba23794ec53c1cb.png)### token信息扩展#### 透明token扩展 package chick.authorization.token; import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.stereotype.Service; /** * @Author xkx * @Description 透明token扩展 * @Date 2024/11/28 21:33 * @Param * @return **/ @Service public class ChickSelfContainedTokenEnhancer implements OAuth2TokenCustomizer { @Override public void customize(JwtEncodingContext context) { context.getClaims().claims(claims -> { claims.put("custom1", "1"); claims.put("custom2", "2"); }); } } #### 不透明token扩展 package chick.authorization.token; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.stereotype.Service; /** * @Author xkx * @Description 不透明token扩展 * @Date 2024/11/28 21:33 * @Param * @return **/ @Service public class ChickReferenceTokenEnhancer implements OAuth2TokenCustomizer { @Override public void customize(OAuth2TokenClaimsContext context) { context.getClaims().claims(claims -> { claims.put("custom1", "1"); claims.put("custom2", "2"); }); } } #### 配置修改 // 透明token扩展 private final ChickSelfContainedTokenEnhancer chickSelfContainedTokenEnhancer; // 非透明token扩展 private final ChickReferenceTokenEnhancer chickReferenceTokenEnhancer; // 数据库链接 private final JdbcTemplate jdbcTemplate; // 密码编码器 private final PasswordEncoder passwordEncoder; // jwt编码器 private final JwtEncoder jwtEncoder; // 客户端管理 private final RegisteredClientRepository registeredClientRepository; // 用户检索 private final UserDetailsService userDetailsService; public SecurityConfig(ChickSelfContainedTokenEnhancer chickSelfContainedTokenEnhancer, ChickReferenceTokenEnhancer chickReferenceTokenEnhancer, JdbcTemplate jdbcTemplate, @Lazy PasswordEncoder passwordEncoder, @Lazy JwtEncoder jwtEncoder, @Lazy RegisteredClientRepository registeredClientRepository, @Lazy UserDetailsService userDetailsService) { this.chickSelfContainedTokenEnhancer = chickSelfContainedTokenEnhancer; this.chickReferenceTokenEnhancer = chickReferenceTokenEnhancer; this.jdbcTemplate = jdbcTemplate; this.passwordEncoder = passwordEncoder; this.jwtEncoder = jwtEncoder; this.registeredClientRepository = registeredClientRepository; this.userDetailsService = userDetailsService; } /** * token生成器。配置要使用的token生成器 **/ @Bean public OAuth2TokenGenerator<?> tokenGenerator() { // 当客户端的tokenSetting的OAuth2TokenFormat设置为OAuth2TokenFormat.SELF_CONTAINED时 使用下面的 JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);// jwtToken生成器(当客户端的token格式为self-contained时使用) jwtGenerator.setJwtCustomizer(chickSelfContainedTokenEnhancer);// 设置jwt-token自定义扩展 // 当客户端的tokenSetting的OAuth2TokenFormat设置为OAuth2TokenFormat.REFERENCE 使用下面的 OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();// 不透明的token生成器 accessTokenGenerator.setAccessTokenCustomizer(chickReferenceTokenEnhancer);// 设置id-token自定义扩展 // refreshToken生成器 OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();// refreshToken生成器 return new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, refreshTokenGenerator); } #### 测试登录发现不透明和透明token都加上了我们自定义的信息### 自定义token生成器#### 以不透名token为例 package chick.authorization.token; import org.springframework.security.crypto.keygen.StringKeyGenerator; import java.util.UUID; /uuid生成/ public class UUIDKeyGenerator implements StringKeyGenerator { @Override public String generateKey() { return UUID.randomUUID().toString().toLowerCase(); } } #### 创建token生成器 package chick.authorization.token; import org.springframework.security.crypto.keygen.StringKeyGenerator; import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.time.Instant; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.UUID; /* * @Author xkx * @Description 自定义token生成器 * @Date 2024/12/3 22:33 * @Param * @return/ public class UUIDOAuth2TokenGenerator implements OAuth2TokenGenerator { private final StringKeyGenerator accessTokenGenerator = new UUIDKeyGenerator(); @Override public OAuth2AccessToken generate(OAuth2TokenContext context) { // @formatter:off if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) || !OAuth2TokenFormat.REFERENCE.equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) { return null; } // @formatter:on String issuer = null; if (context.getAuthorizationServerContext() != null) { issuer = context.getAuthorizationServerContext().getIssuer(); } RegisteredClient registeredClient = context.getRegisteredClient(); Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive()); // @formatter:off OAuth2TokenClaimsSet.Builder claimsBuilder = OAuth2TokenClaimsSet.builder(); if (StringUtils.hasText(issuer)) { claimsBuilder.issuer(issuer); } claimsBuilder .subject(context.getPrincipal().getName()) .audience(Collections.singletonList(registeredClient.getClientId())) .issuedAt(issuedAt) .expiresAt(expiresAt) .notBefore(issuedAt) .id(UUID.randomUUID().toString()); if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) { claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes()); } OAuth2TokenClaimsSet accessTokenClaimsSet = claimsBuilder.build(); return new OAuth2AccessTokenClaims(OAuth2AccessToken.TokenType.BEARER, this.accessTokenGenerator.generateKey(), accessTokenClaimsSet.getIssuedAt(), accessTokenClaimsSet.getExpiresAt(), context.getAuthorizedScopes(), accessTokenClaimsSet.getClaims()); } private static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor { private final Map<String, Object> claims; private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt, Set scopes, Map<String, Object> claims) { super(tokenType, tokenValue, issuedAt, expiresAt, scopes); this.claims = claims; } @Override public Map<String, Object> getClaims() { return this.claims; } } } #### 创建refreshToken生成器 package chick.authorization.token; import io.micrometer.common.lang.Nullable; import org.springframework.security.crypto.keygen.StringKeyGenerator; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import java.time.Instant; /* @Author xkx * @Description 自定义refreshToken生成器 * @Date 2024/12/3 22:33 * @Param * @return **/ public class UUIDOAuth2RefreshTokenGenerator implements OAuth2TokenGenerator { private final StringKeyGenerator refreshTokenGenerator = new UUIDKeyGenerator(); @Nullable @Override public OAuth2RefreshToken generate(OAuth2TokenContext context) { if (!OAuth2TokenType.REFRESH_TOKEN.equals(context.getTokenType())) { return null; } Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plus(context.getRegisteredClient().getTokenSettings().getRefreshTokenTimeToLive()); return new OAuth2RefreshToken(this.refreshTokenGenerator.generateKey(), issuedAt, expiresAt); } } #### 配置 @Bean public OAuth2TokenGenerator<?> tokenGenerator() { JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder); UUIDOAuth2TokenGenerator accessTokenGenerator = new UUIDOAuth2TokenGenerator();// 不透明的token生成器 UUIDOAuth2RefreshTokenGenerator refreshTokenGenerator = new UUIDOAuth2RefreshTokenGenerator();// refreshToken生成器 return new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, refreshTokenGenerator); } #### 测试登录![](https://i-blog.csdnimg.cn/img_convert/b5dcf01e63824b882a38e057bffa4425.png)![](https://i-blog.csdnimg.cn/img_convert/0b4d51dd8276f9a9ba47f181378c2eff.png)#### 总结测试完成发现不透明token变成了uuid的形式,成功### 扩展授权类型#### 自定义GrantType类型 package chick.authorization.granter; import org.springframework.security.oauth2.core.AuthorizationGrantType; /* 扩展GrantType类型 */ public record CustomAuthorizationGrantType(String value) { // 账号密码模式 public static final AuthorizationGrantType PASSWORD = new AuthorizationGrantType("password"); } #### 工具类 package chick.authorization.utils; import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; /** * @Author xkx * @Description 端点工具类 * @Date 2024/12/1 19:55 * @Param * @return **/ public class OAuth2EndpointUtils { private OAuth2EndpointUtils() { } public static MultiValueMap

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/28 18:24:25

C++ STL核心组件解析:从容器、迭代器到泛型编程实战

1. 从“轮子”到“工具箱”&#xff1a;为什么你需要STL 如果你写过一段时间的C&#xff0c;尤其是从C语言转过来&#xff0c;或者自己动手实现过链表、动态数组&#xff0c;那你一定经历过那种“造轮子”的痛苦。每次新项目开始&#xff0c;都得先吭哧吭哧写一个 Vector 类&…

作者头像 李华
网站建设 2026/8/28 18:22:40

边缘推理框架升级的核查

边缘推理框架升级的核查量化推理框架升级时&#xff0c;先检查算子接口、张量布局、缓存分配策略和运行时依赖。不同模型、驱动与硬件的组合可能表现不同&#xff0c;不能把某一份日志或单次测量当作通用结论。 先比较内存与数值契约 在隔离环境中固定模型、量化方式、输入集与…

作者头像 李华
网站建设 2026/8/28 18:17:33

使用 authentik 搭建统一身份认证与 OIDC 单点登录实践

在自建应用越来越多之后&#xff0c;最容易失控的不是部署&#xff0c;而是登录入口。每个应用都维护一套账号密码&#xff0c;用户要记住多个密码&#xff0c;管理员要处理多次找回密码&#xff0c;审计又看不到统一的登录记录。authentik 是解决这类问题的开源身份认证平台&a…

作者头像 李华
网站建设 2026/8/28 18:15:12

海康工业相机SDK C#开发实战:从示例程序到项目工程化

简介&#xff1a;工业相机作为机器视觉系统的核心传感器&#xff0c;其软件开发涉及设备通信、图像采集与处理等关键技术。通过SDK&#xff08;软件开发工具包&#xff09;与相机交互&#xff0c;开发者可以控制相机参数、获取图像数据&#xff0c;并将其集成到自动化检测、测量…

作者头像 李华
网站建设 2026/8/28 18:14:54

Python SymPy求解方程组:从数学建模到工程实战

1. 从“人狗大作战”到科学计算&#xff1a;为什么SymPy是Python数学建模的隐形王牌最近在社区里看到不少朋友在讨论“人狗大作战”这类趣味编程项目&#xff0c;还有各种自动化脚本、数据分析的需求。这背后其实都指向一个核心能力&#xff1a;如何让计算机帮你处理复杂的计算…

作者头像 李华
网站建设 2026/8/28 18:13:03

软考系统架构设计师论文涉及知识点之Redis(5)

接前一篇文章:软考系统架构设计师论文涉及知识点之Redis(4) 本文内容参考: Redis 从入门到实战:一篇文章彻底搞懂 Redis 核心知识_redis从入门到实战-CSDN博客 软考架构师必看|Redis 10 个必考方向详解(附模拟题+踩分技巧)_redis 软考-CSDN博客 软考高级系统架构师之…

作者头像 李华