This commit is contained in:
ran.shang
2025-06-11 09:42:13 +08:00
commit 7e38b8d971
1935 changed files with 132836 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
package com.newyetai.security.social.service;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import com.newyetai.security.ssb.SsbAuthenticationFilter;
import com.newyetai.usercenter.auth.constant.AuthTypeEnum;
public enum CommonOAuth2Provider {
WECHAT {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
builder.scope("snsapi_userinfo");
builder.authorizationUri("https://open.weixin.qq.com/connect/oauth2/authorize");
builder.tokenUri("https://api.weixin.qq.com/sns/oauth2/access_token");
builder.userInfoUri("https://api.weixin.qq.com/sns/userinfo");
builder.userNameAttributeName("openid");
builder.clientName(AuthTypeEnum.WECHAT.name());
builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
return builder;
}
},
SSBAPP {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
builder.authorizationUri("https://api.eshimin.com/newOauth/authorize");
builder.tokenUri("https://api.eshimin.com/newOauth/accessToken");
builder.userInfoUri("https://api.eshimin.com/newOauth/userInfo");
builder.clientName(AuthTypeEnum.SSB_APP.name());
builder.scope("read");
builder.redirectUriTemplate("{baseUrl}/" + SsbAuthenticationFilter.LOGIN_URI);
builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
return builder;
}
},
WECHAT_LITE {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
builder.clientName(AuthTypeEnum.WECHAT_LITE.name());
return builder;
}
},
ALIPAY {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.POST, DEFAULT_REDIRECT_URL);
builder.clientName(AuthTypeEnum.ALIPAY.name());
return builder;
}
},
ALIPAY_LITE {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
builder.clientName(AuthTypeEnum.ALIPAY_LITE.name());
return builder;
}
},
;
private static final String DEFAULT_REDIRECT_URL =
"{baseUrl}/{action}/oauth2/code/{registrationId}";
protected final ClientRegistration.Builder getBuilder(String registrationId,
ClientAuthenticationMethod method, String redirectUri) {
ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(registrationId);
builder.clientAuthenticationMethod(method);
builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
builder.redirectUriTemplate(redirectUri);
return builder;
}
/**
* Create a new
* {@link org.springframework.security.oauth2.client.registration.ClientRegistration.Builder
* ClientRegistration.Builder} pre-configured with provider defaults.
* @param registrationId the registration-id used with the new builder
* @return a builder instance
*/
public abstract ClientRegistration.Builder getBuilder(String registrationId);
}

View File

@@ -0,0 +1,66 @@
package com.newyetai.security.social.service;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.user.OAuth2User;
public interface IOauthProvider {
String getName();
/**
* 修改默认auth的请求地址
*
* @param request
* @param authorizationRequest
* @return
* @author sr on 2021-06-28
*/
default OAuth2AuthorizationRequest decorateRequest(HttpServletRequest request,
OAuth2AuthorizationRequest authorizationRequest) {
return authorizationRequest;
}
default OAuth2AccessTokenResponse getTokenResponse(
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
return null;
}
/**
* 获取token转换器(无则OAuth2AuthorizationCodeGrantRequestEntityConverter)
* @return
* @author sr on 2021-06-28
*/
default Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> getTokenConvert(){
return null;
}
/**
* 获取用户转换器(无则OAuth2UserRequestEntityConverter)
* @return
* @author sr on 2021-06-28
*/
default Converter<OAuth2UserRequest, RequestEntity<?>> getUserConvert(){
return null;
}
/**
* 处理第三方用户信息,获取登录用户
* @param userRequest
* @param userJsonStr
* @return
* @author sr on 2021-06-28
*/
default OAuth2User dealUser(OAuth2UserRequest userRequest, String userJsonStr) {
return null;
}
}

View File

