소스 검색

Merge branch 'main' of https://gitee.com/dromara/MaxKey

MaxKey 2 년 전
부모
커밋
fe82cb8c72
25개의 변경된 파일900개의 추가작업 그리고 50개의 파일을 삭제
  1. 7 0
      build.gradle
  2. 3 3
      maxkey-authentications/maxkey-authentication-sms/src/main/java/org/maxkey/password/sms/SmsOtpAuthnService.java
  3. 58 0
      maxkey-authentications/maxkey-authentication-social/src/main/java/me/zhyd/oauth/request/AuthMaxkeyRequest.java
  4. 51 0
      maxkey-authentications/maxkey-authentication-social/src/main/java/me/zhyd/oauth/request/MaxkeyAuthDefaultSource.java
  5. 127 18
      maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/SocialSignOnEndpoint.java
  6. 24 4
      maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/service/SocialSignOnProviderService.java
  7. 82 0
      maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/token/RedisTokenStore.java
  8. 12 1
      maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/autoconfigure/SocialSignOnAutoConfiguration.java
  9. 2 2
      maxkey-web-frontend/maxkey-web-app/src/app/routes/config/socials-provider/socials-provider.component.ts
  10. 31 2
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/callback.component.ts
  11. 5 2
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.html
  12. 2 1
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.less
  13. 52 1
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.ts
  14. 2 2
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/passport.module.ts
  15. 30 0
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.html
  16. 0 0
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.less
  17. 42 0
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.spec.ts
  18. 124 0
      maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.ts
  19. 4 0
      maxkey-web-frontend/maxkey-web-app/src/app/service/authn.service.ts
  20. 8 0
      maxkey-web-frontend/maxkey-web-app/src/app/service/socials-provider.service.ts
  21. 146 0
      maxkey-web-frontend/maxkey-web-app/src/assets/qrcode/qrcode.min.js
  22. 1 1
      maxkey-web-frontend/maxkey-web-app/src/environments/environment.ts
  23. 4 3
      maxkey-web-frontend/maxkey-web-app/src/index.html
  24. 36 6
      maxkey-webs/maxkey-web-maxkey/build.gradle
  25. 47 4
      maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/LoginEntryPoint.java

+ 7 - 0
build.gradle

