From b0444c0e7590cc773db8313e89afd7660b3cba27 Mon Sep 17 00:00:00 2001 From: Jeff Tian Date: Thu, 28 Mar 2024 13:17:24 +0800 Subject: [PATCH] feat: move endpoint out of the parent class to a independent class --- pom.xml | 2 +- .../org/keycloak/social/weixin/Endpoint.java | 170 ++++++++++++++++++ .../social/weixin/WeiXinIdentityProvider.java | 156 +--------------- 3 files changed, 175 insertions(+), 153 deletions(-) create mode 100644 src/main/java/org/keycloak/social/weixin/Endpoint.java diff --git a/pom.xml b/pom.xml index 5c29e26..fbfac09 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.keycloak keycloak-services-social-weixin jar - 0.5.27 + 0.5.28 Keycloak Services Social WeiXin Keycloak 微信登录插件,支持 PC 端扫码登录、关注公众号即登录、手机微信等登录方式。 diff --git a/src/main/java/org/keycloak/social/weixin/Endpoint.java b/src/main/java/org/keycloak/social/weixin/Endpoint.java new file mode 100644 index 0000000..9c5fe42 --- /dev/null +++ b/src/main/java/org/keycloak/social/weixin/Endpoint.java @@ -0,0 +1,170 @@ +package org.keycloak.social.weixin; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import org.keycloak.OAuth2Constants; +import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; +import org.keycloak.broker.provider.BrokeredIdentityContext; +import org.keycloak.broker.provider.IdentityProvider; +import org.keycloak.broker.provider.util.SimpleHttp; +import org.keycloak.common.ClientConnection; +import org.keycloak.events.Errors; +import org.keycloak.events.EventBuilder; +import org.keycloak.events.EventType; +import org.keycloak.models.RealmModel; +import org.keycloak.services.ErrorPage; +import org.keycloak.services.messages.Messages; +import org.keycloak.social.weixin.helpers.UserAgentHelper; +import org.keycloak.social.weixin.helpers.WMPHelper; + +import java.util.Map; +import java.util.Objects; + +public class Endpoint extends WeiXinIdentityProvider { + private final WeiXinIdentityProvider weiXinIdentityProvider; + protected IdentityProvider.AuthenticationCallback callback; + protected RealmModel realm; + protected EventBuilder event; + + @Context + protected ClientConnection clientConnection; + + @Context + protected org.keycloak.http.HttpRequest request; + + public Endpoint(WeiXinIdentityProvider weiXinIdentityProvider, IdentityProvider.AuthenticationCallback callback, RealmModel realm, EventBuilder event) { + super(weiXinIdentityProvider.session, weiXinIdentityProvider.getConfig()); + + this.weiXinIdentityProvider = weiXinIdentityProvider; + this.callback = callback; + this.realm = realm; + this.event = event; + } + + @GET + public Response authResponse(@QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_STATE) String state, + @QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_CODE) String authorizationCode, @QueryParam(OAuth2Constants.ERROR) String error, @QueryParam(OAuth2Constants.SCOPE_OPENID) String openid, @QueryParam("clientId") String clientId, @QueryParam("tabId") String tabId) { + AbstractOAuth2IdentityProvider.logger.info(String.format("OAUTH2_PARAMETER_CODE = %s, %s, %s, %s, %s", authorizationCode, error, openid, clientId, tabId)); + var wechatLoginType = WechatLoginType.FROM_PC_QR_CODE_SCANNING; + + String ua = weiXinIdentityProvider.session.getContext().getRequestHeaders().getHeaderString("user-agent").toLowerCase(); + if (UserAgentHelper.isWechatBrowser(ua)) { + AbstractOAuth2IdentityProvider.logger.info("user-agent=wechat"); + wechatLoginType = WechatLoginType.FROM_WECHAT_BROWSER; + } + + if (error != null) { + if (error.equals(AbstractOAuth2IdentityProvider.ACCESS_DENIED)) { + AbstractOAuth2IdentityProvider.logger.error(AbstractOAuth2IdentityProvider.ACCESS_DENIED + " for broker login " + weiXinIdentityProvider.getConfig().getProviderId() + " " + state); + return callback.cancelled(weiXinIdentityProvider.getConfig()); + } else { + AbstractOAuth2IdentityProvider.logger.error(error + " for broker login " + weiXinIdentityProvider.getConfig().getProviderId()); + return callback.error(state + " " + Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR); + } + } + + try { + BrokeredIdentityContext federatedIdentity; + + if (openid != null) { + // TODO: use ticket here instead, and then use this ticket to get openid from sso.jiwai.win + federatedIdentity = weiXinIdentityProvider.customAuth.auth(openid, wechatLoginType); + + setFederatedIdentity(state, federatedIdentity, weiXinIdentityProvider.customAuth.accessToken); + + return authenticated(federatedIdentity); + } + + if (authorizationCode != null) { + if (state == null) { + wechatLoginType = WechatLoginType.FROM_WECHAT_MINI_PROGRAM; + AbstractOAuth2IdentityProvider.logger.info("response from wmp with code = " + authorizationCode); + } + + var responses = generateTokenRequest(authorizationCode, wechatLoginType); + var response = responses[0].asString(); + var accessTokenResponse = responses[1] != null ? responses[1].asString() : ""; + AbstractOAuth2IdentityProvider.logger.info("response from auth code = " + response + ", " + accessTokenResponse); + federatedIdentity = weiXinIdentityProvider.getFederatedIdentity(response, wechatLoginType, accessTokenResponse); + + setFederatedIdentity(Objects.requireNonNullElse(state, WMPHelper.createStateForWMP(clientId, tabId)), federatedIdentity, response); + + return authenticated(federatedIdentity); + } + } catch (WebApplicationException e) { + return e.getResponse(); + } catch (Exception e) { + AbstractOAuth2IdentityProvider.logger.error("Failed to make identity provider (weixin) oauth callback", e); + } + event.event(EventType.LOGIN); + event.error(Errors.IDENTITY_PROVIDER_LOGIN_FAILURE); + return ErrorPage.error(weiXinIdentityProvider.session, null, Response.Status.BAD_GATEWAY, + Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR); + } + + private Response authenticated(BrokeredIdentityContext federatedIdentity) { + var weiXinIdentityBrokerService = + new WeiXinIdentityBrokerService(realm); + weiXinIdentityBrokerService.init(weiXinIdentityProvider.session, clientConnection, event, request); + + return weiXinIdentityBrokerService.authenticated(federatedIdentity); + } + + public void setFederatedIdentity(@QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_STATE) String state, BrokeredIdentityContext federatedIdentity, String accessToken) { + if (weiXinIdentityProvider.getConfig().isStoreToken()) { + if (federatedIdentity.getToken() == null) + federatedIdentity.setToken(accessToken); + } + + federatedIdentity.setIdpConfig(weiXinIdentityProvider.getConfig()); + federatedIdentity.setIdp(weiXinIdentityProvider); + federatedIdentity.setContextData(Map.of("state", Objects.requireNonNullElse(state, "wmp"))); + } + + public SimpleHttp[] generateTokenRequest(String authorizationCode, WechatLoginType wechatLoginType) { + AbstractOAuth2IdentityProvider.logger.info(String.format("generateTokenRequest, code = %s, loginType = %s", authorizationCode, wechatLoginType)); + if (WechatLoginType.FROM_WECHAT_BROWSER.equals(wechatLoginType)) { + var mobileMpClientId = weiXinIdentityProvider.getConfig().getClientId(); + var mobileMpClientSecret = weiXinIdentityProvider.getConfig().getClientSecret(); + + AbstractOAuth2IdentityProvider.logger.info(String.format("from wechat browser, posting to %s for fetching token, with mobileMpClientId = %s, mobileMpClientSecret = %s", weiXinIdentityProvider.getConfig().getTokenUrl(), mobileMpClientId, mobileMpClientSecret)); + + return new SimpleHttp[]{SimpleHttp.doPost(weiXinIdentityProvider.getConfig().getTokenUrl(), weiXinIdentityProvider.session) + .param(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_CODE, authorizationCode) + .param(WeiXinIdentityProvider.OAUTH2_PARAMETER_CLIENT_ID, mobileMpClientId) + .param(WeiXinIdentityProvider.OAUTH2_PARAMETER_CLIENT_SECRET, mobileMpClientSecret) + .param(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_REDIRECT_URI, weiXinIdentityProvider.getConfig().getConfig().get(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_REDIRECT_URI)) + .param(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_GRANT_TYPE, AbstractOAuth2IdentityProvider.OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE), null}; + } + + if (WechatLoginType.FROM_WECHAT_MINI_PROGRAM.equals(wechatLoginType)) { + AbstractOAuth2IdentityProvider.logger.info(String.format("from wechat mini program, posting to %s", WeiXinIdentityProvider.WMP_AUTH_URL)); + var wechatSession = SimpleHttp.doGet(WeiXinIdentityProvider.WMP_AUTH_URL, weiXinIdentityProvider.session).param(WeiXinIdentityProvider.OAUTH2_PARAMETER_CLIENT_ID, weiXinIdentityProvider.getConfig().getConfig().get(WeiXinIdentityProvider.WMP_APP_ID)).param(WeiXinIdentityProvider.OAUTH2_PARAMETER_CLIENT_SECRET, weiXinIdentityProvider.getConfig().getConfig().get(WeiXinIdentityProvider.WMP_APP_SECRET)).param("js_code", authorizationCode).param(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_GRANT_TYPE, AbstractOAuth2IdentityProvider.OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE); + + var tokenRes = SimpleHttp.doGet(String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" + + "&appid=%s&secret=%s", weiXinIdentityProvider.getConfig().getConfig().get(WeiXinIdentityProvider.WMP_APP_ID), weiXinIdentityProvider.getConfig().getConfig().get(WeiXinIdentityProvider.WMP_APP_SECRET)), + weiXinIdentityProvider.session); + + return new SimpleHttp[]{wechatSession, tokenRes}; + } + + var isOpenClientEnabled = weiXinIdentityProvider.getConfig().getConfig().get(WeiXinIdentityProvider.OPEN_CLIENT_ENABLED); + + if (isOpenClientEnabled.equals("true")) { + return new SimpleHttp[]{SimpleHttp.doPost(weiXinIdentityProvider.getConfig().getTokenUrl(), weiXinIdentityProvider.session).param(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_CODE, authorizationCode) + .param(WeiXinIdentityProvider.OAUTH2_PARAMETER_CLIENT_ID, weiXinIdentityProvider.getConfig().getConfig().get(WeiXinIdentityProvider.OPEN_CLIENT_ID)) + .param(WeiXinIdentityProvider.OAUTH2_PARAMETER_CLIENT_SECRET, weiXinIdentityProvider.getConfig().getConfig().get(WeiXinIdentityProvider.OPEN_CLIENT_SECRET)) + .param(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_REDIRECT_URI, weiXinIdentityProvider.getConfig().getConfig().get(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_REDIRECT_URI)) + .param(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_GRANT_TYPE, AbstractOAuth2IdentityProvider.OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE), null}; + } + + return new SimpleHttp[]{SimpleHttp.doPost(weiXinIdentityProvider.getConfig().getTokenUrl(), weiXinIdentityProvider.session).param(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_CODE, authorizationCode) + .param(WeiXinIdentityProvider.OAUTH2_PARAMETER_CLIENT_ID, weiXinIdentityProvider.getConfig().getClientId()) + .param(WeiXinIdentityProvider.OAUTH2_PARAMETER_CLIENT_SECRET, weiXinIdentityProvider.getConfig().getClientSecret()) + .param(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_REDIRECT_URI, weiXinIdentityProvider.getConfig().getConfig().get(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_REDIRECT_URI)) + .param(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_GRANT_TYPE, AbstractOAuth2IdentityProvider.OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE), null}; + } +} diff --git a/src/main/java/org/keycloak/social/weixin/WeiXinIdentityProvider.java b/src/main/java/org/keycloak/social/weixin/WeiXinIdentityProvider.java index e0752d9..c5d403c 100644 --- a/src/main/java/org/keycloak/social/weixin/WeiXinIdentityProvider.java +++ b/src/main/java/org/keycloak/social/weixin/WeiXinIdentityProvider.java @@ -18,13 +18,9 @@ import java.io.IOException; import java.net.URI; -import java.util.Map; import java.util.Objects; import java.util.UUID; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.*; import org.keycloak.OAuth2Constants; import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; @@ -35,15 +31,10 @@ import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.social.SocialIdentityProvider; -import org.keycloak.common.ClientConnection; -import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; -import org.keycloak.events.EventType; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.protocol.oidc.OIDCLoginProtocol; -import org.keycloak.services.ErrorPage; -import org.keycloak.services.messages.Messages; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -52,7 +43,6 @@ import org.keycloak.social.weixin.egress.wechat.mp.models.Scene; import org.keycloak.social.weixin.egress.wechat.mp.models.TicketRequest; import org.keycloak.social.weixin.helpers.UserAgentHelper; -import org.keycloak.social.weixin.helpers.WMPHelper; public class WeiXinIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider { @@ -84,6 +74,7 @@ public class WeiXinIdentityProvider extends AbstractOAuth2IdentityProvider