@@ -0,0 +1,74 @@
package com.newyetai.security.social.service;
import java.util.Map;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.core.convert.ConversionException;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.util.StringUtils;
import com.google.common.collect.Maps;
import com.newyetai.common.exception.util.Assert;
import com.newyetai.common.util.JsonUtils;
import com.newyetai.usercenter.auth.constant.AuthTypeEnum;
import com.newyetai.usercenter.auth.dto.AuthClientDTO;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class OAuth2ClientRegistrationAdapter {
private static Map<String, CommonOAuth2Provider> providers = Maps.newConcurrentMap();
private OAuth2ClientRegistrationAdapter() {}
public static ClientRegistration getClientRegistration(String registrationId,
AuthTypeEnum authTypeEnum,
AuthClientDTO authClientDTO) {
Assert.isTrue(authTypeEnum != null, new IllegalArgumentException("非法授权标识"));
OAuth2ClientProperties.Registration registration =
JsonUtils.toObject(authClientDTO.getParamData(), OAuth2ClientProperties.Registration.class);
return getClientRegistration(registrationId, authTypeEnum, registration);
}
private static ClientRegistration getClientRegistration(String registrationId,
AuthTypeEnum authTypeEnum,
OAuth2ClientProperties.Registration registration) {
Assert.isTrue(authTypeEnum != null, new IllegalArgumentException("非法授权标识"));
Builder builder = getBuilder(authTypeEnum.name()).getBuilder(registrationId);
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(registration::getClientId).to(builder::clientId);
map.from(registration::getClientSecret).to(builder::clientSecret);
map.from(registration::getClientAuthenticationMethod).as(ClientAuthenticationMethod::new)
.to(builder::clientAuthenticationMethod);
map.from(registration::getAuthorizationGrantType).as(AuthorizationGrantType::new)
.to(builder::authorizationGrantType);
map.from(registration::getRedirectUri).to(builder::redirectUriTemplate);
map.from(registration::getScope).as((scope) -> StringUtils.toStringArray(scope))
.to(builder::scope);
map.from(registration::getClientName).to(builder::clientName);
return builder.build();
}
public static CommonOAuth2Provider getBuilder(String authTypeName) {
CommonOAuth2Provider provider = providers.get(authTypeName);
if (provider == null) {
try {
provider =
ApplicationConversionService.getSharedInstance().convert(authTypeName,
CommonOAuth2Provider.class);
providers.put(authTypeName, provider);
} catch (ConversionException ex) {
log.error("授权类型标识错误,未找到对应的oauth2提供者:{}", authTypeName);
throw new IllegalArgumentException("授权类型标识错误,未找到对应的oauth2提供者:" + authTypeName, ex);
}
}
return provider;
}
}

View File

@@ -0,0 +1,103 @@
package com.newyetai.security.social.service;
import java.util.Arrays;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequestEntityConverter;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import com.newyetai.security.social.util.Oauth2ProviderUtils;
/**
* code获取token
*
* @author sr on 2021-01-12
*/
public class StandardAuthorizationCodeTokenResponseClient
implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
public static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
public static final RestOperations restOperations;
private Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> requestEntityConverter =
new OAuth2AuthorizationCodeGrantRequestEntityConverter();
static {
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter(),
new StringHttpMessageConverter()));
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
restOperations = restTemplate;
}
@Override
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
Assert.notNull(authorizationCodeGrantRequest, "authorizationCodeGrantRequest cannot be null");
// 不同类型不同请求体
OAuth2AccessTokenResponse tokenResponse = Oauth2ProviderUtils
.getProvider(authorizationCodeGrantRequest.getClientRegistration().getClientName())
.getTokenResponse(authorizationCodeGrantRequest);
if (tokenResponse != null) {
return tokenResponse;
}
RequestEntity<?> request = this.requestEntityConverter.convert(authorizationCodeGrantRequest);
ResponseEntity<OAuth2AccessTokenResponse> response;
try {
response = restOperations.exchange(request, OAuth2AccessTokenResponse.class);
} 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);
}
tokenResponse = response.getBody();
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
// As per spec, in Section 5.1 Successful Access Token Response
// https://tools.ietf.org/html/rfc6749#section-5.1
// If AccessTokenResponse.scope is empty, then default to the scope
// originally requested by the client in the Token Request
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
.scopes(authorizationCodeGrantRequest.getClientRegistration().getScopes())
.build();
}
return tokenResponse;
}
/**
* Sets the {@link Converter} used for converting the {@link OAuth2AuthorizationCodeGrantRequest}
* to a {@link RequestEntity} representation of the OAuth 2.0 Access Token Request.
*
* @param requestEntityConverter the {@link Converter} used for converting to a {@link RequestEntity} representation of the Access Token Request
*/
public void setRequestEntityConverter(
Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> requestEntityConverter) {
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null");
this.requestEntityConverter = requestEntityConverter;
}
}

