|
@@ -0,0 +1,424 @@
|
|
|
+package timing.ukulele.auth.authorization;
|
|
|
+
|
|
|
+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 lombok.SneakyThrows;
|
|
|
+import org.springframework.context.annotation.Bean;
|
|
|
+import org.springframework.context.annotation.Configuration;
|
|
|
+import org.springframework.http.MediaType;
|
|
|
+import org.springframework.security.authentication.AuthenticationManager;
|
|
|
+import org.springframework.security.config.Customizer;
|
|
|
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
|
|
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
|
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
|
|
+import org.springframework.security.config.http.SessionCreationPolicy;
|
|
|
+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.jose.jws.SignatureAlgorithm;
|
|
|
+import org.springframework.security.oauth2.jwt.JwtDecoder;
|
|
|
+import org.springframework.security.oauth2.jwt.JwtEncoder;
|
|
|
+import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
|
|
+import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
|
|
|
+import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
|
|
|
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
|
|
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
|
|
+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.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
|
|
+import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
|
|
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
|
|
+import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
|
|
+import org.springframework.security.web.SecurityFilterChain;
|
|
|
+import org.springframework.security.web.header.HeaderWriterFilter;
|
|
|
+import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
|
|
+import org.springframework.util.ObjectUtils;
|
|
|
+import timing.ukulele.auth.authorization.device.DeviceClientAuthenticationConverter;
|
|
|
+import timing.ukulele.auth.authorization.device.DeviceClientAuthenticationProvider;
|
|
|
+import timing.ukulele.auth.authorization.filter.JWTAuthorizationFilter;
|
|
|
+import timing.ukulele.auth.authorization.handler.ConsentAuthenticationFailureHandler;
|
|
|
+import timing.ukulele.auth.authorization.handler.ConsentAuthorizationResponseHandler;
|
|
|
+import timing.ukulele.auth.authorization.handler.DeviceAuthorizationResponseHandler;
|
|
|
+import timing.ukulele.auth.authorization.handler.LoginTargetAuthenticationEntryPoint;
|
|
|
+import timing.ukulele.auth.config.property.TimingSecurityProperties;
|
|
|
+import timing.ukulele.auth.constant.RedisConstants;
|
|
|
+import timing.ukulele.auth.constant.SecurityConstants;
|
|
|
+import timing.ukulele.auth.support.RedisOperator;
|
|
|
+
|
|
|
+import java.security.KeyPair;
|
|
|
+import java.security.KeyPairGenerator;
|
|
|
+import java.security.interfaces.RSAPrivateKey;
|
|
|
+import java.security.interfaces.RSAPublicKey;
|
|
|
+import java.time.Duration;
|
|
|
+import java.util.UUID;
|
|
|
+
|
|
|
+@Configuration
|
|
|
+public class AuthorizationConfig {
|
|
|
+
|
|
|
+ private final RedisOperator<String> redisOperator;
|
|
|
+ private final TimingSecurityProperties securityProperties;
|
|
|
+
|
|
|
+ public AuthorizationConfig(
|
|
|
+ RedisOperator<String> redisOperator,
|
|
|
+ TimingSecurityProperties securityProperties) {
|
|
|
+ this.redisOperator = redisOperator;
|
|
|
+ this.securityProperties = securityProperties;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自定义jwt解析器,设置解析出来的权限信息的前缀与在jwt中的key
|
|
|
+ *
|
|
|
+ * @return jwt解析器 JwtAuthenticationConverter
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
|
|
+ JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
|
|
+ // 设置解析权限信息的前缀,设置为空是去掉前缀
|
|
|
+ grantedAuthoritiesConverter.setAuthorityPrefix("");
|
|
|
+ // 设置权限信息在jwt claims中的key
|
|
|
+ grantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.AUTHORITIES_KEY);
|
|
|
+
|
|
|
+ JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
|
|
+ jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
|
|
|
+
|
|
|
+ return jwtAuthenticationConverter;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将AuthenticationManager注入ioc中,其它需要使用地方可以直接从ioc中获取
|
|
|
+ *
|
|
|
+ * @param authenticationConfiguration 导出认证配置
|
|
|
+ * @return AuthenticationManager 认证管理器
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ @SneakyThrows
|
|
|
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) {
|
|
|
+ return authenticationConfiguration.getAuthenticationManager();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 配置jwk源,使用非对称加密,公开用于检索匹配指定选择器的JWK的方法
|
|
|
+ *
|
|
|
+ * @return JWKSource
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ @SneakyThrows
|
|
|
+ public JWKSource<SecurityContext> jwkSource() {
|
|
|
+ // 先从redis获取
|
|
|
+ String jwkSetCache = redisOperator.get(RedisConstants.AUTHORIZATION_JWS_PREFIX_KEY);
|
|
|
+ if (ObjectUtils.isEmpty(jwkSetCache)) {
|
|
|
+ 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();
|
|
|
+ // 生成jws
|
|
|
+ JWKSet jwkSet = new JWKSet(rsaKey);
|
|
|
+ // 转为json字符串
|
|
|
+ String jwkSetString = jwkSet.toString(Boolean.FALSE);
|
|
|
+ // 存入redis
|
|
|
+ redisOperator.set(RedisConstants.AUTHORIZATION_JWS_PREFIX_KEY, jwkSetString);
|
|
|
+ return new ImmutableJWKSet<>(jwkSet);
|
|
|
+ }
|
|
|
+ // 解析存储的jws
|
|
|
+ JWKSet jwkSet = JWKSet.parse(jwkSetCache);
|
|
|
+ return new ImmutableJWKSet<>(jwkSet);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成rsa密钥对,提供给jwk
|
|
|
+ *
|
|
|
+ * @return 密钥对
|
|
|
+ */
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 配置jwt解析器
|
|
|
+ *
|
|
|
+ * @param jwkSource jwk源
|
|
|
+ * @return JwtDecoder
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
|
|
|
+ return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Bean
|
|
|
+ JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
|
|
|
+ return new NimbusJwtEncoder(jwkSource);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加认证服务器配置,设置jwt签发者、默认端点请求地址等
|
|
|
+ *
|
|
|
+ * @return AuthorizationServerSettings
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ public AuthorizationServerSettings authorizationServerSettings() {
|
|
|
+ // 注意!!!!此处的Issuer很重要,资源服务里的配置要与此一致
|
|
|
+ return AuthorizationServerSettings.builder().issuer(securityProperties.getIssuerUrl()).build();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 配置端点的过滤器链
|
|
|
+ *
|
|
|
+ * @param http spring security核心配置类
|
|
|
+ * @return 过滤器链
|
|
|
+ * @throws Exception 抛出
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
|
|
|
+ JwtDecoder jwtDecoder) throws Exception {
|
|
|
+ // 配置默认的设置,忽略认证端点的csrf校验
|
|
|
+ OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
|
|
|
+ http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
|
|
|
+ // 开启OpenID Connect 1.0协议相关端点
|
|
|
+ .oidc(Customizer.withDefaults())
|
|
|
+ // 设置自定义用户确认授权页
|
|
|
+ .authorizationEndpoint(authorizationEndpoint -> {
|
|
|
+// // 校验授权确认页面是否为完整路径;是否是前后端分离的页面
|
|
|
+// boolean absoluteUrl = UrlUtils.isAbsoluteUrl(securityProperties.getConsentPageUri());
|
|
|
+// // 如果是分离页面则重定向,否则转发请求
|
|
|
+ authorizationEndpoint.consentPage(securityProperties.getConsentPageUri());
|
|
|
+// if (absoluteUrl) {
|
|
|
+ // 适配前后端分离的授权确认页面,成功/失败响应json
|
|
|
+ authorizationEndpoint.errorResponseHandler(new ConsentAuthenticationFailureHandler(securityProperties.getConsentPageUri()));
|
|
|
+ authorizationEndpoint.authorizationResponseHandler(new ConsentAuthorizationResponseHandler(securityProperties.getConsentPageUri()));
|
|
|
+// }
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ // 设备码配置
|
|
|
+// applyDeviceSecurity(http, registeredClientRepository, authorizationServerSettings);
|
|
|
+
|
|
|
+ http
|
|
|
+ // 当未登录时访问认证端点时重定向至login页面
|
|
|
+ .exceptionHandling((exceptions) -> exceptions
|
|
|
+ .defaultAuthenticationEntryPointFor(
|
|
|
+ new LoginTargetAuthenticationEntryPoint(securityProperties.getLoginUrl(), securityProperties.getDeviceActivateUri()),
|
|
|
+ new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
|
|
|
+ )
|
|
|
+// .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
|
|
|
+// .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
|
|
|
+ );
|
|
|
+ // 处理使用access token访问用户信息端点和客户端注册端点
|
|
|
+// .oauth2ResourceServer((resourceServer) -> resourceServer
|
|
|
+// .jwt(Customizer.withDefaults()));
|
|
|
+ JWTAuthorizationFilter jwtFilter = new JWTAuthorizationFilter(jwtDecoder);
|
|
|
+// http.addFilterBefore(jwtFilter, OAuth2AuthorizationEndpointFilter.class);
|
|
|
+// http.addFilterAfter(jwtFilter, SecurityContextHolderFilter.class);
|
|
|
+ http.addFilterAfter(jwtFilter, HeaderWriterFilter.class);
|
|
|
+
|
|
|
+// http.sessionManagement(AbstractHttpConfigurer::disable);
|
|
|
+ http.sessionManagement(session->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
|
|
+ return http.build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void applyDeviceSecurity(HttpSecurity http,
|
|
|
+ RegisteredClientRepository registeredClientRepository,
|
|
|
+ AuthorizationServerSettings authorizationServerSettings) {
|
|
|
+ // 新建设备码converter和provider
|
|
|
+ DeviceClientAuthenticationConverter deviceClientAuthenticationConverter =
|
|
|
+ new DeviceClientAuthenticationConverter(
|
|
|
+ authorizationServerSettings.getDeviceAuthorizationEndpoint());
|
|
|
+ DeviceClientAuthenticationProvider deviceClientAuthenticationProvider =
|
|
|
+ new DeviceClientAuthenticationProvider(registeredClientRepository);
|
|
|
+
|
|
|
+ http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
|
|
|
+ // 设置设备码用户验证url(自定义用户验证页)
|
|
|
+ .deviceAuthorizationEndpoint(deviceAuthorizationEndpoint -> {
|
|
|
+// deviceAuthorizationEndpoint.deviceAuthorizationRequestConverter(deviceClientAuthenticationConverter);
|
|
|
+ deviceAuthorizationEndpoint.verificationUri(securityProperties.getDeviceActivateUri());
|
|
|
+ }
|
|
|
+ )
|
|
|
+ // 设置验证设备码用户确认页面
|
|
|
+ .deviceVerificationEndpoint(deviceVerificationEndpoint -> {
|
|
|
+ // 如果是分离页面则重定向,否则转发请求
|
|
|
+ deviceVerificationEndpoint.consentPage(securityProperties.getConsentPageUri());
|
|
|
+ deviceVerificationEndpoint.errorResponseHandler(new ConsentAuthenticationFailureHandler(securityProperties.getConsentPageUri()));
|
|
|
+ // 添加响应json处理
|
|
|
+ deviceVerificationEndpoint.deviceVerificationResponseHandler(new DeviceAuthorizationResponseHandler(securityProperties.getDeviceActivatedUri()));
|
|
|
+ deviceVerificationEndpoint.authenticationProvider(deviceClientAuthenticationProvider);
|
|
|
+ deviceVerificationEndpoint.deviceVerificationRequestConverter(deviceClientAuthenticationConverter);
|
|
|
+ }
|
|
|
+ );
|
|
|
+// .clientAuthentication(clientAuthentication ->
|
|
|
+// // 客户端认证添加设备码的converter和provider
|
|
|
+// clientAuthentication
|
|
|
+// .authenticationConverter(deviceClientAuthenticationConverter)
|
|
|
+// .authenticationProvider(deviceClientAuthenticationProvider)
|
|
|
+// );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 配置客户端Repository
|
|
|
+ *
|
|
|
+ * @param passwordEncoder 密码解析器
|
|
|
+ * @return 基于数据库的repository
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) {
|
|
|
+ // 默认需要授权确认
|
|
|
+ ClientSettings.Builder builder = ClientSettings.builder()
|
|
|
+ .requireAuthorizationConsent(Boolean.TRUE);
|
|
|
+ TokenSettings.Builder tokenSettingsBuilder = TokenSettings.builder()
|
|
|
+ // 自包含token(jwt)
|
|
|
+ .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
|
|
|
+ // Access Token 存活时间:2小时
|
|
|
+ .accessTokenTimeToLive(Duration.ofHours(2L))
|
|
|
+ // 授权码存活时间:5分钟
|
|
|
+ .authorizationCodeTimeToLive(Duration.ofMinutes(5L))
|
|
|
+ // 设备码存活时间:5分钟
|
|
|
+ .deviceCodeTimeToLive(Duration.ofMinutes(5L))
|
|
|
+ // Refresh Token 存活时间:7天
|
|
|
+ .refreshTokenTimeToLive(Duration.ofDays(7L))
|
|
|
+ // 刷新 Access Token 后是否重用 Refresh Token
|
|
|
+ .reuseRefreshTokens(Boolean.TRUE)
|
|
|
+ // 设置 Id Token 加密方式
|
|
|
+ .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256);
|
|
|
+ RegisteredClient registeredClient = RegisteredClient.withId("messaging-client")
|
|
|
+ // 客户端id
|
|
|
+ .clientId("messaging-client")
|
|
|
+ // 客户端名称
|
|
|
+ .clientName("授权码")
|
|
|
+ // 客户端秘钥,使用密码解析器加密
|
|
|
+ .clientSecret(passwordEncoder.encode("123456"))
|
|
|
+ // 客户端认证方式,基于请求头的认证
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
|
|
+ // 配置资源服务器使用该客户端获取授权时支持的方式
|
|
|
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
|
+ .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
|
|
+ .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
|
|
+ // 授权码模式回调地址,oauth2.1已改为精准匹配,不能只设置域名,并且屏蔽了localhost,本机使用127.0.0.1访问
|
|
|
+ .redirectUri("http://www.test.com:5173/OAuth2Redirect")
|
|
|
+ .redirectUri("http://www.test.com:8000/login/oauth2/code/messaging-client-oidc")
|
|
|
+ // 该客户端的授权范围,OPENID与PROFILE是IdToken的scope,获取授权时请求OPENID的scope时认证服务会返回IdToken
|
|
|
+ .scope(OidcScopes.OPENID)
|
|
|
+ .scope(OidcScopes.PROFILE)
|
|
|
+ // 指定scope
|
|
|
+ .scope("message.read")
|
|
|
+ .scope("message.write")
|
|
|
+ // 客户端设置,设置用户需要确认授权
|
|
|
+ .clientSettings(builder.build())
|
|
|
+ // token相关配置
|
|
|
+ .tokenSettings(tokenSettingsBuilder.build())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ // 正常授权码客户端
|
|
|
+ RegisteredClient opaqueClient = RegisteredClient.withId("opaque-client")
|
|
|
+ // 客户端id
|
|
|
+ .clientId("opaque-client")
|
|
|
+ // 客户端名称
|
|
|
+ .clientName("匿名token")
|
|
|
+ // 客户端秘钥,使用密码解析器加密
|
|
|
+ .clientSecret(passwordEncoder.encode("123456"))
|
|
|
+ // 客户端认证方式,基于请求头的认证
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
|
|
+ // 配置资源服务器使用该客户端获取授权时支持的方式
|
|
|
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
|
+ .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
|
|
+ .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
|
|
+ // 授权码模式回调地址,oauth2.1已改为精准匹配,不能只设置域名,并且屏蔽了localhost,本机使用127.0.0.1访问
|
|
|
+ .redirectUri("http://www.test.com:5173/OAuth2Redirect")
|
|
|
+ // 该客户端的授权范围,OPENID与PROFILE是IdToken的scope,获取授权时请求OPENID的scope时认证服务会返回IdToken
|
|
|
+ .scope(OidcScopes.OPENID)
|
|
|
+ .scope(OidcScopes.PROFILE)
|
|
|
+ // 指定scope
|
|
|
+ .scope("message.read")
|
|
|
+ .scope("message.write")
|
|
|
+ // 客户端设置,设置用户需要确认授权
|
|
|
+ .clientSettings(builder.build())
|
|
|
+ // token相关配置, 设置token为匿名token(opaque token)
|
|
|
+ .tokenSettings(tokenSettingsBuilder.accessTokenFormat(OAuth2TokenFormat.REFERENCE).build())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ // 设备码授权客户端
|
|
|
+ RegisteredClient deviceClient = RegisteredClient.withId("device-message-client")
|
|
|
+ .clientId("device-message-client")
|
|
|
+ .clientName("普通公共客户端")
|
|
|
+ // 公共客户端
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
|
|
|
+ // 设备码授权
|
|
|
+ .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
|
|
+ .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
|
|
+ // 指定scope
|
|
|
+ .scope("message.read")
|
|
|
+ .scope("message.write")
|
|
|
+ // token相关配置
|
|
|
+ .tokenSettings(tokenSettingsBuilder.build())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ // PKCE客户端
|
|
|
+ RegisteredClient pkceClient = RegisteredClient.withId("pkce-message-client")
|
|
|
+ .clientId("pkce-message-client")
|
|
|
+ .clientName("PKCE流程")
|
|
|
+ // 公共客户端
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
|
|
|
+ // 设备码授权
|
|
|
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
|
+ .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
|
|
+ // 授权码模式回调地址,oauth2.1已改为精准匹配,不能只设置域名,并且屏蔽了localhost,本机使用127.0.0.1访问
|
|
|
+ .redirectUri("http://k7fsqkhtbx.cdhttp.cn/PkceRedirect")
|
|
|
+ .redirectUri("http://www.test.com:5173/PkceRedirect")
|
|
|
+ // 开启 PKCE 流程
|
|
|
+ .clientSettings(builder.requireProofKey(Boolean.TRUE).build())
|
|
|
+ // 指定scope
|
|
|
+ .scope("message.read")
|
|
|
+ .scope("message.write")
|
|
|
+ // token相关配置
|
|
|
+ .tokenSettings(tokenSettingsBuilder.build())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ // 基于db存储客户端,还有一个基于内存的实现 JdbcRegisteredClientRepository
|
|
|
+ InMemoryRegisteredClientRepository registeredClientRepository = new InMemoryRegisteredClientRepository(registeredClient, deviceClient, opaqueClient, pkceClient);
|
|
|
+
|
|
|
+ return registeredClientRepository;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * oauth2的授权管理服务
|
|
|
+ *
|
|
|
+ * @param registeredClientRepository 上边注入的客户端repository
|
|
|
+ * @return JdbcOAuth2AuthorizationService
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ public OAuth2AuthorizationService authorizationService(RegisteredClientRepository registeredClientRepository) {
|
|
|
+ // 基于db的oauth2认证服务,JdbcOAuth2AuthorizationService InMemoryOAuth2AuthorizationService
|
|
|
+ return new InMemoryOAuth2AuthorizationService();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 配置基于db的授权确认管理服务
|
|
|
+ *
|
|
|
+ * @param registeredClientRepository 客户端repository
|
|
|
+ * @return JdbcOAuth2AuthorizationConsentService
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ public OAuth2AuthorizationConsentService authorizationConsentService(RegisteredClientRepository registeredClientRepository) {
|
|
|
+ // 基于db的授权确认管理服务,还有一个基于内存的服务实现:InMemoryOAuth2AuthorizationConsentService JdbcOAuth2AuthorizationConsentService
|
|
|
+ return new InMemoryOAuth2AuthorizationConsentService();
|
|
|
+ }
|
|
|
+
|
|
|
+}
|