@@ -395,6 +395,13 @@ subprojects {
          implementation group: 'io.netty', name: 'netty-all', version: "${nettyVersion}"
           //阿里云
          implementation group: 'com.aliyun', name: 'aliyun-java-sdk-core', version: "${aliyunjavasdkcoreVersion}"
+         // https://mvnrepository.com/artifact/io.opentracing/opentracing-util
+         implementation 'io.opentracing:opentracing-util:0.33.0'
+         // https://mvnrepository.com/artifact/io.opentracing/opentracing-api
+         implementation 'io.opentracing:opentracing-api:0.33.0'
+         implementation 'io.opentracing:opentracing-noop:0.33.0'
+
+
          //腾讯云
          implementation group: 'com.tencentcloudapi', name: 'tencentcloud-sdk-java', version: "${tencentcloudsdkjavaVersion}"
          //json

+ 3 - 3
maxkey-authentications/maxkey-authentication-sms/src/main/java/org/maxkey/password/sms/SmsOtpAuthnService.java

@@ -71,7 +71,7 @@ public class SmsOtpAuthnService {
     			if(smsProvider.getProvider().equalsIgnoreCase("aliyun")) {
     				SmsOtpAuthnAliyun aliyun = new SmsOtpAuthnAliyun(
 													smsProvider.getAppKey(),
-													smsProvider.getAppSecret(),
+													PasswordReciprocal.getInstance().decoder(smsProvider.getAppSecret()),
 													smsProvider.getTemplateId(),
 													smsProvider.getSignName()
 												);
@@ -82,7 +82,7 @@ public class SmsOtpAuthnService {
     			}else if(smsProvider.getProvider().equalsIgnoreCase("tencentcloud")) {
     				SmsOtpAuthnTencentCloud tencentCloud = new SmsOtpAuthnTencentCloud(
     												smsProvider.getAppKey(),
-    												smsProvider.getAppSecret(),
+													PasswordReciprocal.getInstance().decoder(smsProvider.getAppSecret()),
     												smsProvider.getSmsSdkAppId(),
     												smsProvider.getTemplateId(),
     												smsProvider.getSignName()
@@ -94,7 +94,7 @@ public class SmsOtpAuthnService {
     			}else if(smsProvider.getProvider().equalsIgnoreCase("neteasesms")) {
     				SmsOtpAuthnYunxin yunxin = new SmsOtpAuthnYunxin(
     												smsProvider.getAppKey(),
-    												smsProvider.getAppSecret(),
+													PasswordReciprocal.getInstance().decoder(smsProvider.getAppSecret()),
     												smsProvider.getTemplateId()
     											);
     				if(redisOptTokenStore != null) {

+ 58 - 0
maxkey-authentications/maxkey-authentication-social/src/main/java/me/zhyd/oauth/request/AuthMaxkeyRequest.java

@@ -0,0 +1,58 @@
+/*
+ * 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 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;
+    }
+}

+ 51 - 0
maxkey-authentications/maxkey-authentication-social/src/main/java/me/zhyd/oauth/request/MaxkeyAuthDefaultSource.java

@@ -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;
+	        }
+	    }
+}

+ 127 - 18
maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/SocialSignOnEndpoint.java

@@ -22,6 +22,8 @@ package org.maxkey.authn.support.socialsignon;
 
 import javax.servlet.http.HttpServletRequest;
 
+import me.zhyd.oauth.request.AuthMaxkeyRequest;
+import org.apache.commons.lang3.StringUtils;
 import org.maxkey.authn.LoginCredential;
 import org.maxkey.authn.annotation.CurrentUser;
 import org.maxkey.authn.jwt.AuthJwt;
@@ -30,18 +32,18 @@ import org.maxkey.entity.Message;
 import org.maxkey.entity.SocialsAssociate;
 import org.maxkey.entity.SocialsProvider;
 import org.maxkey.entity.UserInfo;
+import org.maxkey.uuid.UUID;
 import org.maxkey.web.WebContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.core.Authentication;
 import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.*;
 import me.zhyd.oauth.request.AuthRequest;
 
+import java.util.Map;
+
 /**
  * @author Crystal.Sea
  *
@@ -50,7 +52,7 @@ import me.zhyd.oauth.request.AuthRequest;
 @RequestMapping(value = "/logon/oauth20")
 public class SocialSignOnEndpoint  extends AbstractSocialSignOnEndpoint{
 	final static Logger _logger = LoggerFactory.getLogger(SocialSignOnEndpoint.class);
-    
+
 	@RequestMapping(value={"/authorize/{provider}"}, method = RequestMethod.GET)
 	@ResponseBody
 	public ResponseEntity<?> authorize( HttpServletRequest request,
@@ -59,13 +61,13 @@ public class SocialSignOnEndpoint  extends AbstractSocialSignOnEndpoint{
 		_logger.trace("SocialSignOn provider : " + provider);
 		String instId = WebContext.getInst().getId();
 		String originURL =WebContext.getHttpContextPath(request,false);
-    	String authorizationUrl = 
+    	String authorizationUrl =
 				buildAuthRequest(
 						instId,
 						provider,
 						originURL + applicationConfig.getFrontendUri()
 				).authorize(authTokenService.genRandomJwt());
-    	
+
 		_logger.trace("authorize SocialSignOn : " + authorizationUrl);
 		return new Message<Object>((Object)authorizationUrl).buildResponse();
 	}
@@ -85,7 +87,8 @@ public class SocialSignOnEndpoint  extends AbstractSocialSignOnEndpoint{
 	    if(authRequest == null ) {
 	        _logger.error("build authRequest fail .");
 	    }
-	    String state = authTokenService.genRandomJwt();
+		String state = UUID.generate().toString();
+	    //String state = authTokenService.genRandomJwt();
 	    authRequest.authorize(state);
 	    
 		SocialsProvider socialSignOnProvider = socialSignOnProviderService.get(instId,provider);
@@ -94,10 +97,14 @@ public class SocialSignOnEndpoint  extends AbstractSocialSignOnEndpoint{
 		scanQrProvider.setRedirectUri(
 				socialSignOnProviderService.getRedirectUri(
 						originURL + applicationConfig.getFrontendUri(), provider));
+		//缓存state票据在缓存或者是redis中五分钟过期
+		if (provider.equalsIgnoreCase(AuthMaxkeyRequest.KEY)) {
+			socialSignOnProviderService.setToken(state);
+		}
 		
 		return new Message<SocialsProvider>(scanQrProvider).buildResponse();
-	}	
-	
+	}
+
 	
 	@RequestMapping(value={"/bind/{provider}"}, method = RequestMethod.GET)
 	public ResponseEntity<?> bind(@PathVariable String provider,
@@ -105,7 +112,7 @@ public class SocialSignOnEndpoint  extends AbstractSocialSignOnEndpoint{
 								  HttpServletRequest request) {
 		 //auth call back may exception 
 	    try {
-	    	String originURL =WebContext.getHttpContextPath(request,false);
+	    	String originURL = WebContext.getHttpContextPath(request,false);
 	    	SocialsAssociate socialsAssociate = 
 	    			this.authCallback(userInfo.getInstId(),provider,originURL + applicationConfig.getFrontendUri());
 		    socialsAssociate.setSocialUserInfo(accountJsonString);
@@ -125,6 +132,8 @@ public class SocialSignOnEndpoint  extends AbstractSocialSignOnEndpoint{
 	    return new Message<AuthJwt>(Message.ERROR).buildResponse();
 	}
 
+
+
 	@RequestMapping(value={"/callback/{provider}"}, method = RequestMethod.GET)
 	public ResponseEntity<?> callback(@PathVariable String provider,
 									  HttpServletRequest request) {
@@ -134,15 +143,20 @@ public class SocialSignOnEndpoint  extends AbstractSocialSignOnEndpoint{
 	    	String instId = WebContext.getInst().getId();
 	    	SocialsAssociate socialsAssociate = 
 	    			this.authCallback(instId,provider,originURL + applicationConfig.getFrontendUri());
+
+			SocialsAssociate socialssssociate1 = this.socialsAssociateService.get(socialsAssociate);
 		
-	    	socialsAssociate=this.socialsAssociateService.get(socialsAssociate);
-		
-	    	_logger.debug("Loaded SocialSignOn Socials Associate : "+socialsAssociate);
-		
-	    	if(null == socialsAssociate) {
-	    		return new Message<AuthJwt>(Message.ERROR).buildResponse();
-	    	}
+	    	_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()).buildResponse();
+				}
+			}
+
+			socialsAssociate = socialssssociate1;
 	    	_logger.debug("Social Sign On from {} mapping to user {}",
 		                socialsAssociate.getProvider(),socialsAssociate.getUsername());
 		
@@ -163,4 +177,99 @@ public class SocialSignOnEndpoint  extends AbstractSocialSignOnEndpoint{
 	    	 return new Message<AuthJwt>(Message.ERROR).buildResponse();
 	    }
 	}
+
+
+	/**
+	 * 提供给第三方应用关联用户接口
+	 * @return
+	 */
+	@RequestMapping(value={"/workweixin/qr/auth/login"}, method = {RequestMethod.POST})
+	public ResponseEntity<?> qrAuthLogin(
+			@RequestParam Map<String, String> param,
+			HttpServletRequest request) {
+
+		try {
+			if (null == param){
+				return new Message<AuthJwt>(Message.ERROR).buildResponse();
+			}
+			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>().buildResponse();
+				}
+			} else {
+				return new Message<AuthJwt>(Message.WARNING,"Invalid token").buildResponse();
+			}
+		}catch(Exception e) {
+			_logger.error("qrAuthLogin Exception  ",e);
+		}
+		return new Message<AuthJwt>(Message.ERROR).buildResponse();
+	}
+
+
+	/**
+	 * maxkey 监听扫码回调
+	 * @param provider
+	 * @param state
+	 * @param request
+	 * @return
+	 */
+	@RequestMapping(value={"/qrcallback/{provider}/{state}"}, method = RequestMethod.GET)
+	public ResponseEntity<?> qrcallback(@PathVariable String provider,@PathVariable String state,
+										HttpServletRequest request) {
+		try {
+			//判断只有maxkey扫码
+			if (!provider.equalsIgnoreCase(AuthMaxkeyRequest.KEY)) {
+				return new Message<AuthJwt>(Message.ERROR).buildResponse();
+			}
+
+			String loginName = socialSignOnProviderService.getToken(state);
+			if (StringUtils.isEmpty(loginName)) {
+				//二维码过期
+				return new Message<AuthJwt>(Message.PROMPT).buildResponse();
+			}
+			if("-1".equalsIgnoreCase(loginName)){
+				//暂无用户扫码
+				return new Message<AuthJwt>(Message.WARNING).buildResponse();
+			}
+			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).buildResponse();
+			}
+
+			_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)).buildResponse();
+		}catch(Exception e) {
+			_logger.error("qrcallback Exception  ",e);
+			return new Message<AuthJwt>(Message.ERROR).buildResponse();
+		}
+	}
 }