View File

@@ -0,0 +1,50 @@
package com.newyetai.security.social.service;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import com.newyetai.common.exception.util.Assert;
import com.newyetai.usercenter.auth.constant.AuthTypeEnum;
import com.newyetai.usercenter.auth.dto.AuthClientDTO;
import com.newyetai.usercenter.auth.dto.AuthClientQueryDTO;
import com.newyetai.usercenter.user.login.service.UserLoginService;
public class StandardClientRegistrationRepository implements ClientRegistrationRepository {
private UserLoginService userLoginService;
public StandardClientRegistrationRepository(UserLoginService userLoginService) {
this.userLoginService = userLoginService;
}
@Override
public ClientRegistration findByRegistrationId(String registrationId) {
Assert.isTrue(StringUtils.isNotBlank(registrationId),
new IllegalArgumentException("oauth授权注册标识不能为空"));
AuthClientDTO authClientDTO = userLoginService
.findAuthClientDTO(AuthClientQueryDTO.builder().id(Long.valueOf(registrationId)).build());
AuthTypeEnum authTypeEnum = AuthTypeEnum.getByCode(authClientDTO.getAuthTypeCode());
return OAuth2ClientRegistrationAdapter.getClientRegistration(authClientDTO.getId().toString(),
authTypeEnum, authClientDTO);
}
public ClientRegistration findByRegistrationId(String authTypeName, String domainOrIp) {
AuthTypeEnum authTypeEnum = AuthTypeEnum.valueOf(authTypeName.toUpperCase());
return this.findByRegistrationId(authTypeEnum, domainOrIp);
}
public ClientRegistration findByRegistrationId(Integer authTypeCode, String domainOrIp) {
AuthTypeEnum authTypeEnum = AuthTypeEnum.getByCode(authTypeCode);
return this.findByRegistrationId(authTypeEnum, domainOrIp);
}
private ClientRegistration findByRegistrationId(AuthTypeEnum authTypeEnum, String domainOrIp) {
AuthClientDTO authClientDTO = userLoginService.findAuthClientDTO(
AuthClientQueryDTO.builder().domainOrIp(domainOrIp).authTypeCode(authTypeEnum.getCode())
.build());
return OAuth2ClientRegistrationAdapter.getClientRegistration(authClientDTO.getId().toString(),
authTypeEnum, authClientDTO);
}
}

View File

