Переглянути джерело

提交社交服务绑定账号+短信验证过程

shibanglin 2 роки тому
батько
коміт
3ed7c987fb

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