+ 24 - 4
maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/service/SocialSignOnProviderService.java

@@ -24,6 +24,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
+import org.maxkey.authn.support.socialsignon.token.RedisTokenStore;
 import org.maxkey.constants.ConstsTimeInterval;
 import org.maxkey.crypto.password.PasswordReciprocal;
 import org.maxkey.entity.SocialsProvider;
@@ -54,6 +55,9 @@ public class SocialSignOnProviderService{
 	HashMap<String ,SocialsProvider>socialSignOnProviderMaps = new HashMap<String ,SocialsProvider>();
 	
 	private final JdbcTemplate jdbcTemplate;
+
+
+	RedisTokenStore redisTokenStore;
 	
 	public SocialSignOnProviderService(JdbcTemplate jdbcTemplate) {
         this.jdbcTemplate=jdbcTemplate; 
@@ -62,6 +66,17 @@ public class SocialSignOnProviderService{
 	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;
@@ -129,10 +144,10 @@ public class SocialSignOnProviderService{
             authRequest = new AuthWeChatEnterpriseWebRequest(authConfig);
         }else if(provider.equalsIgnoreCase("welink")) {
             authRequest = new AuthHuaweiWeLinkRequest(authConfig);
-        }
-		
-		
-		
+        }else if(provider.equalsIgnoreCase("maxkey")) {
+			authRequest = new AuthMaxkeyRequest(authConfig);
+		}
+
 		return authRequest;
 	}
 	
@@ -234,4 +249,9 @@ public class SocialSignOnProviderService{
             return socialsProvider;
         }
     }