@@ -0,0 +1,109 @@
package com.newyetai.security.social.service;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequestEntityConverter;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import com.newyetai.security.social.util.Oauth2ProviderUtils;
public class StandardOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private static final String MISSING_USER_INFO_URI_ERROR_CODE = "missing_user_info_uri";
private static final String MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE =
"missing_user_name_attribute";
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
private Converter<OAuth2UserRequest, RequestEntity<?>> requestEntityConverter =
new OAuth2UserRequestEntityConverter();
private RestOperations restOperations;
public StandardOAuth2UserService() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
this.restOperations = restTemplate;
}
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
Assert.notNull(userRequest, "userRequest cannot be null");
if (!StringUtils.hasText(
userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) {
OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_INFO_URI_ERROR_CODE,
"Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: "
+ userRequest.getClientRegistration().getRegistrationId(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
if (!StringUtils.hasText(userNameAttributeName)) {
OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE,
"Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: "
+ userRequest.getClientRegistration().getRegistrationId(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
IOauthProvider provider = Oauth2ProviderUtils
.getProvider(userRequest.getClientRegistration().getClientName());
// 不同类型不同请求体
Converter<OAuth2UserRequest, RequestEntity<?>> converter =
provider.getUserConvert();
if (converter == null) {
converter = requestEntityConverter;
}
// 获得request
RequestEntity<?> request = converter.convert(userRequest);
ResponseEntity<String> response;
try {
// 执行request
response = this.restOperations.exchange(request, String.class);
} catch (OAuth2AuthorizationException ex) {
OAuth2Error oauth2Error = ex.getError();
StringBuilder errorDetails = new StringBuilder();
errorDetails.append("Error details: [");
errorDetails.append("UserInfo Uri: ").append(
userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri());
errorDetails.append(", Error Code: ").append(oauth2Error.getErrorCode());
if (oauth2Error.getDescription() != null) {
errorDetails.append(", Error Description: ").append(oauth2Error.getDescription());
}
errorDetails.append("]");
oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the UserInfo Resource: "
+ errorDetails.toString(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
} catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the UserInfo Resource: "
+ ex.getMessage(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
}
// 解析response
String userAttributes = response.getBody();
return provider.dealUser(userRequest, userAttributes);
}
}

View File

@@ -0,0 +1,189 @@
package com.newyetai.security.social.service;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.util.UriComponentsBuilder;
import com.newyetai.common.util.RequestUtils;
import com.newyetai.security.social.util.Oauth2ProviderUtils;
import com.newyetai.usercenter.constants.SecurityConstants;
import com.newyetai.usercenter.user.constant.UserTypeEnum;
/**
* An implementation of an {@link OAuth2AuthorizationRequestResolver} that attempts to
* resolve an {@link OAuth2AuthorizationRequest} from the provided {@code HttpServletRequest}
* using the default request {@code URI} pattern {@code /oauth2/authorization/{registrationId}}.
*
* <p>
* <b>NOTE:</b> The default base {@code URI} {@code /oauth2/authorization} may be overridden
* via it's constructor {@link #DefaultOAuth2AuthorizationRequestResolver(ClientRegistrationRepository, String)}.
*
* @author Joe Grandja
* @author Rob Winch
* @since 5.1
* @see OAuth2AuthorizationRequestResolver
* @see OAuth2AuthorizationRequestRedirectFilter
*/
public final class StandardOauth2AuthorizationRequestResolver
implements OAuth2AuthorizationRequestResolver {
private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
private final ClientRegistrationRepository clientRegistrationRepository;
private final AntPathRequestMatcher authorizationRequestMatcher;
private final StringKeyGenerator stateGenerator =
new Base64StringKeyGenerator(Base64.getUrlEncoder());
/**
* Constructs a {@code DefaultOAuth2AuthorizationRequestResolver} using the provided parameters.
*
* @param clientRegistrationRepository the repository of client registrations
* @param authorizationRequestBaseUri the base {@code URI} used for resolving authorization requests
*/
public StandardOauth2AuthorizationRequestResolver(
// StandardClientRegistrationRepository standardClientRegistrationRepository,
ClientRegistrationRepository standardClientRegistrationRepository,
String authorizationRequestBaseUri) {
Assert.notNull(standardClientRegistrationRepository,
"clientRegistrationRepository cannot be null");
Assert.hasText(authorizationRequestBaseUri, "authorizationRequestBaseUri cannot be empty");
this.clientRegistrationRepository = standardClientRegistrationRepository;
this.authorizationRequestMatcher = new AntPathRequestMatcher(
authorizationRequestBaseUri + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}");
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
String registrationId = this.resolveRegistrationId(request);
String redirectUriAction = getAction(request, "login");
return resolve(request, registrationId, redirectUriAction);
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId) {
if (registrationId == null) {
return null;
}
String redirectUriAction = getAction(request, "authorize");
return resolve(request, registrationId, redirectUriAction);
}
private String getAction(HttpServletRequest request, String defaultAction) {
String action = request.getParameter("action");
if (action == null) {
return defaultAction;
}
return action;
}
private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId,
String redirectUriAction) {
if (registrationId == null) {
return null;
}
String domainOrIp = RequestUtils.getDomain(request);
ClientRegistration clientRegistration = null;
if (clientRegistrationRepository instanceof StandardClientRegistrationRepository) {
clientRegistration =
((StandardClientRegistrationRepository) this.clientRegistrationRepository)
.findByRegistrationId(registrationId, domainOrIp);
} else {
clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
}
if (clientRegistration == null) {
throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
}
OAuth2AuthorizationRequest.Builder builder;
if (AuthorizationGrantType.AUTHORIZATION_CODE
.equals(clientRegistration.getAuthorizationGrantType())) {
builder = OAuth2AuthorizationRequest.authorizationCode();
} else if (AuthorizationGrantType.IMPLICIT
.equals(clientRegistration.getAuthorizationGrantType())) {
builder = OAuth2AuthorizationRequest.implicit();
} else {
throw new IllegalArgumentException("Invalid Authorization Grant Type (" +
clientRegistration.getAuthorizationGrantType().getValue() +
") for Client Registration with Id: " + clientRegistration.getRegistrationId());
}
String redirectUriStr = this.expandRedirectUri(request, clientRegistration, redirectUriAction);
Map<String, Object> additionalParameters = new HashMap<>();
additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID,
clientRegistration.getRegistrationId());
String userType = request.getParameter(SecurityConstants.LOGIN_USER_TYPE_KEY);
String registerCode = request.getParameter(SecurityConstants.LOGIN_REGISTER_CODE_KEY);
String entrypoint = request.getParameter(SecurityConstants.LOGIN_ENTRYPOINT_KEY);
if (StringUtils.isBlank(userType)) {
userType = UserTypeEnum.PERSON.getType().toString();
}
additionalParameters.put(SecurityConstants.LOGIN_USER_TYPE_KEY, userType);
if (StringUtils.isNotBlank(registerCode)) {
additionalParameters.put(SecurityConstants.LOGIN_REGISTER_CODE_KEY, registerCode);
}
if (StringUtils.isNotBlank(entrypoint)) {
additionalParameters.put(SecurityConstants.LOGIN_ENTRYPOINT_KEY, entrypoint);
}
OAuth2AuthorizationRequest authorizationRequest = builder
.clientId(clientRegistration.getClientId())
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
.redirectUri(redirectUriStr)
.scopes(clientRegistration.getScopes())
.state(this.stateGenerator.generateKey())
.additionalParameters(additionalParameters)
.build();
// 不同授权需要额外修饰
IOauthProvider oauthProvider = Oauth2ProviderUtils.getProvider(registrationId);
if (oauthProvider != null) {
return oauthProvider.decorateRequest(request, authorizationRequest);
}
return authorizationRequest;
}
private String resolveRegistrationId(HttpServletRequest request) {
if (this.authorizationRequestMatcher.matches(request)) {
return this.authorizationRequestMatcher
.extractUriTemplateVariables(request).get(REGISTRATION_ID_URI_VARIABLE_NAME);
}
return null;
}
private String expandRedirectUri(HttpServletRequest request,
ClientRegistration clientRegistration, String action) {
// Supported URI variables -> baseUrl, action, registrationId
// Used in -> CommonOAuth2Provider.DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}"
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
String baseUrl = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replaceQuery(null)
.replacePath(request.getContextPath())
.build()
.toUriString();
uriVariables.put("baseUrl", baseUrl);
if (action != null) {
uriVariables.put("action", action);
}
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUriTemplate())
.buildAndExpand(uriVariables)
.toUriString();
}
}

View File

@@ -0,0 +1,67 @@
package com.newyetai.security.social.ssb.service;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.stereotype.Service;
import com.newyetai.sdk.ssb.SsbProperties;
import com.newyetai.sdk.ssb.util.SM2Utils;
import com.newyetai.security.social.service.IOauthProvider;
import com.newyetai.security.util.SecurityUtils;
import com.newyetai.usercenter.auth.constant.AuthTypeEnum;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class SsbOauthLoginProvider implements IOauthProvider {
private static final String AUTH_URL =
"https://api.eshimin.com/newOauth/authorize?client_id=%s&response_type=authorization_code&scope=read&redirect_uri=%s&timestamp=%s&sign=%s&state=%s";
@Autowired
private SsbProperties ssbProperties;
@Override
public String getName() {
return AuthTypeEnum.SSB_APP.name();
}
@Override
public OAuth2AuthorizationRequest decorateRequest(HttpServletRequest request,
OAuth2AuthorizationRequest authorizationRequest) {
String timestamp = String.valueOf(System.currentTimeMillis());
String sign = SM2Utils.sign(ssbProperties.getSm2PrivateKey(),
ssbProperties.getAppKey() + ssbProperties.getAppSecret() + timestamp,
ssbProperties.getAppKey());
// 指定成功跳转地址
String redirectUrl =
SecurityUtils.dealRedirectUrl(request, authorizationRequest.getRedirectUri());
String state = "123";
// 微信Authorization Request URL
String authorizationRequestUri =
String.format(AUTH_URL,
authorizationRequest.getClientId(),
SecurityUtils.encodeURL(redirectUrl),
timestamp,
sign,
state);
return OAuth2AuthorizationRequest.from(authorizationRequest)
.authorizationRequestUri(authorizationRequestUri)
.build();
}
}

View File

@@ -0,0 +1,26 @@
package com.newyetai.security.social.util;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections4.map.CaseInsensitiveMap;
import org.springframework.stereotype.Component;
import com.newyetai.common.exception.util.Assert;
import com.newyetai.security.social.service.IOauthProvider;
@Component
public class Oauth2ProviderUtils {
private static Map<String, IOauthProvider> providerMap = new CaseInsensitiveMap<String, IOauthProvider>();
private Oauth2ProviderUtils(List<IOauthProvider> providers) {
providers.forEach(provider -> providerMap.put(provider.getName(), provider));
}
public static IOauthProvider getProvider(String providerName) {
IOauthProvider provider = providerMap.get(providerName);
return provider;
}
}

View File

@@ -0,0 +1,20 @@
package com.newyetai.security.social.wechat.constant;
/**
* @author sr on 2021-01-13
*/
public interface WechatConstants {
/** 授权code获取token **/
String TOKEN_URL_FORMAT =
"https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=%s";
/** openId换取用户信息 **/
String USER_URL_FORMAT =
"https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=%s";
/** 调起微信授权地址 **/
String AUTH_URL_FORMAT =
"https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=%s&scope=%s&state=%s#wechat_redirect";
}

View File

@@ -0,0 +1,57 @@
package com.newyetai.security.social.wechat.convert;
import java.net.URI;
import java.util.Collections;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.web.util.UriComponentsBuilder;
import com.newyetai.security.social.wechat.constant.WechatConstants;
import lombok.extern.slf4j.Slf4j;
/**
* code换token
*
* @author sr on 2021-01-12
*/
@Slf4j
public class WeChatAuthorizationCodeGrantRequestEntityConverter
implements Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> {
@Override
public RequestEntity<?> convert(
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
HttpHeaders headers = getTokenRequestHeaders();
URI uri = buildUri(authorizationCodeGrantRequest);
return new RequestEntity<>(headers, HttpMethod.GET, uri);
}
private HttpHeaders getTokenRequestHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.TEXT_PLAIN));
return headers;
}
private URI buildUri(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
String appid = clientRegistration.getClientId();
String secret = clientRegistration.getClientSecret();
String code = authorizationCodeGrantRequest.getAuthorizationExchange()
.getAuthorizationResponse().getCode();
String grantType = authorizationCodeGrantRequest.getGrantType().getValue();
String uriString =
String.format(WechatConstants.TOKEN_URL_FORMAT,
appid, secret, code, grantType);
return UriComponentsBuilder.fromUriString(uriString).build().toUri();
}
}

