diff --git a/pom.xml b/pom.xml index 0a9466b..20ac713 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.keycloak keycloak-services-social-weixin - 0.5.0 + 0.5.1 Keycloak Services Social WeiXin diff --git a/src/main/java/org/keycloak/social/weixin/WeiXinIdentityProvider.java b/src/main/java/org/keycloak/social/weixin/WeiXinIdentityProvider.java index b13b97b..3d3a0a6 100644 --- a/src/main/java/org/keycloak/social/weixin/WeiXinIdentityProvider.java +++ b/src/main/java/org/keycloak/social/weixin/WeiXinIdentityProvider.java @@ -47,6 +47,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.keycloak.social.weixin.egress.wechat.mp.WechatMpApi; +import org.keycloak.social.weixin.egress.wechat.mp.models.ActionInfo; +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; @@ -234,12 +238,19 @@ protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) { return uriBuilder; } else { - // 未启用开放平台,且未配置自定义登录页面,则返回一个 html 页面,展示带参二维码 - uriBuilder = UriBuilder.fromUri(config.getAuthorizationUrl()); - uriBuilder.queryParam(OAUTH2_PARAMETER_SCOPE, config.getDefaultScope()) - .queryParam(OAUTH2_PARAMETER_STATE, request.getState().getEncoded()) - .queryParam(OAUTH2_PARAMETER_CLIENT_ID, config.getClientId()) - .queryParam(OAUTH2_PARAMETER_REDIRECT_URI, request.getRedirectUri()); + logger.info("未启用开放平台,且未配置自定义登录页面,则返回一个 html 页面,展示带参二维码"); + uriBuilder = UriBuilder.fromUri("/realms/" + request.getRealm().getName() + "/QrCodeResourceProviderFactory/mp-qr"); + + var wechatApi = new WechatMpApi( + config.getConfig().get(WECHAT_MP_APP_ID), + config.getConfig().get(WECHAT_MP_APP_SECRET), + session + ); + + var ticketUrl = wechatApi.createTmpQrCode(new TicketRequest(2592000, "QR_STR_SCENE", new ActionInfo(new Scene("1")))).url; + logger.info("ticketUrl = " + ticketUrl); + + uriBuilder.queryParam("ticket-url", ticketUrl); } } else { uriBuilder = UriBuilder.fromUri(config.getAuthorizationUrl()); diff --git a/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/WechatMpApi.java b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/WechatMpApi.java new file mode 100644 index 0000000..8fa8c40 --- /dev/null +++ b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/WechatMpApi.java @@ -0,0 +1,48 @@ +package org.keycloak.social.weixin.egress.wechat.mp; + +import lombok.SneakyThrows; +import org.jboss.logging.Logger; +import org.keycloak.broker.provider.util.SimpleHttp; +import org.keycloak.models.KeycloakSession; +import org.keycloak.social.weixin.egress.wechat.mp.models.AccessTokenResponse; +import org.keycloak.social.weixin.egress.wechat.mp.models.TicketRequest; +import org.keycloak.social.weixin.egress.wechat.mp.models.TicketResponse; + + +public class WechatMpApi { + private static final Logger logger = Logger.getLogger(WechatMpApi.class); + private final String appSecret; + private final String appId; + protected final KeycloakSession session; + + public WechatMpApi(String appId, String appSecret, KeycloakSession session) { + this.appId = appId; + this.appSecret = appSecret; + this.session = session; + } + + @SneakyThrows + public AccessTokenResponse getAccessToken(String appId, String appSecret) { + logger.info(String.format("getAccessToken by %s%n%s%n", appId, appSecret)); + var res = + SimpleHttp.doGet(String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" + + "&appid=%s&secret=%s", appId, appSecret), + session).asJson(AccessTokenResponse.class); + + logger.info(String.format("res is %s%n", res)); + + return res; + } + + @SneakyThrows + public TicketResponse createTmpQrCode(TicketRequest ticketRequest) { + logger.info(String.format("createTmpQrCode by %s%n", ticketRequest)); + + var res = SimpleHttp.doPost("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + this.getAccessToken(appId, appSecret).access_token, + session).json(ticketRequest).asJson(TicketResponse.class); + + logger.info(String.format("res is %s%n", res)); + + return res; + } +} diff --git a/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/AccessTokenRequestBody.java b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/AccessTokenRequestBody.java similarity index 83% rename from src/main/java/org/keycloak/social/weixin/egress/wechat/mp/AccessTokenRequestBody.java rename to src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/AccessTokenRequestBody.java index a9364dd..7688d33 100644 --- a/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/AccessTokenRequestBody.java +++ b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/AccessTokenRequestBody.java @@ -1,4 +1,4 @@ -package org.keycloak.social.weixin.egress.wechat.mp; +package org.keycloak.social.weixin.egress.wechat.mp.models; public class AccessTokenRequestBody { public String grant_type; diff --git a/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/AccessTokenResponse.java b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/AccessTokenResponse.java new file mode 100644 index 0000000..759c8b7 --- /dev/null +++ b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/AccessTokenResponse.java @@ -0,0 +1,6 @@ +package org.keycloak.social.weixin.egress.wechat.mp.models; + +public class AccessTokenResponse { + public String access_token; + public String expires_in; +} diff --git a/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/ActionInfo.java b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/ActionInfo.java new file mode 100644 index 0000000..eed270a --- /dev/null +++ b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/ActionInfo.java @@ -0,0 +1,9 @@ +package org.keycloak.social.weixin.egress.wechat.mp.models; + +public class ActionInfo { + public Scene scene; + + public ActionInfo(Scene scene) { + this.scene = scene; + } +} diff --git a/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/Scene.java b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/Scene.java new file mode 100644 index 0000000..59b71d4 --- /dev/null +++ b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/Scene.java @@ -0,0 +1,9 @@ +package org.keycloak.social.weixin.egress.wechat.mp.models; + +public class Scene { + public String scene_str; + + public Scene(String scene_str) { + this.scene_str = scene_str; + } +} diff --git a/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/TicketRequest.java b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/TicketRequest.java new file mode 100644 index 0000000..6d993a4 --- /dev/null +++ b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/TicketRequest.java @@ -0,0 +1,10 @@ +package org.keycloak.social.weixin.egress.wechat.mp.models; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class TicketRequest { + public Number expire_seconds; + public String action_name; + public ActionInfo action_info; +} diff --git a/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/TicketResponse.java b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/TicketResponse.java new file mode 100644 index 0000000..bc29d8f --- /dev/null +++ b/src/main/java/org/keycloak/social/weixin/egress/wechat/mp/models/TicketResponse.java @@ -0,0 +1,7 @@ +package org.keycloak.social.weixin.egress.wechat.mp.models; + +public class TicketResponse { + public String ticket; + public Number expire_seconds; + public String url; +} diff --git a/src/main/java/org/keycloak/social/weixin/resources/QrCodeResourceProvider.java b/src/main/java/org/keycloak/social/weixin/resources/QrCodeResourceProvider.java index 5e48779..b40f167 100644 --- a/src/main/java/org/keycloak/social/weixin/resources/QrCodeResourceProvider.java +++ b/src/main/java/org/keycloak/social/weixin/resources/QrCodeResourceProvider.java @@ -3,12 +3,17 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import lombok.RequiredArgsConstructor; import org.jboss.logging.Logger; import org.keycloak.models.KeycloakSession; import org.keycloak.services.resource.RealmResourceProvider; +import org.keycloak.social.weixin.egress.wechat.mp.WechatMpApi; +import org.keycloak.social.weixin.egress.wechat.mp.models.ActionInfo; +import org.keycloak.social.weixin.egress.wechat.mp.models.Scene; +import org.keycloak.social.weixin.egress.wechat.mp.models.TicketRequest; import java.util.Map; @@ -36,9 +41,9 @@ public Response helloAnonymous() { } @GET - @Path("mp-qr-url") + @Path("mp-qr") @Produces(MediaType.TEXT_HTML) - public Response mpQrUrl() { + public Response mpQrUrl(@QueryParam("ticket-url") String ticketUrl) { logger.info("展示一个 HTML 页面,该页面使用 React 展示一个组件,它调用一个后端 API,得到一个带参二维码 URL,并将该 URL 作为 img 的 src 属性值"); String htmlContent = "\n" + @@ -48,21 +53,8 @@ public Response mpQrUrl() { "\n" + "\n" + "
\n" + - " Loading QR Code...\n" + + " \"QR\n" + "
\n" + - " \n" + "\n" + "";