diff --git a/base-framework-security-starter/pom.xml b/base-framework-security-starter/pom.xml
index d302395..58f1804 100644
--- a/base-framework-security-starter/pom.xml
+++ b/base-framework-security-starter/pom.xml
@@ -88,5 +88,9 @@
spring-boot-starter-data-redis
true
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
\ No newline at end of file
diff --git a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/AuthenticationProviderConfiguration.java b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/AuthenticationProviderConfiguration.java
new file mode 100644
index 0000000..e4a99a4
--- /dev/null
+++ b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/AuthenticationProviderConfiguration.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fuhouyu.framework.security;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+/**
+ *
+ * oidc配置
+ *
+ *
+ * @author fuhouyu
+ * @since 2024/11/4 22:06
+ */
+@Configuration(proxyBeanMethods = false)
+public class AuthenticationProviderConfiguration {
+
+ /**
+ * dao层实现
+ *
+ * @param passwordEncoder 密码管理器
+ * @param userDetailsService 用户详情接口
+ * @return dao默认实现
+ */
+ @Bean
+ public AuthenticationProvider daoAuthenticationProvider(UserDetailsService userDetailsService,
+ PasswordEncoder passwordEncoder) {
+ DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(passwordEncoder);
+ daoAuthenticationProvider.setUserDetailsService(userDetailsService);
+ return daoAuthenticationProvider;
+ }
+
+// /**
+// * oidc provider
+// *
+// * @param userDetailsService 用户详情service
+// * @param clientRegistrationRepository 客户端仓库信息
+// * @return oidcProvider
+// */
+// @Bean
+// public AuthenticationProvider oidcAuthenticationProvider(UserDetailsService userDetailsService,
+// ClientRegistrationRepository clientRegistrationRepository) {
+// return new OidcAuthenticationProvider(new DefaultOAuth2UserService(), userDetailsService, clientRegistrationRepository);
+// }
+
+
+}
diff --git a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/OpenPlatformConfiguration.java b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/OpenPlatformConfiguration.java
deleted file mode 100644
index bc50d83..0000000
--- a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/OpenPlatformConfiguration.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2024-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.fuhouyu.framework.security;
-
-import com.fuhouyu.framework.security.properties.OpenPlatformAuthProperties;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Configuration;
-
-/**
- *
- * 开放平台自动装配类
- *
- *
- * @author fuhouyu
- * @since 2024/8/15 16:23
- */
-@Configuration
-@EnableConfigurationProperties(OpenPlatformAuthProperties.class)
-public class OpenPlatformConfiguration {
-
-
-}
diff --git a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/SecurityAutoConfiguration.java b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/SecurityAutoConfiguration.java
index 3d4711b..17f6efe 100644
--- a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/SecurityAutoConfiguration.java
+++ b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/SecurityAutoConfiguration.java
@@ -23,6 +23,7 @@
import com.fuhouyu.framework.security.token.TokenStoreCache;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -30,8 +31,6 @@
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
-import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
-import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.List;
@@ -44,9 +43,12 @@
* @author fuhouyu
* @since 2024/8/15 16:22
*/
-@Configuration
-@Import({OpenPlatformConfiguration.class})
-@AutoConfigureAfter(CacheAutoConfiguration.class)
+@Configuration(proxyBeanMethods = false)
+@AutoConfigureAfter({
+ CacheAutoConfiguration.class,
+ OAuth2ClientAutoConfiguration.class
+})
+@Import({AuthenticationProviderConfiguration.class})
public class SecurityAutoConfiguration {
/**
@@ -66,17 +68,11 @@ public TokenStore tokenStore(CacheService cacheService) {
* 认证管理器配置这里可以进行除其他登录模式的扩展,需要实现{@link AuthenticationProvider}
*
* @param authenticationProviders 认证提供者集合
- * @param userDetailsService 用户接口详情
- * @param passwordEncoder 密码认证管理器
* @return 认证管理器
*/
- @Bean("authenticationManager")
+ @Bean
@Primary
- public AuthenticationManager authenticationManager(
- List authenticationProviders,
- UserDetailsService userDetailsService,
- PasswordEncoder passwordEncoder) {
- authenticationProviders.add(daoAuthenticationProvider(userDetailsService, passwordEncoder));
+ public AuthenticationManager authenticationManager(List authenticationProviders) {
return new ProviderManager(authenticationProviders);
}
@@ -91,19 +87,5 @@ public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactory.createDelegatingPasswordEncoder("sm3");
}
- /**
- * dao层实现
- *
- * @param passwordEncoder 密码管理器
- * @param userDetailsService 用户详情接口
- * @return dao默认实现
- */
- private AuthenticationProvider daoAuthenticationProvider(UserDetailsService userDetailsService,
- PasswordEncoder passwordEncoder) {
- DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(passwordEncoder);
- daoAuthenticationProvider.setUserDetailsService(userDetailsService);
- return daoAuthenticationProvider;
- }
-
}
diff --git a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/GrantTypeAuthenticationTokenEnum.java b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/GrantTypeAuthenticationTokenEnum.java
index fb04278..c2dae49 100644
--- a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/GrantTypeAuthenticationTokenEnum.java
+++ b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/GrantTypeAuthenticationTokenEnum.java
@@ -16,8 +16,7 @@
package com.fuhouyu.framework.security.core;
import com.fuhouyu.framework.common.utils.JacksonUtil;
-import com.fuhouyu.framework.security.core.authentication.refreshtoken.RefreshAuthenticationProvider;
-import com.fuhouyu.framework.security.core.authentication.wechat.WechatAppletsPlatformProvider;
+import com.fuhouyu.framework.security.core.provider.refreshtoken.RefreshAuthenticationProvider;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AbstractAuthenticationToken;
@@ -62,16 +61,7 @@ public Class getAuthenticationTokenCl
return (Class) RefreshAuthenticationProvider.RefreshAuthenticationToken.class;
}
},
-
- /**
- * 微信小程序
- */
- WECHAT_APPLETS("WECHAT_APPLETS") {
- @Override
- public Class getAuthenticationTokenClass() {
- return (Class) WechatAppletsPlatformProvider.WechatAppletsAuthenticationToken.class;
- }
- };
+ ;
private final String grantType;
diff --git a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/authentication/wechat/WechatAppletsPlatformProvider.java b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/authentication/wechat/WechatAppletsPlatformProvider.java
deleted file mode 100644
index f0d334c..0000000
--- a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/authentication/wechat/WechatAppletsPlatformProvider.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2024-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.fuhouyu.framework.security.core.authentication.wechat;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fuhouyu.framework.common.utils.JacksonUtil;
-import com.fuhouyu.framework.common.utils.LoggerUtil;
-import com.fuhouyu.framework.security.core.AbstractAuthenticationProvider;
-import com.fuhouyu.framework.security.properties.OpenPlatformAuthProperties;
-import lombok.EqualsAndHashCode;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.http.ResponseEntity;
-import org.springframework.security.authentication.AbstractAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-import org.springframework.web.client.RestTemplate;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.function.Function;
-
-/**
- *
- * 微信小程序提供者
- *
- *
- * @author fuhouyu
- * @since 2024/8/15 12:32
- */
-@Slf4j
-public class WechatAppletsPlatformProvider extends AbstractAuthenticationProvider {
-
- private final RestTemplate restTemplate;
-
- private final UserDetailsService userDetailsService;
-
- private final OpenPlatformAuthProperties.AuthDetail authDetail;
-
-
- /**
- * 构造函数
- *
- * @param restTemplate restTemplate
- * @param userDetailsService 用户详情接口
- * @param authDetail 第三方平台认证相关的信息
- */
- public WechatAppletsPlatformProvider(RestTemplate restTemplate,
- UserDetailsService userDetailsService,
- OpenPlatformAuthProperties.AuthDetail authDetail) {
- super(null);
- this.restTemplate = restTemplate;
- this.userDetailsService = userDetailsService;
- this.authDetail = authDetail;
- }
-
- /**
- * 构造函数
- *
- * @param restTemplate restTemplate
- * @param userDetailsService 用户接口详情
- * @param authDetail 认证详情
- * @param registerFunction 注册函数
- */
- public WechatAppletsPlatformProvider(RestTemplate restTemplate,
- UserDetailsService userDetailsService,
- OpenPlatformAuthProperties.AuthDetail authDetail,
- Function registerFunction) {
- super(registerFunction);
- this.restTemplate = restTemplate;
- this.userDetailsService = userDetailsService;
- this.authDetail = authDetail;
- }
-
-
- @Override
- public WechatAppletsUserInfo loadPlatformUser(Authentication authentication) {
- String requestUrl = this.getRequestUrl((String) authentication.getPrincipal());
- ResponseEntity wechatAppletsUserInfoEntity = restTemplate.getForEntity(requestUrl, String.class);
- if (!Objects.equals(wechatAppletsUserInfoEntity.getStatusCode().value(), 200)) {
- LoggerUtil.error(log, "微信小程序请求url失败,jsCode:{}, 请求参数:{}, 返回的状态码:{}",
- authentication.getPrincipal(), requestUrl.replaceAll(authDetail.getClientSecret(), "*"),
- wechatAppletsUserInfoEntity.getStatusCode().value());
- throw new IllegalArgumentException("微信小程序登录不正常,请检索配置参数是否正确");
- }
- WechatAppletsUserInfo wechatAppletsUserInfo =
- JacksonUtil.readValue(wechatAppletsUserInfoEntity.getBody(), WechatAppletsUserInfo.class);
- if (Objects.nonNull(wechatAppletsUserInfo.getErrCode()) && !Objects.equals(wechatAppletsUserInfo.getErrCode(), 0)) {
- throw new IllegalArgumentException(String.format("微信小程序登录失败:%s", wechatAppletsUserInfo.getErrMsg()));
- }
- return wechatAppletsUserInfo;
- }
-
- @Override
- public UserDetails loadUserDetails(WechatAppletsUserInfo wechatAppletsUserInfo) throws UsernameNotFoundException {
- return this.userDetailsService.loadUserByUsername(wechatAppletsUserInfo.getOpenId());
- }
-
- @Override
- public boolean supports(Class> authentication) {
- return WechatAppletsAuthenticationToken.class.isAssignableFrom(authentication);
- }
-
- /**
- * 获取app拼接后的url
- *
- * @param jsCode 登录时获取的 code,可通过wx.login获取
- * @return app参数
- */
- private String getRequestUrl(String jsCode) {
- return String.format("%s?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
- authDetail.getLoginUrl(),
- authDetail.getClientId(),
- authDetail.getClientSecret(),
- jsCode);
- }
-
- /**
- * 使用它才支持微信小程序认证
- */
- @EqualsAndHashCode(callSuper = true)
- public static class WechatAppletsAuthenticationToken extends AbstractAuthenticationToken {
-
- /**
- * 微信一次性认证码
- */
- private final String jsCode;
-
-
- /**
- * 构造函数
- *
- * @param jsCode 登录时获取的 code,可通过wx.login获取
- */
- @JsonCreator
- public WechatAppletsAuthenticationToken(
- @JsonProperty("jsCode") String jsCode) {
- super(List.of());
- this.jsCode = jsCode;
- }
-
- @Override
- public Object getCredentials() {
- return null;
- }
-
- @Override
- public Object getPrincipal() {
- return this.jsCode;
- }
- }
-}
diff --git a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/authentication/wechat/WechatAppletsUserInfo.java b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/authentication/wechat/WechatAppletsUserInfo.java
deleted file mode 100644
index d16972f..0000000
--- a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/authentication/wechat/WechatAppletsUserInfo.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2024-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.fuhouyu.framework.security.core.authentication.wechat;
-
-import com.fasterxml.jackson.annotation.JsonAlias;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.ToString;
-
-/**
- *
- * 微信用户详情
- *
- *
- * @author fuhouyu
- * @since 2024/8/15 12:33
- */
-@ToString
-@Getter
-@Setter
-public class WechatAppletsUserInfo {
-
- /**
- * 会话密钥
- */
- @JsonAlias("session_key")
- private String sessionKey;
-
- /**
- * 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台账号下会返回
- */
- @JsonAlias("unionid")
- private String unionId;
-
- /**
- * 错误信息
- */
- @JsonAlias("errmsg")
- private String errMsg;
-
- /**
- * 用户唯一标识
- */
- @JsonAlias("openid")
- private String openId;
-
- /**
- * 错误码
- */
- @JsonAlias("errcode")
- private Integer errCode;
-}
diff --git a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/provider/oidc/OidcAuthenticationProvider.java b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/provider/oidc/OidcAuthenticationProvider.java
new file mode 100644
index 0000000..048b92f
--- /dev/null
+++ b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/provider/oidc/OidcAuthenticationProvider.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2024-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fuhouyu.framework.security.core.provider.oidc;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.*;
+import org.springframework.http.converter.FormHttpMessageConverter;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
+import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
+import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
+import org.springframework.security.oauth2.core.*;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
+import org.springframework.security.oauth2.core.oidc.OidcIdToken;
+import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
+import org.springframework.security.oauth2.jwt.JwtException;
+import org.springframework.util.Assert;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestOperations;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+
+import static org.springframework.http.MediaType.APPLICATION_JSON;
+
+/**
+ *
+ * oidc 认证token
+ *
+ *
+ * @author fuhouyu
+ * @since 2024/11/4 20:08
+ */
+@Slf4j
+public class OidcAuthenticationProvider implements AuthenticationProvider {
+
+ private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
+
+ private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
+
+ private static final String INVALID_NONCE_ERROR_CODE = "invalid_nonce";
+ private static final MediaType APPLICATION_FORM_URLENCODED_UTF8 = new MediaType(
+ MediaType.APPLICATION_FORM_URLENCODED, StandardCharsets.UTF_8);
+ private final GrantedAuthoritiesMapper authoritiesMapper = ((authorities) -> authorities);
+ private final UserDetailsService userDetailsService;
+ private final OAuth2UserService userService;
+ private final ClientRegistrationRepository clientRegistrationRepository;
+ private final JwtDecoderFactory jwtDecoderFactory = new OidcIdTokenDecoderFactory();
+ private final RestOperations restOperations;
+
+ public OidcAuthenticationProvider(OAuth2UserService userService,
+ UserDetailsService userDetailsService,
+ ClientRegistrationRepository clientRegistrationRepository) {
+ this.userService = userService;
+ this.userDetailsService = userDetailsService;
+ this.clientRegistrationRepository = clientRegistrationRepository;
+ RestTemplate restTemplate = new RestTemplate(
+ Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
+ restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
+ this.restOperations = restTemplate;
+ }
+
+ static String createHash(String nonce) throws NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ byte[] digest = md.digest(nonce.getBytes(StandardCharsets.US_ASCII));
+ return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
+ }
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ OidcAuthenticationToken oidcAuthenticationToken = (OidcAuthenticationToken) authentication;
+ ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId(oidcAuthenticationToken.getClientId());
+ if (Objects.isNull(clientRegistration)) {
+ throw new IllegalArgumentException("invalid client id " + oidcAuthenticationToken.getClientId());
+ }
+ RequestEntity> requestEntity = this.createRequestEntity(clientRegistration);
+ OAuth2AccessTokenResponse accessTokenResponse = this.getResponse(requestEntity);
+ Map additionalParameters = this.getAdditionalParameters(accessTokenResponse, clientRegistration);
+ OidcIdToken idToken = createOidcToken(clientRegistration, accessTokenResponse);
+ validateNonce(oidcAuthenticationToken.getNonce(), idToken);
+ OAuth2User oidcUser = this.userService.loadUser(new OidcUserRequest(clientRegistration,
+ accessTokenResponse.getAccessToken(), idToken, additionalParameters));
+ UserDetails userDetails = this.userDetailsService.loadUserByUsername(oidcUser.getName());
+ if (Objects.isNull(userDetails)) {
+ throw new IllegalArgumentException("系统中的用户不存在");
+ }
+ Collection extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
+ .mapAuthorities(oidcUser.getAuthorities());
+ OidcAuthenticationToken result = new OidcAuthenticationToken(
+ oidcAuthenticationToken.getCode(),
+ oidcAuthenticationToken.getState(),
+ clientRegistration.getClientId(),
+ oidcUser, mappedAuthorities, accessTokenResponse.getAccessToken(), accessTokenResponse.getRefreshToken());
+ result.setDetails(userDetails);
+ return result;
+ }
+
+ /**
+ * 获取扩展信息
+ *
+ * @param accessTokenResponse token 响应
+ * @param clientRegistration 客户端注册信息
+ * @return parameters·
+ */
+ private Map getAdditionalParameters(OAuth2AccessTokenResponse accessTokenResponse, ClientRegistration clientRegistration) {
+ Map additionalParameters = accessTokenResponse.getAdditionalParameters();
+ if (!additionalParameters.containsKey(OidcParameterNames.ID_TOKEN)) {
+ OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE,
+ "Missing (required) ID Token in Token Response for Client Registration: "
+ + clientRegistration.getRegistrationId(),
+ null);
+ throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString());
+ }
+ return additionalParameters;
+ }
+
+ /**
+ * 创建oidcToken
+ *
+ * @param clientRegistration 客户端注册信息
+ * @param accessTokenResponse token响应
+ * @return oidcToken
+ */
+ private OidcIdToken createOidcToken(ClientRegistration clientRegistration,
+ OAuth2AccessTokenResponse accessTokenResponse) {
+ JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
+ Jwt jwt = getJwt(accessTokenResponse, jwtDecoder);
+ return new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(),
+ jwt.getClaims());
+ }
+
+ /**
+ * 获取jwtToken
+ *
+ * @param accessTokenResponse token响应
+ * @param jwtDecoder jwt解析
+ * @return jwt
+ */
+ private Jwt getJwt(OAuth2AccessTokenResponse accessTokenResponse, JwtDecoder jwtDecoder) {
+ try {
+ Map parameters = accessTokenResponse.getAdditionalParameters();
+ return jwtDecoder.decode((String) parameters.get(OidcParameterNames.ID_TOKEN));
+ } catch (JwtException ex) {
+ OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, ex.getMessage(), null);
+ throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), ex);
+ }
+ }
+
+ private void validateNonce(String requestNonce, OidcIdToken idToken) {
+ if (requestNonce == null) {
+ return;
+ }
+ String nonceHash = getNonceHash(requestNonce);
+ String nonceHashClaim = idToken.getNonce();
+ if (nonceHashClaim == null || !nonceHashClaim.equals(nonceHash)) {
+ OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
+ throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
+ }
+ }
+
+ private String getNonceHash(String requestNonce) {
+ try {
+ return createHash(requestNonce);
+ } catch (NoSuchAlgorithmException ex) {
+ OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
+ throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
+ }
+ }
+
+ /**
+ * 获取token
+ *
+ * @param request 请求实体
+ * @return token
+ */
+ private OAuth2AccessTokenResponse getResponse(RequestEntity> request) {
+ try {
+ ResponseEntity tokenResponse = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
+ Assert.notNull(tokenResponse,
+ "The authorization server responded to this Authorization Code grant request with an empty body; as such, it cannot be materialized into an OAuth2AccessTokenResponse instance. Please check the HTTP response code in your server logs for more details.");
+ return tokenResponse.getBody();
+ } catch (RestClientException ex) {
+ OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
+ "An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: "
+ + ex.getMessage(),
+ null);
+ throw new OAuth2AuthorizationException(oauth2Error, ex);
+ }
+ }
+
+ /**
+ * 创建请求实体
+ *
+ * @param clientRegistration 客户端注册信息
+ * @return 请求实体
+ */
+ private RequestEntity> createRequestEntity(ClientRegistration clientRegistration) {
+ URI uri = UriComponentsBuilder
+ .fromUriString(clientRegistration.getProviderDetails().getTokenUri())
+ .build()
+ .toUri();
+ return new RequestEntity<>(this.createParameters(clientRegistration),
+ this.createHttpHeaders(clientRegistration), HttpMethod.POST, uri);
+ }
+
+ /**
+ * 获取请求头
+ *
+ * @param clientRegistration 客户端信息
+ * @return http请求头
+ */
+ private HttpHeaders createHttpHeaders(ClientRegistration clientRegistration) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setAccept(List.of(APPLICATION_JSON));
+ headers.setContentType(APPLICATION_FORM_URLENCODED_UTF8);
+ if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
+ String clientId = URLEncoder.encode(clientRegistration.getClientId(), StandardCharsets.UTF_8);
+ String clientSecret = URLEncoder.encode(clientRegistration.getClientSecret(), StandardCharsets.UTF_8);
+ headers.setBasicAuth(clientId, clientSecret);
+ }
+ return headers;
+ }
+
+ /**
+ * 设置参数
+ *
+ * @param clientRegistration 客户端注册信息
+ * @return 参数对象
+ */
+ private MultiValueMap createParameters(ClientRegistration clientRegistration) {
+ MultiValueMap parameters = new LinkedMultiValueMap<>();
+ parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue());
+ if (!ClientAuthenticationMethod.CLIENT_SECRET_BASIC
+ .equals(clientRegistration.getClientAuthenticationMethod())) {
+ parameters.set(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
+ }
+ if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientRegistration.getClientAuthenticationMethod())) {
+ parameters.set(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
+ }
+
+ String redirectUri = clientRegistration.getRedirectUri();
+ if (redirectUri != null) {
+ parameters.add(OAuth2ParameterNames.REDIRECT_URI, redirectUri);
+ }
+ return parameters;
+ }
+
+ @Override
+ public boolean supports(Class> authentication) {
+ return OidcAuthenticationToken.class.isAssignableFrom(authentication);
+ }
+
+}
diff --git a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/provider/oidc/OidcAuthenticationToken.java b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/provider/oidc/OidcAuthenticationToken.java
new file mode 100644
index 0000000..ab4a513
--- /dev/null
+++ b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/provider/oidc/OidcAuthenticationToken.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2024-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fuhouyu.framework.security.core.provider.oidc;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.lang.Nullable;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ *
+ * oidcToken
+ *
+ *
+ * @author fuhouyu
+ * @since 2024/11/4 20:27
+ */
+@Getter
+@Setter
+@ToString
+public class OidcAuthenticationToken extends AbstractAuthenticationToken {
+
+ private final String code;
+
+ private final String state;
+
+ private final String clientId;
+
+ private final transient OAuth2User principal;
+
+ private final OAuth2AccessToken accessToken;
+
+ private final OAuth2RefreshToken refreshToken;
+
+ private String nonce;
+
+ public OidcAuthenticationToken(String code, String state,
+ String clientId,
+ OAuth2User principal,
+ Collection extends GrantedAuthority> authorities,
+ OAuth2AccessToken accessToken,
+ @Nullable OAuth2RefreshToken refreshToken) {
+ super(authorities);
+ this.code = code;
+ this.state = state;
+ this.clientId = clientId;
+ this.principal = principal;
+ this.accessToken = accessToken;
+ this.refreshToken = refreshToken;
+ }
+
+
+ public OidcAuthenticationToken(String code, String state, String clientId) {
+ this(code, state, clientId, null, Collections.emptyList(), null, null);
+ }
+
+ @Override
+ public Object getCredentials() {
+ return null;
+ }
+
+ @Override
+ public OAuth2User getPrincipal() {
+ return this.principal;
+ }
+}
diff --git a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/authentication/refreshtoken/RefreshAuthenticationProvider.java b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/provider/refreshtoken/RefreshAuthenticationProvider.java
similarity index 97%
rename from base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/authentication/refreshtoken/RefreshAuthenticationProvider.java
rename to base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/provider/refreshtoken/RefreshAuthenticationProvider.java
index 4ef8292..bcb01c5 100644
--- a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/authentication/refreshtoken/RefreshAuthenticationProvider.java
+++ b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/core/provider/refreshtoken/RefreshAuthenticationProvider.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.fuhouyu.framework.security.core.authentication.refreshtoken;
+package com.fuhouyu.framework.security.core.provider.refreshtoken;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
diff --git a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/properties/OpenPlatformAuthProperties.java b/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/properties/OpenPlatformAuthProperties.java
deleted file mode 100644
index 929fb87..0000000
--- a/base-framework-security-starter/src/main/java/com/fuhouyu/framework/security/properties/OpenPlatformAuthProperties.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2024-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.fuhouyu.framework.security.properties;
-
-import com.fuhouyu.framework.common.constants.ConfigPropertiesConstant;
-import com.fuhouyu.framework.security.core.GrantTypeAuthenticationTokenEnum;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.ToString;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-import java.util.Map;
-
-/**
- *
- * 开放平台配置项
- *
- *
- * @author fuhouyu
- * @since 2024/8/15 12:39
- */
-@ConfigurationProperties(prefix = OpenPlatformAuthProperties.PREFIX)
-@ToString
-@Getter
-@Setter
-public class OpenPlatformAuthProperties {
-
- /**
- * 配置文件前缀
- */
- public static final String PREFIX = ConfigPropertiesConstant.PROPERTIES_PREFIX + "open-platform";
-
- /**
- * 客户端相关配置
- */
- private Map auth;
-
- /**
- * 授权的详情
- */
- @ToString
- @Getter
- @Setter
- public static class AuthDetail {
-
- /**
- * 客户端id
- */
- private String clientId;
-
- /**
- * 客户端密钥
- */
- private String clientSecret;
-
- /**
- * 登录的url,一般为换取token
- */
- private String loginUrl;
-
- /**
- * 用户详情的url
- */
- private String userInfoUrl;
-
- }
-}
diff --git a/base-framework-security-starter/src/main/java/module-info.java b/base-framework-security-starter/src/main/java/module-info.java
index 0d11b7b..124212d 100644
--- a/base-framework-security-starter/src/main/java/module-info.java
+++ b/base-framework-security-starter/src/main/java/module-info.java
@@ -29,14 +29,15 @@
requires spring.security.oauth2.core;
requires spring.data.redis;
requires spring.web;
+ requires spring.security.oauth2.client;
+ requires spring.security.oauth2.jose;
opens com.fuhouyu.framework.security to spring.core;
opens com.fuhouyu.framework.security.entity to com.esotericsoftware.kryo.kryo5;
- exports com.fuhouyu.framework.security.core.authentication.refreshtoken to com.fasterxml.jackson.databind;
+ exports com.fuhouyu.framework.security.core.provider.refreshtoken to com.fasterxml.jackson.databind;
exports com.fuhouyu.framework.security;
exports com.fuhouyu.framework.security.core;
exports com.fuhouyu.framework.security.entity;
- exports com.fuhouyu.framework.security.properties;
exports com.fuhouyu.framework.security.serializer;
exports com.fuhouyu.framework.security.token;
}
\ No newline at end of file
diff --git a/base-framework-security-starter/src/test/java/com/fuhouyu/framework/security/BaseComponent.java b/base-framework-security-starter/src/test/java/com/fuhouyu/framework/security/BaseComponent.java
index 68f5556..87e6512 100644
--- a/base-framework-security-starter/src/test/java/com/fuhouyu/framework/security/BaseComponent.java
+++ b/base-framework-security-starter/src/test/java/com/fuhouyu/framework/security/BaseComponent.java
@@ -15,7 +15,7 @@
*/
package com.fuhouyu.framework.security;
-import com.fuhouyu.framework.security.core.authentication.refreshtoken.RefreshAuthenticationProvider;
+import com.fuhouyu.framework.security.core.provider.refreshtoken.RefreshAuthenticationProvider;
import com.fuhouyu.framework.security.token.TokenStore;
import org.springframework.boot.test.context.TestComponent;
import org.springframework.context.annotation.Bean;
diff --git a/base-framework-security-starter/src/test/java/com/fuhouyu/framework/security/OidcProviderTest.java b/base-framework-security-starter/src/test/java/com/fuhouyu/framework/security/OidcProviderTest.java
new file mode 100644
index 0000000..0569f01
--- /dev/null
+++ b/base-framework-security-starter/src/test/java/com/fuhouyu/framework/security/OidcProviderTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fuhouyu.framework.security;
+
+import com.fuhouyu.framework.cache.CacheAutoConfiguration;
+import com.fuhouyu.framework.cache.CaffeineCacheConfiguration;
+import com.fuhouyu.framework.security.core.provider.oidc.OidcAuthenticationToken;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.test.context.TestPropertySource;
+
+/**
+ *
+ * 测试类
+ *
+ *
+ * @author fuhouyu
+ * @since 2024/11/4 22:12
+ */
+@SpringBootTest(classes = {
+ OAuth2ClientAutoConfiguration.class,
+ CacheAutoConfiguration.class,
+ CaffeineCacheConfiguration.class,
+ SecurityAutoConfiguration.class,
+ BaseComponent.class
+})
+@SpringBootApplication
+@Disabled
+@TestPropertySource(locations = {"classpath:application.yaml"})
+class OidcProviderTest {
+
+ private static final String CODE = "code";
+
+ private static final String STATE = "state";
+
+ @Autowired
+ private AuthenticationManager authenticationManager;
+
+ @Test
+ void testOidc() {
+ OidcAuthenticationToken oidcAuthenticationToken = new OidcAuthenticationToken(CODE, STATE, "gitlab");
+ authenticationManager.authenticate(oidcAuthenticationToken);
+ }
+}
diff --git a/base-framework-security-starter/src/test/resources/application.yaml b/base-framework-security-starter/src/test/resources/application.yaml
new file mode 100644
index 0000000..d700ca3
--- /dev/null
+++ b/base-framework-security-starter/src/test/resources/application.yaml
@@ -0,0 +1,25 @@
+base:
+ framework:
+ cache:
+ service:
+ cache-service-type: caffeine
+spring:
+ security:
+ oauth2:
+ client:
+ registration:
+ gitlab:
+ provider: gitlab
+ client-id: b8cfade18e2d4a264c768076e0fdb0492bcb35802a85e1ff6c73ebe16043a268
+ client-secret: gloas-1f0346b92b61a5d04f3a98df8abbe79ad380c500817011a9f06b3a47f6e0980c
+ authorization-grant-type: authorization_code
+ redirect-uri: http://127.0.0.1:8080/login/oauth2/code/login-client
+ scope: openid,profile
+ client-name: gitlab
+ provider:
+ gitlab:
+ authorization-uri: https://gitlab.com/oauth/authorize
+ token-uri: https://gitlab.com/oauth/token
+ user-info-uri: https://gitlab.com/oauth/userinfo
+ jwk-set-uri: https://gitlab.com/oauth/discovery/keys
+ user-name-attribute: email
\ No newline at end of file