View File

@@ -0,0 +1,44 @@
package com.newyetai.security.social.wechat.convert;
import java.net.URI;
import java.util.Collections;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.web.util.UriComponentsBuilder;
import com.newyetai.security.social.wechat.constant.WechatConstants;
public class WeChatUserRequestEntityConverter
implements Converter<OAuth2UserRequest, RequestEntity<?>> {
@Override
public RequestEntity<?> convert(OAuth2UserRequest userRequest) {
HttpHeaders headers = getUserRequestHeaders();
URI uri = buildUri(userRequest);
return new RequestEntity<>(headers, HttpMethod.GET, uri);
}
private HttpHeaders getUserRequestHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.TEXT_PLAIN));
return headers;
}
private URI buildUri(OAuth2UserRequest userRequest) {
ClientRegistration clientRegistration = userRequest.getClientRegistration();
String uri = clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri();
String accessToken = userRequest.getAccessToken().getTokenValue();
String openId = (String) userRequest.getAdditionalParameters().get("openid");
String userInfoUrl =
String.format(WechatConstants.USER_URL_FORMAT,
accessToken, openId, "zh_CN");
return UriComponentsBuilder.fromUriString(userInfoUrl).build().toUri();
}
}

View File

@@ -0,0 +1,70 @@
package com.newyetai.security.social.wechat.service;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.newyetai.common.util.JsonUtils;
import com.newyetai.security.social.service.IOauthProvider;
import com.newyetai.usercenter.constants.SecurityConstants;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@Getter
@Setter
@Slf4j
public class WeChatAccessTokenResponse {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("expires_in")
private Long expiresIn;
@JsonProperty("refresh_token")
private String refreshToken;
private String openid;
private String scope;
private WeChatAccessTokenResponse() {
}
public static WeChatAccessTokenResponse build(String json) {
try {
return JsonUtils.toObject(json, WeChatAccessTokenResponse.class);
} catch (Exception e) {
log.error("An error occurred while attempting to parse the WeiXin Access Token Response: "
+ e.getMessage());
return null;
}
}
public OAuth2AccessTokenResponse toOAuth2AccessTokenResponse(
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken);
builder.tokenType(OAuth2AccessToken.TokenType.BEARER);
builder.expiresIn(expiresIn);
builder.refreshToken(refreshToken);
String[] scopes = scope.split(",");
Set<String> scopeSet = new HashSet<>();
Collections.addAll(scopeSet, scopes);
builder.scopes(scopeSet);
Map<String, Object> additionalParameters = new LinkedHashMap<>();
additionalParameters.put("openid", openid);
Map<String, Object> originParam = authorizationCodeGrantRequest.getAuthorizationExchange().getAuthorizationRequest()
.getAdditionalParameters();
additionalParameters.putAll(originParam);
builder.additionalParameters(additionalParameters);
return builder.build();
}
}

