尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

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

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 Exchangetoken交换本篇文章主要讲解常用的授权码模式、客户端模式、令牌刷新这三种模式以及如何自己扩展授权模式以账号密码模式为例### 框架提供两种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_typecodeclient_idchickscopeopenidredirect_urihttps://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_typeauthorization_coderedirect_urihttps://www.baidu.comcode9ZAclrji…### 测试令牌刷新模式使用上一步返回的refresh_token POST /oauth2/token HTTP/1.1 Authorization: Basic Base64Encode(client_id:client_secret) Content-Type: application/x-www-form-urlencoded grant_typerefresh_tokencode9ZAclrji…### 测试客户端授权模式有多种方式这里介绍常用的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_typeclient_credentialsPOST /oauth2/token HTTP/1.1 Content-Type: application/x-www-form-urlencoded grant_typeclient_credentialsclient_idchickclient_secret123456scopeopenid参数都是对应创建客户端时的设置的### 总结通过项目引入的依赖以及一些简单的配置即可完成授权服务的基础搭建但是目前所完成的部分仅适用于自己进行功能的基本测试无法应用到真正的项目中真正做到定制化还远远不够还存在例如目前客户端、用户、登录信息密钥等都还保存在内存中的问题应用重启后密钥会刷新导致之前的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/数据库名?characterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLfalseuseJDBCCompliantTimezoneShifttrueuseLegacyDatetimeCodefalseserverTimezoneGMT%2B8allowMultiQueriestrueallowPublicKeyRetrievaltrueautoReconnecttrue #### 修改配置-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 tokenYxxTPrfmz__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 MapString, Object claims; private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt, Set scopes, MapString, Object claims) { super(tokenType, tokenValue, issuedAt, expiresAt, scopes); this.claims claims; } Override public MapString, 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
返回列表