maxkey-authentications\maxkey-authentication-social -> maxkey-starter\maxkey-starter-social

This commit is contained in:
MaxKey
2024-10-21 07:32:44 +08:00
parent 8b209be4c4
commit d2516e6933
22 changed files with 10 additions and 11 deletions

View File

@@ -0,0 +1,15 @@
description = "maxkey-starter-social"
dependencies {
//local jars
implementation fileTree(dir: '../maxkey-lib/', include: '*/*.jar')
implementation project(":maxkey-common")
implementation project(":maxkey-core")
implementation project(":maxkey-persistence")
implementation project(":maxkey-authentications:maxkey-authentication-core")
implementation project(":maxkey-authentications:maxkey-authentication-provider")
}

View File

@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Class-Path:

View File

@@ -0,0 +1,51 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 me.zhyd.oauth.config;
import me.zhyd.oauth.request.AuthDefaultRequest;
import me.zhyd.oauth.request.AuthFeishu2Request;
public enum AuthMxkDefaultSource implements AuthSource {
FEISHU2 {
@Override
public String authorize() {
return "https://passport.feishu.cn/suite/passport/oauth/authorize";
}
@Override
public String accessToken() {
return "https://passport.feishu.cn/suite/passport/oauth/token";
}
@Override
public String userInfo() {
return "https://passport.feishu.cn/suite/passport/oauth/userinfo";
}
@Override
public String refresh() {
return "https://passport.feishu.cn/suite/passport/oauth/token";
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthFeishu2Request.class;
}
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 me.zhyd.oauth.request;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xkcoding.http.support.HttpHeader;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthMxkDefaultSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.GlobalAuthUtils;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.StringUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
* 飞书平台,企业自建应用授权登录,
* https://open.feishu.cn/document/common-capabilities/sso/web-application-sso/web-app-overview
* <p>
* 所以,最终修改该平台的实际发布版本为 支持扫码登录
*
* @author beacon
* @author yadong.zhang (yadong.zhang0415(a)gmail.com) 重构业务逻辑 20210101
* @author maxkey 重构业务逻辑 20220216
* @since 1.15.9
*/
public class AuthFeishu2Request extends AuthDefaultRequest {
public AuthFeishu2Request(AuthConfig config) {
super(config, AuthMxkDefaultSource.FEISHU2);
}
public AuthFeishu2Request(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthMxkDefaultSource.FEISHU2, authStateCache);
}
/**
* 获取 app_access_token企业自建应用
* <p>
* Token 有效期为 2 小时,在此期间调用该接口 token 不会改变。当 token 有效期小于 30 分的时候,再次请求获取 token 的时候,
* 会生成一个新的 token与此同时老的 token 依然有效。
*
* @return appAccessToken
*/
private String getAppAccessToken() {
String cacheKey = this.source.getName().concat(":app_access_token:").concat(config.getClientId());
String cacheAppAccessToken = this.authStateCache.get(cacheKey);
if (StringUtils.isNotEmpty(cacheAppAccessToken)) {
return cacheAppAccessToken;
}
String url = "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal/";
JSONObject requestObject = new JSONObject();
requestObject.put("app_id", config.getClientId());
requestObject.put("app_secret", config.getClientSecret());
String response = new HttpUtils(config.getHttpConfig()).post(url, requestObject.toJSONString(), new HttpHeader()
.add("Content-Type", "application/json")).getBody();
JSONObject jsonObject = JSON.parseObject(response);
this.checkResponse(jsonObject);
String appAccessToken = jsonObject.getString("app_access_token");
// 缓存 app access token
this.authStateCache.cache(cacheKey, appAccessToken, jsonObject.getLongValue("expire") * 1000);
return appAccessToken;
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
JSONObject requestObject = new JSONObject();
requestObject.put("app_access_token", this.getAppAccessToken());
requestObject.put("grant_type", "authorization_code");
requestObject.put("client_id", config.getClientId());
requestObject.put("client_secret", config.getClientSecret());
requestObject.put("redirect_uri", config.getRedirectUri());
requestObject.put("code", authCallback.getCode());
return getToken(requestObject, this.source.accessToken());
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String accessToken = authToken.getAccessToken();
String response = new HttpUtils(config.getHttpConfig()).get(source.userInfo(), null, new HttpHeader()
.add("Content-Type", "application/json")
.add("Authorization", "Bearer " + accessToken), false).getBody();
JSONObject object = JSON.parseObject(response);
this.checkResponse(object);
JSONObject data = object;//.getJSONObject("data");
return AuthUser.builder()
.rawUserInfo(object)
.uuid(data.getString("union_id"))
.username(data.getString("name"))
.nickname(data.getString("name"))
.avatar(data.getString("avatar_url"))
.email(data.getString("email"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source.toString())
.build();
}
@Override
public AuthResponse refresh(AuthToken authToken) {
JSONObject requestObject = new JSONObject();
requestObject.put("app_access_token", this.getAppAccessToken());
requestObject.put("grant_type", "refresh_token");
requestObject.put("refresh_token", authToken.getRefreshToken());
return AuthResponse.builder()
.code(AuthResponseStatus.SUCCESS.getCode())
.data(getToken(requestObject, this.source.refresh()))
.build();
}
private AuthToken getToken(JSONObject param, String url) {
String response = new HttpUtils(config.getHttpConfig()).post(url, param.toJSONString(), new HttpHeader()
.add("Content-Type", "application/json")).getBody();
JSONObject jsonObject = JSON.parseObject(response);
this.checkResponse(jsonObject);
JSONObject data = jsonObject;//.getJSONObject("data");
return AuthToken.builder()
.accessToken(data.getString("access_token"))
.refreshToken(data.getString("refresh_token"))
.expireIn(data.getIntValue("expires_in"))
.tokenType(data.getString("token_type"))
.openId(data.getString("open_id"))
.build();
}
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("client_id", config.getClientId())
.queryParam("redirect_uri", GlobalAuthUtils.urlEncode(config.getRedirectUri()))
.queryParam("response_type", "code")
.queryParam("state", getRealState(state))
.build();
}
/**
* 校验响应内容是否正确
*
* @param jsonObject 响应内容
*/
private void checkResponse(JSONObject jsonObject) {
if (jsonObject.getIntValue("code") != 0) {
throw new AuthException(jsonObject.getString("message"));
}
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 me.zhyd.oauth.request;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.enums.scope.AuthHuaweiScope;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.AuthScopeUtils;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import java.util.HashMap;
import java.util.Map;
import static me.zhyd.oauth.enums.AuthResponseStatus.SUCCESS;
public class AuthHuaweiWeLinkRequest extends AuthDefaultRequest {
public AuthHuaweiWeLinkRequest(AuthConfig config) {
super(config, WeLinkAuthDefaultSource.HUAWEI_WELINK);
}
public AuthHuaweiWeLinkRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, WeLinkAuthDefaultSource.HUAWEI_WELINK, authStateCache);
}
/**
* 获取access token
*
* @param authCallback 授权成功后的回调参数
* @return token
* @see AuthDefaultRequest#authorize()
* @see AuthDefaultRequest#authorize(String)
*/
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
Map<String, String> form = new HashMap<>(8);
form.put("grant_type", "authorization_code");
form.put("code", authCallback.getAuthorization_code());
form.put("client_id", config.getClientId());
form.put("client_secret", config.getClientSecret());
form.put("redirect_uri", config.getRedirectUri());
String response = new HttpUtils(config.getHttpConfig()).post(source.accessToken(), form, false).getBody();
return getAuthToken(response);
}
/**
* 使用token换取用户信息
*
* @param authToken token信息
* @return 用户信息
* @see AuthDefaultRequest#getAccessToken(AuthCallback)
*/
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
Map<String, String> form = new HashMap<>(7);
form.put("nsp_ts", System.currentTimeMillis() + "");
form.put("access_token", authToken.getAccessToken());
form.put("nsp_fmt", "JS");
form.put("nsp_svc", "OpenUP.User.getInfo");
String response = new HttpUtils(config.getHttpConfig()).post(source.userInfo(), form, false).getBody();
JSONObject object = JSONObject.parseObject(response);
this.checkResponse(object);
AuthUserGender gender = getRealGender(object);
return AuthUser.builder()
.rawUserInfo(object)
.uuid(object.getString("userId"))
.username(object.getString("userNameCn"))
.nickname(object.getString("userNameCn"))
.gender(gender)
//.avatar(object.getString("headPictureURL"))
.token(authToken)
.source(source.toString())
.build();
}
/**
* 刷新access token (续期)
*
* @param authToken 登录成功后返回的Token信息
* @return AuthResponse
*/
@Override
public AuthResponse refresh(AuthToken authToken) {
Map<String, String> form = new HashMap<>(7);
form.put("client_id", config.getClientId());
form.put("client_secret", config.getClientSecret());
form.put("refresh_token", authToken.getRefreshToken());
form.put("grant_type", "refresh_token");
String response = new HttpUtils(config.getHttpConfig()).post(source.refresh(), form, false).getBody();
return AuthResponse.builder().code(SUCCESS.getCode()).data(getAuthToken(response)).build();
}
private AuthToken getAuthToken(String response) {
JSONObject object = JSONObject.parseObject(response);
this.checkResponse(object);
return AuthToken.builder()
.accessToken(object.getString("access_token"))
.expireIn(object.getIntValue("expires_in"))
.refreshToken(object.getString("refresh_token"))
.build();
}
/**
* 返回带{@code state}参数的授权url授权回调时会带上这个{@code state}
*
* @param state state 验证授权流程的参数可以防止csrf
* @return 返回授权地址
* @since 1.9.3
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(super.authorize(state))
.queryParam("access_type", "offline")
.queryParam("scope", this.getScopes(" ", true, AuthScopeUtils.getDefaultScopes(AuthHuaweiScope.values())))
.build();
}
/**
* 返回获取userInfo的url
*
* @param authToken token
* @return 返回获取userInfo的url
*/
@Override
protected String userInfoUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.userInfo())
.queryParam("nsp_ts", System.currentTimeMillis())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("nsp_fmt", "JS")
.queryParam("nsp_svc", "OpenUP.User.getInfo")
.build();
}
/**
* 获取用户的实际性别。华为系统中用户的性别1表示女0表示男
*
* @param object obj
* @return AuthUserGender
*/
private AuthUserGender getRealGender(JSONObject object) {
int genderCodeInt = object.getIntValue("gender");
String genderCode = genderCodeInt == 1 ? "0" : (genderCodeInt == 0) ? "1" : genderCodeInt + "";
return AuthUserGender.getRealGender(genderCode);
}
/**
* 校验响应结果
*
* @param object 接口返回的结果
*/
private void checkResponse(JSONObject object) {
if (object.containsKey("NSP_STATUS")) {
throw new AuthException(object.getString("error"));
}
if (object.containsKey("error")) {
throw new AuthException(object.getString("sub_error") + ":" + object.getString("error_description"));
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 me.zhyd.oauth.request;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
public class AuthMaxkeyRequest extends AuthDefaultRequest {
public static final String KEY = "maxkey";
public AuthMaxkeyRequest(AuthConfig config) {
super(config, WeLinkAuthDefaultSource.HUAWEI_WELINK);
}
public AuthMaxkeyRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, MaxkeyAuthDefaultSource.MAXKEY, authStateCache);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
return null;
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
return null;
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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.
*/
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package me.zhyd.oauth.request;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.enums.scope.AuthWeChatEnterpriseWebScope;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.AuthScopeUtils;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AuthWeChatEnterpriseWebRequestCost extends AbstractAuthWeChatEnterpriseRequest {
static final Logger _logger = LoggerFactory.getLogger(AuthWeChatEnterpriseWebRequestCost.class);
public AuthWeChatEnterpriseWebRequestCost(AuthConfig config) {
super(config, AuthDefaultSource.WECHAT_ENTERPRISE_WEB);
}
public AuthWeChatEnterpriseWebRequestCost(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthDefaultSource.WECHAT_ENTERPRISE_WEB, authStateCache);
}
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(this.source.authorize()).queryParam("appid", this.config.getClientId()).queryParam("redirect_uri", this.config.getRedirectUri()).queryParam("response_type", "code").queryParam("scope", this.getScopes(",", false, AuthScopeUtils.getDefaultScopes(AuthWeChatEnterpriseWebScope.values()))).queryParam("state", this.getRealState(state).concat("#wechat_redirect")).build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String response = this.doGetUserInfo(authToken);
JSONObject object = this.checkResponse(response);
if (!object.containsKey("UserId")) {
throw new AuthException(AuthResponseStatus.UNIDENTIFIED_PLATFORM, this.source);
} else {
String userId = object.getString("UserId");
if (StringUtils.isEmpty(userId)) {
userId = object.getString("userid");
if (StringUtils.isEmpty(userId)) {
//如果还是空,则异常
throw new AuthException(AuthResponseStatus.UNIDENTIFIED_PLATFORM, this.source);
}
}
_logger.debug("get userid:{}",userId);
//根据userid判断是否是上下游的企业微信扫码下游企业微信扫码返回userid是企业id/用户id,无法获取用户详情会报错400058
if (userId.indexOf("/") == -1) {
try {
String userDetailResponse = this.getUserDetail(authToken.getAccessToken(), userId);
JSONObject userDetail = this.checkResponse(userDetailResponse);
return AuthUser.builder().rawUserInfo(userDetail).username(userDetail.getString("name")).nickname(userDetail.getString("alias")).avatar(userDetail.getString("avatar")).location(userDetail.getString("address")).email(userDetail.getString("email")).uuid(userId).gender(AuthUserGender.getWechatRealGender(userDetail.getString("gender"))).token(authToken).source(this.source.toString()).build();
}catch (Exception e){
_logger.error("get userDetail error:{}",e.getMessage());
}
}
return AuthUser.builder().uuid(userId).build();
}
}
private String getUserDetail(String accessToken, String userId) {
String userDetailUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/user/get").queryParam("access_token", accessToken).queryParam("userid", userId).build();
return (new HttpUtils(this.config.getHttpConfig())).get(userDetailUrl).getBody();
}
private JSONObject checkResponse(String response) {
JSONObject object = JSONObject.parseObject(response);
if (object.containsKey("errcode") && object.getIntValue("errcode") != 0) {
throw new AuthException(object.getString("errmsg"), this.source);
} else {
return object;
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 me.zhyd.oauth.request;
import me.zhyd.oauth.config.AuthSource;
public enum MaxkeyAuthDefaultSource implements AuthSource{
MAXKEY {
@Override
public String authorize() {
return "https://login.welink.huaweicloud.com/connect/oauth2/sns_authorize";
}
@Override
public String accessToken() {
return "https://open.welink.huaweicloud.com/api/auth/v2/tickets";
}
@Override
public String userInfo() {
return "https://open.welink.huaweicloud.com/api/contact/v1/users";
}
@Override
public String refresh() {
return "";
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthHuaweiWeLinkRequest.class;
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 me.zhyd.oauth.request;
import me.zhyd.oauth.config.AuthSource;
public enum WeLinkAuthDefaultSource implements AuthSource{
HUAWEI_WELINK {
@Override
public String authorize() {
return "https://login.welink.huaweicloud.com/connect/oauth2/sns_authorize";
}
@Override
public String accessToken() {
return "https://open.welink.huaweicloud.com/api/auth/v2/tickets";
}
@Override
public String userInfo() {
return "https://open.welink.huaweicloud.com/api/contact/v1/users";
}
@Override
public String refresh() {
return "";
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthHuaweiWeLinkRequest.class;
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 org.dromara.maxkey.authn.support.socialsignon;
import org.dromara.maxkey.authn.jwt.AuthTokenService;
import org.dromara.maxkey.authn.provider.AbstractAuthenticationProvider;
import org.dromara.maxkey.authn.support.socialsignon.service.SocialSignOnProviderService;
import org.dromara.maxkey.authn.support.socialsignon.service.SocialsAssociateService;
import org.dromara.maxkey.configuration.ApplicationConfig;
import org.dromara.maxkey.entity.SocialsAssociate;
import org.dromara.maxkey.entity.SocialsProvider;
import org.dromara.maxkey.web.WebContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.request.AuthRequest;
/**
* @author Crystal.Sea
*
*/
public class AbstractSocialSignOnEndpoint {
static final Logger _logger = LoggerFactory.getLogger(AbstractSocialSignOnEndpoint.class);
protected AuthRequest authRequest;
protected String accountJsonString;
@Autowired
protected SocialSignOnProviderService socialSignOnProviderService;
@Autowired
protected SocialsAssociateService socialsAssociateService;
@Autowired
@Qualifier("authenticationProvider")
AbstractAuthenticationProvider authenticationProvider ;
@Autowired
AuthTokenService authTokenService;
@Autowired
ApplicationConfig applicationConfig;
protected AuthRequest buildAuthRequest(String instId,String provider,String baseUrl){
try {
SocialsProvider socialSignOnProvider = socialSignOnProviderService.get(instId,provider);
_logger.debug("socialSignOn Provider : "+socialSignOnProvider);
if(socialSignOnProvider != null){
authRequest = socialSignOnProviderService.getAuthRequest(instId,provider,baseUrl);
return authRequest;
}
}catch(Exception e) {
_logger.debug("buildAuthRequest Exception ",e);
}
return null;
}
protected SocialsAssociate authCallback(String instId,String provider,String baseUrl) throws Exception {
SocialsAssociate socialsAssociate = null;
AuthCallback authCallback=new AuthCallback();
authCallback.setCode(WebContext.getRequest().getParameter("code"));
authCallback.setAuth_code(WebContext.getRequest().getParameter("auth_code"));
authCallback.setOauth_token(WebContext.getRequest().getParameter("oauthToken"));
authCallback.setAuthorization_code(WebContext.getRequest().getParameter("authorization_code"));
authCallback.setOauth_verifier(WebContext.getRequest().getParameter("oauthVerifier"));
authCallback.setState(WebContext.getRequest().getParameter("state"));
_logger.debug("Callback OAuth code {}, auth_code {}, oauthToken {}, authorization_code {}, oauthVerifier {} , state {}",
authCallback.getCode(),
authCallback.getAuth_code(),
authCallback.getOauth_token(),
authCallback.getAuthorization_code(),
authCallback.getOauth_verifier(),
authCallback.getState());
if(authRequest == null) {//if authRequest is null renew one
authRequest=socialSignOnProviderService.getAuthRequest(instId,provider,baseUrl);
_logger.debug("session authRequest is null , renew one");
}
//State time out, re set
if(authCallback.getState() != null) {
authRequest.authorize(WebContext.getRequest().getSession().getId());
}
AuthResponse<?> authResponse=authRequest.login(authCallback);
_logger.debug("Response : " + authResponse.getData());
socialsAssociate =new SocialsAssociate();
socialsAssociate.setProvider(provider);
socialsAssociate.setSocialUserId(
socialSignOnProviderService.getAccountId(provider, authResponse));
socialsAssociate.setInstId(instId);
return socialsAssociate;
}
}

View File

@@ -0,0 +1,274 @@
/*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 org.dromara.maxkey.authn.support.socialsignon;
import me.zhyd.oauth.request.AuthMaxkeyRequest;
import org.apache.commons.lang3.StringUtils;
import org.dromara.maxkey.authn.LoginCredential;
import org.dromara.maxkey.authn.annotation.CurrentUser;
import org.dromara.maxkey.authn.jwt.AuthJwt;
import org.dromara.maxkey.constants.ConstsLoginType;
import org.dromara.maxkey.entity.Message;
import org.dromara.maxkey.entity.SocialsAssociate;
import org.dromara.maxkey.entity.SocialsProvider;
import org.dromara.maxkey.entity.idm.UserInfo;
import org.dromara.maxkey.uuid.UUID;
import org.dromara.maxkey.web.WebContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletRequest;
import me.zhyd.oauth.request.AuthRequest;
import java.util.Map;
/**
* @author Crystal.Sea
*
*/
@Controller
@RequestMapping(value = "/logon/oauth20")
public class SocialSignOnEndpoint extends AbstractSocialSignOnEndpoint{
static final Logger _logger = LoggerFactory.getLogger(SocialSignOnEndpoint.class);
@RequestMapping(value={"/authorize/{provider}"}, method = RequestMethod.GET)
@ResponseBody
public Message<Object> authorize( HttpServletRequest request,
@PathVariable("provider") String provider
) {
_logger.trace("SocialSignOn provider : " + provider);
String instId = WebContext.getInst().getId();
String originURL =WebContext.getContextPath(request,false);
String authorizationUrl =
buildAuthRequest(
instId,
provider,
originURL + applicationConfig.getFrontendUri()
).authorize(authTokenService.genRandomJwt());
_logger.trace("authorize SocialSignOn : " + authorizationUrl);
return new Message<Object>((Object)authorizationUrl);
}
@RequestMapping(value={"/scanqrcode/{provider}"}, method = RequestMethod.GET)
@ResponseBody
public Message<SocialsProvider> scanQRCode(HttpServletRequest request,
@PathVariable("provider") String provider) {
String instId = WebContext.getInst().getId();
String originURL =WebContext.getContextPath(request,false);
AuthRequest authRequest =
buildAuthRequest(
instId,
provider,
originURL + applicationConfig.getFrontendUri());
if(authRequest == null ) {
_logger.error("build authRequest fail .");
}
String state = UUID.generate().toString();
//String state = authTokenService.genRandomJwt();
authRequest.authorize(state);
SocialsProvider socialSignOnProvider = socialSignOnProviderService.get(instId,provider);
SocialsProvider scanQrProvider = new SocialsProvider(socialSignOnProvider);
scanQrProvider.setState(state);
scanQrProvider.setRedirectUri(
socialSignOnProviderService.getRedirectUri(
originURL + applicationConfig.getFrontendUri(), provider));
//缓存state票据在缓存或者是redis中五分钟过期
if (provider.equalsIgnoreCase(AuthMaxkeyRequest.KEY)) {
socialSignOnProviderService.setToken(state);
}
return new Message<SocialsProvider>(scanQrProvider);
}
@RequestMapping(value={"/bind/{provider}"}, method = RequestMethod.GET)
public Message<AuthJwt> bind(@PathVariable("provider") String provider,
@CurrentUser UserInfo userInfo,
HttpServletRequest request) {
//auth call back may exception
try {
String originURL = WebContext.getContextPath(request,false);
SocialsAssociate socialsAssociate =
this.authCallback(userInfo.getInstId(),provider,originURL + applicationConfig.getFrontendUri());
socialsAssociate.setSocialUserInfo(accountJsonString);
socialsAssociate.setUserId(userInfo.getId());
socialsAssociate.setUsername(userInfo.getUsername());
socialsAssociate.setInstId(userInfo.getInstId());
//socialsAssociate.setAccessToken(JsonUtils.object2Json(accessToken));
//socialsAssociate.setExAttribute(JsonUtils.object2Json(accessToken.getResponseObject()));
_logger.debug("Social Bind : "+socialsAssociate);
this.socialsAssociateService.delete(socialsAssociate);
this.socialsAssociateService.insert(socialsAssociate);
return new Message<AuthJwt>();
}catch(Exception e) {
_logger.error("callback Exception ",e);
}
return new Message<AuthJwt>(Message.ERROR);
}
@RequestMapping(value={"/callback/{provider}"}, method = RequestMethod.GET)
public Message<AuthJwt> callback(@PathVariable("provider") String provider,
HttpServletRequest request) {
//auth call back may exception
try {
String originURL =WebContext.getContextPath(request,false);
String instId = WebContext.getInst().getId();
SocialsAssociate socialsAssociate =
this.authCallback(instId,provider,originURL + applicationConfig.getFrontendUri());
SocialsAssociate socialssssociate1 = this.socialsAssociateService.get(socialsAssociate);
_logger.debug("Loaded SocialSignOn Socials Associate : "+socialssssociate1);
if (null == socialssssociate1) {
//如果存在第三方ID并且在数据库无法找到映射关系则进行绑定逻辑
if (StringUtils.isNotEmpty(socialsAssociate.getSocialUserId())) {
//返回message为第三方用户标识
return new Message<AuthJwt>(Message.PROMPT,socialsAssociate.getSocialUserId());
}
}
socialsAssociate = socialssssociate1;
_logger.debug("Social Sign On from {} mapping to user {}",
socialsAssociate.getProvider(),socialsAssociate.getUsername());
LoginCredential loginCredential =new LoginCredential(
socialsAssociate.getUsername(),"",ConstsLoginType.SOCIALSIGNON);
SocialsProvider socialSignOnProvider = socialSignOnProviderService.get(instId,provider);
loginCredential.setProvider(socialSignOnProvider.getProviderName());
Authentication authentication = authenticationProvider.authenticate(loginCredential,true);
//socialsAssociate.setAccessToken(JsonUtils.object2Json(this.accessToken));
socialsAssociate.setSocialUserInfo(accountJsonString);
//socialsAssociate.setExAttribute(JsonUtils.object2Json(accessToken.getResponseObject()));
this.socialsAssociateService.update(socialsAssociate);
return new Message<AuthJwt>(authTokenService.genAuthJwt(authentication));
}catch(Exception e) {
_logger.error("callback Exception ",e);
return new Message<AuthJwt>(Message.ERROR);
}
}
/**
* 提供给第三方应用关联用户接口
* @return
*/
@RequestMapping(value={"/workweixin/qr/auth/login"}, method = {RequestMethod.POST})
public Message<AuthJwt> qrAuthLogin(
@RequestParam Map<String, String> param,
HttpServletRequest request) {
try {
if (null == param){
return new Message<AuthJwt>(Message.ERROR);
}
String token = param.get("token");
String username = param.get("username");
//判断token是否合法
String redisusername = this.socialSignOnProviderService.getToken(token);
if (StringUtils.isNotEmpty(redisusername)){
//设置token和用户绑定
boolean flag = this.socialSignOnProviderService.bindtoken(token,username);
if (flag) {
return new Message<AuthJwt>();
}
} else {
return new Message<AuthJwt>(Message.WARNING,"Invalid token");
}
}catch(Exception e) {
_logger.error("qrAuthLogin Exception ",e);
}
return new Message<AuthJwt>(Message.ERROR);
}
/**
* maxkey 监听扫码回调
* @param provider
* @param state
* @param request
* @return
*/
@RequestMapping(value={"/qrcallback/{provider}/{state}"}, method = RequestMethod.GET)
public Message<AuthJwt> qrcallback(@PathVariable("provider") String provider,@PathVariable("state") String state,
HttpServletRequest request) {
try {
//判断只有maxkey扫码
if (!provider.equalsIgnoreCase(AuthMaxkeyRequest.KEY)) {
return new Message<AuthJwt>(Message.ERROR);
}
String loginName = socialSignOnProviderService.getToken(state);
if (StringUtils.isEmpty(loginName)) {
//二维码过期
return new Message<AuthJwt>(Message.PROMPT);
}
if("-1".equalsIgnoreCase(loginName)){
//暂无用户扫码
return new Message<AuthJwt>(Message.WARNING);
}
String instId = WebContext.getInst().getId();
SocialsAssociate socialsAssociate = new SocialsAssociate();
socialsAssociate.setProvider(provider);
socialsAssociate.setSocialUserId(loginName);
socialsAssociate.setInstId(instId);
socialsAssociate = this.socialsAssociateService.get(socialsAssociate);
_logger.debug("qrcallback Loaded SocialSignOn Socials Associate : "+socialsAssociate);
if(null == socialsAssociate) {
return new Message<AuthJwt>(Message.ERROR);
}
_logger.debug("qrcallback Social Sign On from {} mapping to user {}", socialsAssociate.getProvider(),socialsAssociate.getUsername());
LoginCredential loginCredential =new LoginCredential(
socialsAssociate.getUsername(),"",ConstsLoginType.SOCIALSIGNON);
SocialsProvider socialSignOnProvider = socialSignOnProviderService.get(instId,provider);
loginCredential.setProvider(socialSignOnProvider.getProviderName());
Authentication authentication = authenticationProvider.authenticate(loginCredential,true);
//socialsAssociate.setAccessToken(JsonUtils.object2Json(this.accessToken));
socialsAssociate.setSocialUserInfo(accountJsonString);
//socialsAssociate.setExAttribute(JsonUtils.object2Json(accessToken.getResponseObject()));
this.socialsAssociateService.update(socialsAssociate);
return new Message<AuthJwt>(authTokenService.genAuthJwt(authentication));
}catch(Exception e) {
_logger.error("qrcallback Exception ",e);
return new Message<AuthJwt>(Message.ERROR);
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 org.dromara.maxkey.authn.support.socialsignon.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Date;
import java.util.List;
import org.dromara.maxkey.constants.ConstsDatabase;
import org.dromara.maxkey.entity.SocialsAssociate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
public class JdbcSocialsAssociateService implements SocialsAssociateService{
private static final Logger _logger = LoggerFactory.getLogger(JdbcSocialsAssociateService.class);
private static final String DEFAULT_DEFAULT_INSERT_STATEMENT = "insert into mxk_socials_associate(id, userid , username , provider , socialuserid , accesstoken , socialuserinfo , exattribute , instid)values( ? , ? , ? , ? , ?, ? , ? , ?, ?)";
private static final String DEFAULT_DEFAULT_INSERT_STATEMENT_ORACLE = "insert into mxk_socials_associate(id, userid , username , provider , socialuserid , accesstoken , socialuserinfo , exattribute , instid)values( ? , ? , ? , ? , ?, ? , ? , ?, ?)";
private static final String DEFAULT_DEFAULT_SIGNON_SELECT_STATEMENT = "select id, userid , username , provider , socialuserid , accesstoken , socialuserinfo , exattribute , createddate , updateddate , instid from mxk_socials_associate where provider = ? and socialuserid = ? and instId = ?";
private static final String DEFAULT_DEFAULT_BIND_SELECT_STATEMENT = "select id, userid , username , provider , socialuserid , accesstoken , socialuserinfo , exattribute , createddate , updateddate , instid from mxk_socials_associate where userid = ?" ;
private static final String DEFAULT_DEFAULT_DELETE_STATEMENT = "delete from mxk_socials_associate where userid = ? and provider = ?";
private static final String DEFAULT_DEFAULT_UPDATE_STATEMENT= "update mxk_socials_associate set accesstoken = ? , socialuserinfo = ? , exattribute = ? ,updateddate = ? where id = ?";
private final JdbcTemplate jdbcTemplate;
public JdbcSocialsAssociateService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate=jdbcTemplate;
}
@Override
public boolean insert(SocialsAssociate socialsAssociate) {
socialsAssociate.setId(socialsAssociate.generateId());
jdbcTemplate.update(
ConstsDatabase.compare(ConstsDatabase.ORACLE)?
DEFAULT_DEFAULT_INSERT_STATEMENT_ORACLE:DEFAULT_DEFAULT_INSERT_STATEMENT,
new Object[] {
socialsAssociate.getId(),
socialsAssociate.getUserId(),
socialsAssociate.getUsername(),
socialsAssociate.getProvider(),
socialsAssociate.getSocialUserId(),
socialsAssociate.getAccessToken(),
socialsAssociate.getSocialUserInfo(),
socialsAssociate.getExAttribute(),
socialsAssociate.getInstId()
},
new int[] {
Types.VARCHAR, Types.VARCHAR,Types.VARCHAR,Types.VARCHAR,
Types.VARCHAR,Types.VARCHAR, Types.VARCHAR,Types.VARCHAR,
Types.VARCHAR
});
return true;
}
@Override
public boolean delete(SocialsAssociate socialsAssociate) {
jdbcTemplate.update(DEFAULT_DEFAULT_DELETE_STATEMENT,
new Object[] {
socialsAssociate.getUserId(),
socialsAssociate.getProvider()
},
new int[] {Types.VARCHAR, Types.VARCHAR});
return true;
}
@Override
public SocialsAssociate get(SocialsAssociate socialsAssociate) {
List<SocialsAssociate> listsocialsAssociate=jdbcTemplate.query(
DEFAULT_DEFAULT_SIGNON_SELECT_STATEMENT,
new SocialsAssociateRowMapper(),
socialsAssociate.getProvider(),
socialsAssociate.getSocialUserId(),
socialsAssociate.getInstId());
_logger.debug("list socialsAssociate "+listsocialsAssociate);
return (listsocialsAssociate.size()>0)?listsocialsAssociate.get(0):null;
}
@Override
public List<SocialsAssociate> query(
SocialsAssociate socialsAssociate) {
List<SocialsAssociate> listsocialsAssociate=jdbcTemplate.query(
DEFAULT_DEFAULT_BIND_SELECT_STATEMENT,
new SocialsAssociateRowMapper(),
socialsAssociate.getUserId());
_logger.debug("query bind SocialSignOnUser "+listsocialsAssociate);
return listsocialsAssociate;
}
@Override
public boolean update(SocialsAssociate socialsAssociate) {
jdbcTemplate.update(DEFAULT_DEFAULT_UPDATE_STATEMENT,
new Object[] {
socialsAssociate.getAccessToken(),
socialsAssociate.getSocialUserInfo(),
socialsAssociate.getExAttribute(),
new Date(),
socialsAssociate.getId()
},
new int[] {Types.VARCHAR, Types.VARCHAR,Types.VARCHAR, Types.TIMESTAMP,Types.VARCHAR });
return false;
}
private final class SocialsAssociateRowMapper implements RowMapper<SocialsAssociate> {
@Override
public SocialsAssociate mapRow(ResultSet rs, int rowNum)
throws SQLException {
SocialsAssociate socialsAssociate=new SocialsAssociate();
socialsAssociate.setId(rs.getString(1));
socialsAssociate.setUserId(rs.getString(2));
socialsAssociate.setUsername(rs.getString(3));
socialsAssociate.setProvider(rs.getString(4));
socialsAssociate.setSocialUserId(rs.getString(5));
socialsAssociate.setAccessToken(rs.getString(6));
socialsAssociate.setSocialUserInfo(rs.getString(7));
socialsAssociate.setExAttribute(rs.getString(8));
socialsAssociate.setCreatedDate(rs.getTimestamp(9));
socialsAssociate.setUpdatedDate(rs.getTimestamp(10));
socialsAssociate.setInstId(rs.getString(11));
return socialsAssociate;
}
}
}

View File

@@ -0,0 +1,257 @@
/*
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 org.dromara.maxkey.authn.support.socialsignon.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.dromara.maxkey.authn.support.socialsignon.token.RedisTokenStore;
import org.dromara.maxkey.constants.ConstsTimeInterval;
import org.dromara.maxkey.crypto.password.PasswordReciprocal;
import org.dromara.maxkey.entity.SocialsProvider;
import org.dromara.maxkey.entity.SocialsProviderLogin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.*;
public class SocialSignOnProviderService{
private static Logger _logger = LoggerFactory.getLogger(SocialSignOnProviderService.class);
private static final String DEFAULT_SELECT_STATEMENT = "select * from mxk_socials_provider where instid = ? and status = 1 order by sortindex";
protected static final Cache<String, SocialsProviderLogin> socialsProviderLoginStore =
Caffeine.newBuilder()
.expireAfterWrite(ConstsTimeInterval.ONE_HOUR, TimeUnit.MINUTES)
.build();
HashMap<String ,SocialsProvider>socialSignOnProviderMaps = new HashMap<String ,SocialsProvider>();
private final JdbcTemplate jdbcTemplate;
RedisTokenStore redisTokenStore;
public SocialSignOnProviderService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate=jdbcTemplate;
}
public SocialsProvider get(String instId,String provider){
return socialSignOnProviderMaps.get(instId + "_" + provider);
}
public void setToken(String token){
this.redisTokenStore.store(token);
}
public boolean bindtoken(String token,String loginName){
return this.redisTokenStore.bindtoken(token,loginName);
}
public String getToken(String token){
return this.redisTokenStore.get(token);
}
public String getRedirectUri(String baseUri,String provider) {
return baseUri + "/passport/callback/"+provider;
}
public AuthRequest getAuthRequest(String instId,String provider,String baseUri) throws Exception {
AuthRequest authRequest = null;
AuthConfig authConfig = AuthConfig.builder()
.clientId(this.get(instId,provider).getClientId())
.clientSecret(this.get(instId,provider).getClientSecret())
.redirectUri(getRedirectUri(baseUri , provider))
.build();
if(provider.equalsIgnoreCase("WeChatOpen")) {
authRequest = new AuthWeChatOpenRequest(authConfig);
}else if(provider.equalsIgnoreCase("sinaweibo")) {
authRequest = new AuthWeiboRequest(authConfig);
}else if(provider.equalsIgnoreCase("qq")) {
authRequest = new AuthQqRequest(authConfig);
}else if(provider.equalsIgnoreCase("Alipay")) {
String alipayPublicKey = "";
authRequest = new AuthAlipayRequest(authConfig,alipayPublicKey);
}else if(provider.equalsIgnoreCase("Twitter")) {
authRequest = new AuthTwitterRequest(authConfig);
}else if(provider.equalsIgnoreCase("google")) {
authRequest = new AuthGoogleRequest(authConfig);
}else if(provider.equalsIgnoreCase("microsoft")) {
authRequest = new AuthMicrosoftRequest(authConfig);
}else if(provider.equalsIgnoreCase("Linkedin")) {
authRequest = new AuthLinkedinRequest(authConfig);
}else if(provider.equalsIgnoreCase("DingTalk")) {
authRequest = new AuthDingTalkRequest(authConfig);
}else if(provider.equalsIgnoreCase("gitee")) {
authRequest = new AuthGiteeRequest(authConfig);
}else if(provider.equalsIgnoreCase("Baidu")) {
authRequest = new AuthBaiduRequest(authConfig);
}else if(provider.equalsIgnoreCase("Douyin")) {
authRequest = new AuthDouyinRequest(authConfig);
}else if(provider.equalsIgnoreCase("Eleme")) {
authRequest = new AuthElemeRequest(authConfig);
}else if(provider.equalsIgnoreCase("Feishu")) {
//authRequest = new AuthFeishuRequest(authConfig);
authRequest = new AuthFeishu2Request(authConfig);
}else if(provider.equalsIgnoreCase("Github")) {
authRequest = new AuthGithubRequest(authConfig);
}else if(provider.equalsIgnoreCase("Gitlab")) {
authRequest = new AuthGitlabRequest(authConfig);
}else if(provider.equalsIgnoreCase("Huawei")) {
authRequest = new AuthHuaweiRequest(authConfig);
}else if(provider.equalsIgnoreCase("jd")) {
authRequest = new AuthJdRequest(authConfig);
}else if(provider.equalsIgnoreCase("Meituan")) {
authRequest = new AuthMeituanRequest(authConfig);
}else if(provider.equalsIgnoreCase("Mi")) {
authRequest = new AuthMiRequest(authConfig);
}else if(provider.equalsIgnoreCase("Oschina")) {
authRequest = new AuthOschinaRequest(authConfig);
}else if(provider.equalsIgnoreCase("Taobao")) {
authRequest = new AuthTaobaoRequest(authConfig);
}else if(provider.equalsIgnoreCase("Toutiao")) {
authRequest = new AuthToutiaoRequest(authConfig);
}else if(provider.equalsIgnoreCase("WeChatQyQrcode")) {
authRequest = new AuthWeChatEnterpriseQrcodeRequest(authConfig);
}else if(provider.equalsIgnoreCase("workweixin")) {
authRequest = new AuthWeChatEnterpriseWebRequestCost(authConfig);
}else if(provider.equalsIgnoreCase("welink")) {
authRequest = new AuthHuaweiWeLinkRequest(authConfig);
}else if(provider.equalsIgnoreCase("maxkey")) {
authRequest = new AuthMaxkeyRequest(authConfig);
}
return authRequest;
}
public String getAccountId(String provider,AuthResponse<?> authResponse) throws Exception {
if(authResponse.getData() != null) {
AuthUser authUser = (AuthUser)authResponse.getData();
_logger.debug("AuthUser[{},{},{},{},{},{},{},{},{},{},{},{}]",
authUser.getUuid(),
authUser.getUsername(),
authUser.getNickname(),
authUser.getGender(),
authUser.getEmail(),
authUser.getCompany(),
authUser.getBlog(),
authUser.getLocation(),
authUser.getRemark(),
authUser.getSource(),
authUser.getBlog(),
authUser.getAvatar());
_logger.debug("RawUserInfo {}",authUser.getRawUserInfo());
if(provider.equalsIgnoreCase("WeChatOpen")) {
return authUser.getUuid();
}else if(provider.equalsIgnoreCase("sinaweibo")) {
return authUser.getUuid();
}else if(provider.equalsIgnoreCase("qq")) {
return authUser.getUuid();
}else if(provider.equalsIgnoreCase("Alipay")) {
return authUser.getUuid();
}else if(provider.equalsIgnoreCase("Twitter")) {
return authUser.getUuid();
}else if(provider.equalsIgnoreCase("google")) {
return authUser.getUuid();
}else if(provider.equalsIgnoreCase("microsoft")) {
return authUser.getUuid();
}else if(provider.equalsIgnoreCase("Linkedin")) {
return authUser.getUuid();
}else if(provider.equalsIgnoreCase("DingTalk")) {
return authUser.getUuid();
}else {
return authUser.getUuid();
}
}
return null;
}
public SocialsProviderLogin loadSocials(String instId) {
SocialsProviderLogin socialsLogin = socialsProviderLoginStore.getIfPresent(instId);
if(socialsLogin == null) {
List<SocialsProvider> listSocialsProvider = jdbcTemplate.query(
DEFAULT_SELECT_STATEMENT,
new SocialsProviderRowMapper(),instId);
_logger.trace("query SocialsProvider " + listSocialsProvider);
List<SocialsProvider> socialSignOnProviders = new ArrayList<SocialsProvider>();
socialsLogin = new SocialsProviderLogin(socialSignOnProviders);
for(SocialsProvider socialsProvider : listSocialsProvider){
_logger.debug("Social Provider {} ({})" ,
socialsProvider.getProvider() ,socialsProvider.getProviderName());
if(socialsProvider.getDisplay().equals("true")) {
socialSignOnProviders.add(new SocialsProvider(socialsProvider));
}
if(socialsProvider.getScanCode().equalsIgnoreCase("true")) {
socialsLogin.setQrScan(socialsProvider.getProvider());
}
//add to socialSignOnProviderMaps
socialSignOnProviderMaps.put(instId + "_" + socialsProvider.getProvider() , socialsProvider);
}
_logger.debug("social SignOn Providers Login {}" , socialsLogin);
socialsProviderLoginStore.put(instId, socialsLogin);
}
return socialsLogin;
}
private final class SocialsProviderRowMapper implements RowMapper<SocialsProvider> {
@Override
public SocialsProvider mapRow(ResultSet rs, int rowNum)
throws SQLException {
SocialsProvider socialsProvider=new SocialsProvider();
socialsProvider.setId(rs.getString("id"));
socialsProvider.setProvider(rs.getString("provider"));
socialsProvider.setProviderName(rs.getString("providername"));
socialsProvider.setIcon(rs.getString("icon"));
socialsProvider.setClientId(rs.getString("clientid"));
String clientSecret= rs.getString("clientsecret");
clientSecret = PasswordReciprocal.getInstance().decoder(clientSecret);
socialsProvider.setClientSecret(clientSecret);
socialsProvider.setAgentId(rs.getString("agentId"));
socialsProvider.setDisplay(rs.getString("display"));
socialsProvider.setSortIndex(rs.getInt("sortindex"));
socialsProvider.setScanCode(rs.getString("scancode"));
socialsProvider.setStatus(rs.getInt("status"));
socialsProvider.setInstId(rs.getString("instid"));
return socialsProvider;
}
}
public void setRedisTokenStore(RedisTokenStore redisTokenStore) {
this.redisTokenStore = redisTokenStore;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 org.dromara.maxkey.authn.support.socialsignon.service;
import java.util.List;
import org.dromara.maxkey.entity.SocialsAssociate;
public interface SocialsAssociateService{
public boolean insert(SocialsAssociate socialsAssociate);
public List<SocialsAssociate> query (SocialsAssociate socialsAssociate);
public SocialsAssociate get (SocialsAssociate socialsAssociate);
public boolean delete (SocialsAssociate socialsAssociate);
public boolean update (SocialsAssociate socialsAssociate);
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 org.dromara.maxkey.authn.support.socialsignon.token;
import org.apache.commons.lang3.StringUtils;
import org.dromara.maxkey.constants.ConstsTimeInterval;
import java.util.concurrent.ConcurrentHashMap;
public class RedisTokenStore {
protected int validitySeconds = ConstsTimeInterval.ONE_MINUTE * 2;
private final ConcurrentHashMap<String, String> tokenStore = new ConcurrentHashMap<String, String>();
public RedisTokenStore() {
super();
}
public static String PREFIX = "REDIS_QRSCRAN_SERVICE_";
public void store(String token) {
tokenStore.put(PREFIX + token,"-1");
/* DateTime currentDateTime = new DateTime();
RedisConnection conn = connectionFactory.getConnection();
conn.getConn().setex(PREFIX + token, validitySeconds, "-1");
conn.close();*/
}
public boolean bindtoken(String token,String loginname) {
boolean flag = false;
try {
/* DateTime currentDateTime = new DateTime();
RedisConnection conn = connectionFactory.getConnection();
conn.getConn().setex(PREFIX + token, validitySeconds, loginname);
//conn.setexObject(PREFIX + token, validitySeconds, loginname);
conn.close();*/
tokenStore.put(PREFIX + token,loginname);
return true;
}catch (Exception e) {
}
return flag;
}
public String get(String token) {
/* RedisConnection conn = connectionFactory.getConnection();
String value = conn.get(PREFIX + token);
if(StringUtils.isNotEmpty(value) && !"-1".equalsIgnoreCase(value)) {
conn.delete(PREFIX + token);
return value;
}*/
String value = tokenStore.get(PREFIX + token);
if(StringUtils.isNotEmpty(value) && !"-1".equalsIgnoreCase(value)) {
tokenStore.remove(PREFIX + token);
return value;
}
return value;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
*
* 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
*
* http://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 org.dromara.maxkey.autoconfigure;
import org.dromara.maxkey.authn.support.socialsignon.service.JdbcSocialsAssociateService;
import org.dromara.maxkey.authn.support.socialsignon.service.SocialSignOnProviderService;
import org.dromara.maxkey.authn.support.socialsignon.token.RedisTokenStore;
import org.dromara.maxkey.entity.SocialsProvider;
import org.dromara.maxkey.persistence.redis.RedisConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.jdbc.core.JdbcTemplate;
@AutoConfiguration
@ComponentScan(basePackages = {
"org.maxkey.authn.support.socialsignon"
})
public class SocialSignOnAutoConfiguration{
private static final Logger _logger = LoggerFactory.getLogger(SocialSignOnAutoConfiguration.class);
@Bean(name = "socialSignOnProviderService")
@ConditionalOnClass(SocialsProvider.class)
SocialSignOnProviderService socialSignOnProviderService(
@Value("${maxkey.server.persistence}") int persistence,
JdbcTemplate jdbcTemplate,
RedisConnectionFactory redisConnFactory) {
SocialSignOnProviderService socialSignOnProviderService = new SocialSignOnProviderService(jdbcTemplate);
//load default Social Providers from database
socialSignOnProviderService.loadSocials("1");
RedisTokenStore redisTokenStore = new RedisTokenStore();
socialSignOnProviderService.setRedisTokenStore(redisTokenStore);
_logger.debug("SocialSignOnProviderService inited.");
return socialSignOnProviderService;
}
@Bean(name = "socialsAssociateService")
JdbcSocialsAssociateService socialsAssociateService(JdbcTemplate jdbcTemplate) {
JdbcSocialsAssociateService socialsAssociateService = new JdbcSocialsAssociateService(jdbcTemplate);
_logger.debug("JdbcSocialsAssociateService inited.");
return socialsAssociateService;
}
}

View File

@@ -0,0 +1 @@
org.dromara.maxkey.autoconfigure.SocialSignOnAutoConfiguration