View File

@@ -0,0 +1,157 @@
package com.newyetai.security.social.wechat.service;
import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.client.RestClientException;
import com.newyetai.common.util.JsonUtils;
import com.newyetai.security.mapstruct.AuthDetailMapMapper;
import com.newyetai.security.mapstruct.UserMapMapper;
import com.newyetai.security.social.service.IOauthProvider;
import com.newyetai.security.social.service.StandardAuthorizationCodeTokenResponseClient;
import com.newyetai.security.social.wechat.constant.WechatConstants;
import com.newyetai.security.social.wechat.convert.WeChatAuthorizationCodeGrantRequestEntityConverter;
import com.newyetai.security.social.wechat.convert.WeChatUserRequestEntityConverter;
import com.newyetai.security.util.SecurityUtils;
import com.newyetai.usercenter.auth.constant.AuthTypeEnum;
import com.newyetai.usercenter.auth.dto.AuthDetailLoginDTO;
import com.newyetai.usercenter.auth.wechat.dto.WechatAuthUserInfoDTO;
import com.newyetai.usercenter.constants.SecurityConstants;
import com.newyetai.usercenter.user.login.dto.UserDetailDTO;
import com.newyetai.usercenter.user.login.service.UserLoginService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class WechatOauthLoginProvider implements IOauthProvider {
private Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> tokenConverter =
new WeChatAuthorizationCodeGrantRequestEntityConverter();
private Converter<OAuth2UserRequest, RequestEntity<?>> userConverter =
new WeChatUserRequestEntityConverter();
private UserLoginService userLoginService;
public WechatOauthLoginProvider(UserLoginService userLoginService) {
this.userLoginService = userLoginService;
}
@Override
public String getName() {
return AuthTypeEnum.WECHAT.name();
}
@Override
public OAuth2AuthorizationRequest decorateRequest(HttpServletRequest request,
OAuth2AuthorizationRequest authorizationRequest) {
// 指定成功跳转地址
String redirectUrl =
SecurityUtils.dealRedirectUrl(request, authorizationRequest.getRedirectUri());
// 微信Authorization Request URL
String authorizationRequestUri =
String.format(WechatConstants.AUTH_URL_FORMAT,
authorizationRequest.getClientId(),
SecurityUtils.encodeURL(redirectUrl),
OAuth2AuthorizationResponseType.CODE.getValue(), getScope(authorizationRequest),
authorizationRequest.getState());
return OAuth2AuthorizationRequest.from(authorizationRequest)
.authorizationRequestUri(authorizationRequestUri)
.build();
}
private static String getScope(OAuth2AuthorizationRequest authorizationRequest) {
return authorizationRequest.getScopes().stream().findFirst().orElse("snsapi_userinfo");
}
@Override
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
// 调用WeChatAuthorizationCodeGrantRequestEntityConverter获取request
RequestEntity<?> request = this.tokenConverter.convert(authorizationCodeGrantRequest);
ResponseEntity<String> response;
try {
// 执行request
response = StandardAuthorizationCodeTokenResponseClient.restOperations.exchange(request,
String.class);
} catch (RestClientException ex) {
String description =
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: ";
log.error(description, ex);
OAuth2Error oauth2Error =
new OAuth2Error(
StandardAuthorizationCodeTokenResponseClient.INVALID_TOKEN_RESPONSE_ERROR_CODE,
description + ex.getMessage(), null);
throw new OAuth2AuthorizationException(oauth2Error, ex);
}
// 解析response
OAuth2AccessTokenResponse tokenResponse =
WeChatAccessTokenResponse.build(response.getBody()).toOAuth2AccessTokenResponse(authorizationCodeGrantRequest);
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
.scopes(authorizationCodeGrantRequest.getClientRegistration().getScopes())
.build();
}
return tokenResponse;
}
@Override
public Converter<OAuth2UserRequest, RequestEntity<?>> getUserConvert() {
return userConverter;
}
@Override
public OAuth2User dealUser(OAuth2UserRequest userRequest, String userJsonStr) {
WechatAuthUserInfoDTO wechatUserInfoDTO = null;
try {
// 编码转换
userJsonStr = new String(userJsonStr.getBytes("ISO-8859-1"), "UTF-8");
wechatUserInfoDTO = JsonUtils.toObject(userJsonStr, WechatAuthUserInfoDTO.class);
} catch (UnsupportedEncodingException e) {
log.error("An error occurred while attempting to encode userAttributes: " + e.getMessage());
}
AuthDetailLoginDTO authDetailLoginDTO = AuthDetailMapMapper.INSTANCE.toDto(wechatUserInfoDTO);
Integer userType = MapUtils.getInteger(userRequest.getAdditionalParameters(),
SecurityConstants.LOGIN_USER_TYPE_KEY);
String registerCode = MapUtils.getString(userRequest.getAdditionalParameters(),
SecurityConstants.LOGIN_REGISTER_CODE_KEY);
String entrypoint = MapUtils.getString(userRequest.getAdditionalParameters(),
SecurityConstants.LOGIN_ENTRYPOINT_KEY);
authDetailLoginDTO
.setUserType(userType);
authDetailLoginDTO
.setAuthClientId(Long.valueOf(userRequest.getClientRegistration().getRegistrationId()));
authDetailLoginDTO.setAuthTypeCode(AuthTypeEnum.WECHAT.getCode());
authDetailLoginDTO.setRegisterCode(registerCode);
authDetailLoginDTO.setEntrypoint(entrypoint);
UserDetailDTO userDetailDTO = userLoginService.findUserDetailDTO(authDetailLoginDTO);
return UserMapMapper.INSTANCE.dtoToDto(userDetailDTO);
}
}