diff --git a/maxkey-authentications/maxkey-authentication-social/src/main/java/me/zhyd/oauth/request/AuthMaxkeyRequest.java b/maxkey-authentications/maxkey-authentication-social/src/main/java/me/zhyd/oauth/request/AuthMaxkeyRequest.java new file mode 100644 index 000000000..9c2ba3b40 --- /dev/null +++ b/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; + } +} diff --git a/maxkey-authentications/maxkey-authentication-social/src/main/java/me/zhyd/oauth/request/MaxkeyAuthDefaultSource.java b/maxkey-authentications/maxkey-authentication-social/src/main/java/me/zhyd/oauth/request/MaxkeyAuthDefaultSource.java new file mode 100644 index 000000000..d0bb19ce0 --- /dev/null +++ b/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 getTargetClass() { + return AuthHuaweiWeLinkRequest.class; + } + } +} diff --git a/maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/SocialSignOnEndpoint.java b/maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/SocialSignOnEndpoint.java index 79b61334a..46ca58eb0 100644 --- a/maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/SocialSignOnEndpoint.java +++ b/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)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(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(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(Message.ERROR).buildResponse(); - } + _logger.debug("Loaded SocialSignOn Socials Associate : "+socialssssociate1); + if (null == socialssssociate1) { + //如果存在第三方ID并且在数据库无法找到映射关系,则进行绑定逻辑 + if (StringUtils.isNotEmpty(socialsAssociate.getSocialUserId())) { + //返回message为第三方用户标识 + return new Message(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(Message.ERROR).buildResponse(); } } + + + /** + * 提供给第三方应用关联用户接口 + * @return + */ + @RequestMapping(value={"/workweixin/qr/auth/login"}, method = {RequestMethod.POST}) + public ResponseEntity qrAuthLogin( + @RequestParam Map param, + HttpServletRequest request) { + + try { + if (null == param){ + return new Message(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().buildResponse(); + } + } else { + return new Message(Message.WARNING,"Invalid token").buildResponse(); + } + }catch(Exception e) { + _logger.error("qrAuthLogin Exception ",e); + } + return new Message(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(Message.ERROR).buildResponse(); + } + + String loginName = socialSignOnProviderService.getToken(state); + if (StringUtils.isEmpty(loginName)) { + //二维码过期 + return new Message(Message.PROMPT).buildResponse(); + } + if("-1".equalsIgnoreCase(loginName)){ + //暂无用户扫码 + return new Message(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(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(authTokenService.genAuthJwt(authentication)).buildResponse(); + }catch(Exception e) { + _logger.error("qrcallback Exception ",e); + return new Message(Message.ERROR).buildResponse(); + } + } } diff --git a/maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/service/SocialSignOnProviderService.java b/maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/service/SocialSignOnProviderService.java index 8f0422713..6624ff3c3 100644 --- a/maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/service/SocialSignOnProviderService.java +++ b/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{ HashMapsocialSignOnProviderMaps = new HashMap(); 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; + } } diff --git a/maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/token/RedisTokenStore.java b/maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/authn/support/socialsignon/token/RedisTokenStore.java new file mode 100644 index 000000000..891f20811 --- /dev/null +++ b/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 tokenStore = new ConcurrentHashMap(); + + 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; + } + +} diff --git a/maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/autoconfigure/SocialSignOnAutoConfiguration.java b/maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/autoconfigure/SocialSignOnAutoConfiguration.java index c29b80b18..a100d59db 100644 --- a/maxkey-authentications/maxkey-authentication-social/src/main/java/org/maxkey/autoconfigure/SocialSignOnAutoConfiguration.java +++ b/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; }