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

This commit is contained in:
shibanglin
2023-02-13 13:46:54 +08:00
parent 24a9c341e1
commit 3ed7c987fb
6 changed files with 354 additions and 23 deletions

View File

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

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

@@ -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();
}
}
}

View File

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

View File

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

View File

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