+
+
+	public void setRedisTokenStore(RedisTokenStore redisTokenStore) {
+		this.redisTokenStore = redisTokenStore;
+	}
 }

+ 82 - 0
maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/token/RedisTokenStore.java

@@ -0,0 +1,82 @@
+/*
+ * 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.maxkey.authn.support.socialsignon.token;
+
+import org.apache.commons.lang3.StringUtils;
+import org.joda.time.DateTime;
+import org.maxkey.constants.ConstsTimeInterval;
+import org.maxkey.persistence.redis.RedisConnection;
+import org.maxkey.persistence.redis.RedisConnectionFactory;
+
+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;
+    }
+
+}

+ 12 - 1
maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/autoconfigure/SocialSignOnAutoConfiguration.java

@@ -20,10 +20,14 @@ package org.maxkey.autoconfigure;
 import java.io.IOException;
 import org.maxkey.authn.support.socialsignon.service.JdbcSocialsAssociateService;
 import org.maxkey.authn.support.socialsignon.service.SocialSignOnProviderService;
+import org.maxkey.authn.support.socialsignon.token.RedisTokenStore;
+import org.maxkey.constants.ConstsPersistence;
 import org.maxkey.entity.SocialsProvider;
+import org.maxkey.persistence.redis.RedisConnectionFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
+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;
@@ -40,10 +44,17 @@ public class SocialSignOnAutoConfiguration implements InitializingBean {
     @Bean(name = "socialSignOnProviderService")
     @ConditionalOnClass(SocialsProvider.class)
     public SocialSignOnProviderService socialSignOnProviderService(
-                    JdbcTemplate jdbcTemplate) throws IOException {
+            @Value("${maxkey.server.persistence}") int persistence,
+            JdbcTemplate jdbcTemplate,
+            RedisConnectionFactory redisConnFactory
+                    ) throws IOException {
         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;
     }

+ 2 - 2
maxkey-web-frontend/maxkey-web-app/src/app/routes/config/socials-provider/socials-provider.component.ts

@@ -123,7 +123,7 @@ export class SocialsProviderComponent implements OnInit {
   onAdd(e: MouseEvent): void {
     e.preventDefault();
     const modal = this.modalService.create({
-      //nzContent: SocialsProviderEditerComponent,
+      //nzContent: SocialsProviderBindUserComponent,
       nzViewContainerRef: this.viewContainerRef,
       nzComponentParams: {
         isEdit: false,
@@ -143,7 +143,7 @@ export class SocialsProviderComponent implements OnInit {
     e.preventDefault();
 
     const modal = this.modalService.create({
-      //nzContent: SocialsProviderEditerComponent,
+      //nzContent: SocialsProviderBindUserComponent,
       nzViewContainerRef: this.viewContainerRef,
       nzComponentParams: {
         isEdit: true,

+ 31 - 2
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/callback.component.ts

@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-import { Inject, Optional, Component, OnInit } from '@angular/core';
+import { Inject, Optional, Component, OnInit, ViewContainerRef } from '@angular/core';
 import { ActivatedRoute, Router } from '@angular/router';
 import { ReuseTabService } from '@delon/abc/reuse-tab';
 import { SettingsService } from '@delon/theme';
-
+import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal';
 import { AuthnService } from '../../service/authn.service';
 import { SocialsProviderService } from '../../service/socials-provider.service';
+import {SocialsProviderBindUserComponent} from "./socials-provider-bind-user/socials-provider-bind-user.component";
 
 @Component({
   selector: 'app-callback',
@@ -30,6 +31,8 @@ export class CallbackComponent implements OnInit {
   provider = '';
 
   constructor(
+    private viewContainerRef: ViewContainerRef,
+    private modalService: NzModalService,
     private router: Router,
     private socialsProviderService: SocialsProviderService,
     private settingsService: SettingsService,
@@ -50,6 +53,11 @@ export class CallbackComponent implements OnInit {
           // 设置用户Token信息
           this.authnService.auth(res.data);
         }
+        else if (res.code === 102) {
+          //绑定用户
+          this.openBindUser(res.message)
+          return;
+        }
         this.authnService.navigate({});
       });
     } else {
@@ -60,4 +68,25 @@ export class CallbackComponent implements OnInit {
       });
     }
   }
+
+  openBindUser(socialUserId: String) {
+    console.log("bind user : ",this.provider,socialUserId);
+    const modal = this.modalService.create({
+      nzContent: SocialsProviderBindUserComponent,
+      nzViewContainerRef: this.viewContainerRef,
+      nzComponentParams: {
+        socialUserId: socialUserId,
+        provider: this.provider
+      },
+      nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
+    });
+    // Return a result when closed
+    modal.afterClose.subscribe(result => {
+      if (result.refresh) {
+
+      }
+    });
+
+
+  }
 }

+ 5 - 2
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.html

@@ -82,8 +82,11 @@
       </nz-form-control>
     </nz-form-item>
   </div>
-  <div nz-row style="{{ loginType == 'qrscan' ? '' : 'display: none;' }}">
-    <div class="qrcode" id="div_qrcodelogin"> </div>
+  <div nz-row *ngIf="loginType=='qrscan'">
+    <div class="qrcode" id="div_qrcodelogin" style="background: #fff;padding: 20px;"> </div>
+    <div id="qrexpire" *ngIf="qrexpire" style="width: 100%;text-align: center;background: #fff;padding-bottom: 20px;">
+      二维码过期 <a href="javascript:" (click)="getQrCode()">刷新</a>
+    </div>
   </div>
   <nz-form-item *ngIf="loginType == 'normal' || loginType == 'mobile'">
     <nz-col [nzSpan]="12">

+ 2 - 1
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.less

@@ -1,5 +1,6 @@
 @import '@delon/theme/index';
 
+
 :host {
   display: block;
   width: 368px;
@@ -81,4 +82,4 @@ input{
 
 .qrcode iframe{
   border: 0;
-}
+}

+ 52 - 1
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.ts

@@ -31,6 +31,7 @@ import { ImageCaptchaService } from '../../../service/image-captcha.service';
 import { SocialsProviderService } from '../../../service/socials-provider.service';
 import { CONSTS } from '../../../shared/consts';
 
+
 import { stringify } from 'querystring';
 
 @Component({
@@ -54,6 +55,7 @@ export class UserLoginComponent implements OnInit, OnDestroy {
   loginType = 'normal';
   loading = false;
   passwordVisible = false;
+  qrexpire = false;
   imageCaptcha = '';
   captchaType = '';
   state = '';
@@ -287,6 +289,10 @@ export class UserLoginComponent implements OnInit, OnDestroy {
   }
 
   getQrCode(): void {
+    this.qrexpire = false;
+    if (this.interval$) {
+      clearInterval(this.interval$);
+    }
     this.authnService.clearUser();
     this.socialsProviderService.scanqrcode(this.socials.qrScan).subscribe(res => {
       if (res.code === 0) {
@@ -294,11 +300,14 @@ export class UserLoginComponent implements OnInit, OnDestroy {
           this.qrScanWorkweixin(res.data);
         } else if (this.socials.qrScan === 'dingtalk') {
           this.qrScanDingtalk(res.data);
-        } else if (this.socials.qrScan === 'feishu') {
+        } else if (this.socials.qrScan === 'maxkey') {
+          this.qrScanMaxkey(res.data);
+        }else if (this.socials.qrScan === 'feishu') {
           this.qrScanFeishu(res.data);
         }
       }
     });
+
   }
 
   // #endregion
@@ -364,4 +373,46 @@ export class UserLoginComponent implements OnInit, OnDestroy {
     });
   }
   // #region QR Scan end
+
+  qrScanMaxkey(data: any) {
+    // @ts-ignore
+    document.getElementById("div_qrcodelogin").innerHTML='';
+    // @ts-ignore
+    var qrcode = new QRCode("div_qrcodelogin", {
+      width: 200,
+      height: 200,
+      colorDark : "#000000",
+      colorLight : "#ffffff"
+    }).makeCode(data.state);
+      //3分钟监听二维码
+    this.count = 90;
+    this.interval$ = setInterval(() => {
+      this.count -= 1;
+      if(this.loginType != 'qrscan') {
+        clearInterval(this.interval$);
+      }
+      if (this.count <= 0) {
+        clearInterval(this.interval$);
+      }
+      //轮询发送监听请求
+      this.socialsProviderService.qrcallback(this.socials.qrScan,data.state).subscribe(res => {
+        if (res.code === 0) {
+          // 清空路由复用信息
+          this.reuseTabService.clear();
+          // 设置用户Token信息
+          this.authnService.auth(res.data);
+          this.authnService.navigate({});
+          clearInterval(this.interval$);
+        } else if (res.code === 102) {
+          // 二维码过期
+          clearInterval(this.interval$);
+          this.qrexpire = true;
+          this.cdr.detectChanges();
+        } else if (res.code === 103) {
+          // 暂无用户扫码
+        }
+      });
+      this.cdr.detectChanges();
+    }, 2000);
+  }
 }

+ 2 - 2
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/passport.module.ts

@@ -17,8 +17,8 @@
 import { NgModule } from '@angular/core';
 import { SharedModule } from '@shared';
 import { NzStepsModule } from 'ng-zorro-antd/steps';
-
 import { CallbackComponent } from './callback.component';
+import { SocialsProviderBindUserComponent } from './socials-provider-bind-user/socials-provider-bind-user.component';
 import { ForgotComponent } from './forgot/forgot.component';
 import { UserLockComponent } from './lock/lock.component';
 import { UserLoginComponent } from './login/login.component';
@@ -26,7 +26,7 @@ import { PassportRoutingModule } from './passport-routing.module';
 import { UserRegisterResultComponent } from './register-result/register-result.component';
 import { UserRegisterComponent } from './register/register.component';
 
-const COMPONENTS = [UserLoginComponent, UserRegisterResultComponent, UserRegisterComponent, UserLockComponent, CallbackComponent];
+const COMPONENTS = [SocialsProviderBindUserComponent,UserLoginComponent, UserRegisterResultComponent, UserRegisterComponent, UserLockComponent, CallbackComponent];
 
 @NgModule({
   imports: [SharedModule, PassportRoutingModule, NzStepsModule],

+ 30 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.html

@@ -0,0 +1,30 @@
+<div *nzModalTitle>绑定</div>
+<div>
+  <form nz-form [formGroup]="formGroup" (ngSubmit)="onSubmit($event)" se-container="1">
+    <nz-form-item style="width: 100%">
+      <nz-form-control nzErrorTip="Please input your telephone!">
+        <nz-input-group nzSize="large" nzPrefixIcon="user">
+          <input nz-input formControlName="telephone" placeholder="{{ 'mxk.login.text.mobile' | i18n }}" />
+        </nz-input-group>
+      </nz-form-control>
+    </nz-form-item>
+    <nz-form-item style="width: 100%">
+      <nz-form-control nzErrorTip="Please input your verification code!">
+        <nz-input-group nzSize="large" nzPrefixIcon="mail" nzSearch [nzAddOnAfter]="suffixSendOtpCodeButton">
+          <input nz-input formControlName="verificationCode" placeholder="{{ 'mxk.login.text.captcha' | i18n }}" />
+        </nz-input-group>
+        <ng-template #suffixSendOtpCodeButton>
+          <button type="button" nz-button nzSize="large" (click)="sendOtpCode()" [disabled]="count > 0" nzBlock [nzLoading]="loading">
+            {{ count ? count + 's' : ('app.register.get-verification-code' | i18n) }}
+          </button>
+        </ng-template>
+      </nz-form-control>
+    </nz-form-item>
+  </form>
+</div>
+
+
+<div *nzModalFooter>
+  <button nz-button nzType="default" (click)="onClose($event)">{{ 'mxk.text.close' | i18n }}</button>
+  <button nz-button nzType="primary" (click)="onSubmit($event)">{{ 'mxk.text.submit' | i18n }}</button>
+</div>

+ 0 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.less


+ 42 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.spec.ts

@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SocialsProviderBindUserComponent } from './socials-provider-bind-user.component';
+
+describe('SocialsProviderBindUserComponent', () => {
+  let component: SocialsProviderBindUserComponent;
+  let fixture: ComponentFixture<SocialsProviderBindUserComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ SocialsProviderBindUserComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(SocialsProviderBindUserComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 124 - 0
maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/socials-provider-bind-user/socials-provider-bind-user.component.ts

@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+import { Component, ChangeDetectorRef, Input, OnInit, Inject } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { I18NService } from '@core';
+import { _HttpClient, ALAIN_I18N_TOKEN, SettingsService } from '@delon/theme';
+import format from 'date-fns/format';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal';
+import { AuthnService } from "../../../service/authn.service";
+import { SocialsProviderService } from "../../../service/socials-provider.service";
+import { ReuseTabService } from "@delon/abc/reuse-tab";
+
+@Component({
+  selector: 'app-socials-provider-binduser',
+  templateUrl: './socials-provider-bind-user.component.html',
+  styles: [
+    `
+      nz-form-item {
+        width: 100%;
+      }
+    `
+  ],
+  styleUrls: ['./socials-provider-bind-user.component.less']
+})
+export class SocialsProviderBindUserComponent implements OnInit {
+  @Input() socialUserId?: String;
+  @Input() provider?: String;
+  loading = false;
+  count = 0;
+  formGroup: FormGroup = new FormGroup({});
+  interval$: any;
+  constructor(
+    private modalRef: NzModalRef,
+    private fb: FormBuilder,
+    private authnService: AuthnService,
+    private msg: NzMessageService,
+    private socialsProviderService: SocialsProviderService,
+    @Inject(ALAIN_I18N_TOKEN) private i18n: I18NService,
+    @Inject(ReuseTabService)
+    private reuseTabService: ReuseTabService,
+    private cdr: ChangeDetectorRef
+  ) {}
+
+  ngOnInit(): void {
+    this.formGroup = this.fb.group({
+      telephone: [null, [Validators.required]],
+      verificationCode: [null, [Validators.required]]
+    });
+    console.log("bind open form : ",this.provider,this.socialUserId)
+  }
+
+  onClose(e: MouseEvent): void {
+    e.preventDefault();
+    this.modalRef.destroy({ refresh: false });
+  }
+
+  onSubmit(e: MouseEvent): void {
+    console.log("this.formGroup.valid",this.formGroup.valid)
+    //表单验证
+    if (this.formGroup.valid) {
+      let request = {
+        username: this.socialUserId,
+        mobile: this.formGroup.get('telephone')?.value,
+        code: this.formGroup.get('verificationCode')?.value,
+        authType: this.provider
+      }
+      this.authnService.bindSocialsUser(request).subscribe(res => {
+        if (res.code === 0) {
+          // 清空路由复用信息
+          this.reuseTabService.clear();
+          // 设置用户Token信息
+          this.authnService.auth(res.data);
+          this.authnService.navigate({});
+        } else {
+          this.msg.error(`绑定失败`);
+        }
+      });
+
+    } else {
+      Object.values(this.formGroup.controls).forEach(control => {
+        if (control.invalid) {
+          control.markAsDirty();
+          control.updateValueAndValidity({ onlySelf: true });
+        }
+      });
+    }
+
+    e.preventDefault();
+  }
+
+  sendOtpCode(): void {
+
+    this.authnService.produceOtp({ mobile:this.formGroup.get('telephone')?.value}).subscribe(res => {
+      if (res.code !== 0) {
+        this.msg.error(`发送失败`);
+      }else {
+        this.msg.success(`发送成功`);
+      }
+    });
+    this.count = 59;
+    this.interval$ = setInterval(() => {
+      this.count -= 1;
+      if (this.count <= 0) {
+        clearInterval(this.interval$);
+      }
+      this.cdr.detectChanges();
+    }, 1000);
+  }
+}

+ 4 - 0
maxkey-web-frontend/maxkey-web-app/src/app/service/authn.service.ts

@@ -61,6 +61,10 @@ export class AuthnService {
     return this.http.post('/login/signin?_allow_anonymous=true', authParam);
   }
 
+  bindSocialsUser(authParam: any) {
+    return this.http.post('/login/signin/bindusersocials?_allow_anonymous=true', authParam);
+  }
+
   //退出
   logout() {
     this.cookieService.delete(CONSTS.CONGRESS, '/');

+ 8 - 0
maxkey-web-frontend/maxkey-web-app/src/app/service/socials-provider.service.ts

@@ -43,7 +43,15 @@ export class SocialsProviderService extends BaseService<SocialsProvider> {
     return this.getByParams(params, `/logon/oauth20/callback/${provider}?_allow_anonymous=true`);
   }
 
+  bindUser(provider: string, params: NzSafeAny): Observable<Message<SocialsProvider>> {
+    return this.getByParams(params, `/logon/oauth20/binduser/${provider}?_allow_anonymous=true`);
+  }
+
   bind(provider: string, params: NzSafeAny): Observable<Message<SocialsProvider>> {
     return this.getByParams(params, `/logon/oauth20/bind/${provider}?_allow_anonymous=true`);
   }
+
+  qrcallback(provider: string, token: string ): Observable<Message<SocialsProvider>> {
+    return this.getByParams({}, `/logon/oauth20/qrcallback/${provider}/${token}?_allow_anonymous=true`);
+  }
 }

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 146 - 0
maxkey-web-frontend/maxkey-web-app/src/assets/qrcode/qrcode.min.js


+ 1 - 1
maxkey-web-frontend/maxkey-web-app/src/environments/environment.ts

@@ -27,7 +27,7 @@ export const environment = {
   production: false,
   useHash: true,
   api: {
-    baseUrl: 'http://sso.maxkey.top:9527/sign/',
+    baseUrl: '/sign/',
     refreshTokenEnabled: true,
     refreshTokenType: 're-request'
   },

+ 4 - 3
maxkey-web-frontend/maxkey-web-app/src/index.html

@@ -27,6 +27,7 @@
   <meta http-equiv="description" content="MaxKey Single Sign-On">
   <link rel="icon" type="image/x-icon" href="favicon.ico">
   <script src="./assets/transform.js"></script>
+  <script src="./assets/qrcode/qrcode.min.js"></script>
   <!-- Apple Touch Icon -->
   <!-- <link rel="apple-touch-icon" href="custom-icon.png"> -->
   <style type="text/css">
@@ -142,9 +143,9 @@
   </div>
 </body>
 <!--attention http or https-->
-<!--企业微信
+--企业微信
 <script src="http://wwcdn.weixin.qq.com/node/wework/wwopen/js/wwLogin-1.2.7.js"></script>
--->
+
 <!--钉钉-->
 <!---->
 <script src="http://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
@@ -191,4 +192,4 @@
 </script>
 -->
 
-</html>
+</html>

+ 36 - 6
maxkey-webs/maxkey-web-maxkey/build.gradle

@@ -1,7 +1,38 @@
+buildscript {
+	repositories {
+		maven { url 'https://maven.aliyun.com/nexus/content/groups/public/'}
+	}
+	dependencies {
+		//springboot jar
+		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
+	}
+}
+
+plugins {
+	id 'java'
+	id "io.spring.dependency-management" version "1.0.11.RELEASE"
+	id 'org.springframework.boot' version "${springBootVersion}"
+}
+
+apply plugin: 'io.spring.dependency-management'
+
 description = "maxkey-web-maxkey"
 
-//add support for Java
-apply plugin: 'java'
+bootJar {
+	dependsOn jar
+	baseName = 'maxkey-boot'
+    version = "${project.version}-ga"
+    mainClass = 'org.maxkey.MaxKeyApplication'
+	manifest {
+	        attributes(
+						"Implementation-Title": project.name,
+	                	"Implementation-Vendor": project.vendor,
+	                	"Created-By": project.author,
+	                	"Implementation-Date": java.time.ZonedDateTime.now(),
+	                	"Implementation-Version": project.version
+	           )
+	    }	
+}
 
 dependencies {
 	implementation project(":maxkey-common")
@@ -12,8 +43,8 @@ dependencies {
    	implementation project(":maxkey-authentications:maxkey-authentication-social")
    	implementation project(":maxkey-authentications:maxkey-authentication-captcha")
    	implementation project(":maxkey-authentications:maxkey-authentication-otp")
-   	implementation project(":maxkey-authentications:maxkey-authentication-provider")
-   	implementation project(":maxkey-authentications:maxkey-authentication-sms")
+	implementation project(":maxkey-authentications:maxkey-authentication-provider")
+	implementation project(":maxkey-authentications:maxkey-authentication-sms")
    	
    	implementation project(":maxkey-protocols:maxkey-protocol-authorize")
    	implementation project(":maxkey-protocols:maxkey-protocol-cas")
@@ -23,5 +54,4 @@ dependencies {
    	implementation project(":maxkey-protocols:maxkey-protocol-oauth-2.0")
    	implementation project(":maxkey-protocols:maxkey-protocol-saml-2.0")
 	implementation project(":maxkey-protocols:maxkey-protocol-jwt")
-	
-}
+}

+ 47 - 4
maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/LoginEntryPoint.java

@@ -34,12 +34,11 @@ import org.maxkey.authn.support.rememberme.AbstractRemeberMeManager;
 import org.maxkey.authn.support.rememberme.RemeberMe;
 import org.maxkey.authn.support.socialsignon.service.SocialSignOnProviderService;
 import org.maxkey.configuration.ApplicationConfig;
-import org.maxkey.entity.Institutions;
-import org.maxkey.entity.Message;
-import org.maxkey.entity.UserInfo;
+import org.maxkey.constants.ConstsLoginType;
+import org.maxkey.entity.*;
 import org.maxkey.password.onetimepwd.AbstractOtpAuthn;
-import org.maxkey.password.onetimepwd.MailOtpAuthnService;
 import org.maxkey.password.sms.SmsOtpAuthnService;
+import org.maxkey.persistence.service.SocialsAssociatesService;
 import org.maxkey.persistence.service.UserInfoService;
 import org.maxkey.web.WebConstants;
 import org.maxkey.web.WebContext;
@@ -81,6 +80,9 @@ public class LoginEntryPoint {
 
 	@Autowired
 	SocialSignOnProviderService socialSignOnProviderService;
+
+	@Autowired
+	SocialsAssociatesService socialsAssociatesService;
 	
 	@Autowired
 	KerberosService kerberosService;
@@ -165,6 +167,47 @@ public class LoginEntryPoint {
         
         return new Message<AuthJwt>(Message.FAIL).buildResponse();
     }
+
+	@RequestMapping(value={"/signin/bindusersocials"}, produces = {MediaType.APPLICATION_JSON_VALUE})
+	public ResponseEntity<?> bindusersocials(@RequestBody LoginCredential credential) {
+		//短信验证码
+		String code = credential.getCode();
+		//映射社交服务的账号
+		String username = credential.getUsername();
+		//maxkey存储的手机号
+		String mobile = credential.getMobile();
+		//社交服务类型
+		String authType = credential.getAuthType();
+
+		UserInfo userInfo = userInfoService.findByEmailMobile(mobile);
+		//验证码验证是否合法
+		if (smsAuthnService.getByInstId(WebContext.getInst().getId()).validate(userInfo,code)) {
+			//合法进行用户绑定
+			SocialsAssociate socialsAssociate = new SocialsAssociate();
+			socialsAssociate.setUserId(userInfo.getId());
+			socialsAssociate.setUsername(userInfo.getUsername());
+			socialsAssociate.setProvider(authType);
+			socialsAssociate.setSocialUserId(username);
+			socialsAssociate.setInstId(userInfo.getInstId());
+			//插入Maxkey和社交服务的用户映射表
+			socialsAssociatesService.insert(socialsAssociate);
+
+			//设置完成后,进行登录认证
+			LoginCredential loginCredential =new LoginCredential(
+					socialsAssociate.getUsername(),"", ConstsLoginType.SOCIALSIGNON);
+
+			SocialsProvider socialSignOnProvider = socialSignOnProviderService.get(socialsAssociate.getInstId(),socialsAssociate.getProvider());
+
+			loginCredential.setProvider(socialSignOnProvider.getProviderName());
+
+			Authentication  authentication = authenticationProvider.authenticate(loginCredential,true);
+
+			return new Message<AuthJwt>(authTokenService.genAuthJwt(authentication)).buildResponse();
+
+		}
+		return new Message<AuthJwt>(Message.FAIL).buildResponse();
+	}
+
  	
  	/**
  	 * normal

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.