13 | * 验证 URL Echostr 算法:
14 | * 1. 将 Token (用户在微信后台配置的值),
15 | * 时间戳(微信请求 URL 时传过来的 timestamp 值),
16 | * nonce(微信请求 URL 时传过来的 nonce 值)按照字母顺序排列;
17 | * 2. 排列好后拼成一个字符串;
18 | * 3. 通过 sha1 算法转换此字符串后的结果如果正常就是 echostr 的值。
19 | *
20 | * @return
21 | */
22 | @SneakyThrows
23 | public static boolean isWechatMpMessage(String signature, String timestamp, String nonce) {
24 | var sortedArr = Arrays.stream(new String[]{"uni-heart", timestamp, nonce}).sorted().toArray();
25 | return verify(signature, sortedArr);
26 | }
27 |
28 | @SneakyThrows
29 | public static boolean isWechatMpMessage(String token, String signature, String timestamp, String nonce) {
30 | var sortedArr = Arrays.stream(new String[]{token, timestamp, nonce}).sorted().toArray();
31 | return verify(signature, sortedArr);
32 | }
33 |
34 | private static boolean verify(String signature, Object[] sortedArr) throws NoSuchAlgorithmException {
35 | StringBuilder content = new StringBuilder();
36 | for (var item : sortedArr) {
37 | content.append(item);
38 | }
39 |
40 | var hash = MessageDigest.getInstance("SHA-1");
41 | hash.update(content.toString().getBytes());
42 | var hashed = hash.digest();
43 | var hexDigest = new StringBuilder();
44 | for (byte hashByte : hashed) {
45 | hexDigest.append(String.format("%02x", hashByte));
46 | }
47 |
48 | var upperCase = hexDigest.toString().toUpperCase();
49 |
50 | return upperCase.equals(signature.toUpperCase());
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/org/keycloak/social/weixin/WMPUserSessionModelSerializer.java:
--------------------------------------------------------------------------------
1 | package org.keycloak.social.weixin;
2 |
3 | import com.google.gson.JsonElement;
4 | import com.google.gson.JsonObject;
5 | import com.google.gson.JsonSerializationContext;
6 | import com.google.gson.JsonSerializer;
7 | import org.keycloak.models.UserModel;
8 | import org.keycloak.social.weixin.helpers.JsonHelper;
9 |
10 | import java.lang.reflect.Type;
11 | import java.util.Objects;
12 |
13 | public class WMPUserSessionModelSerializer implements JsonSerializer 请使用微信扫描下方二维码 等待扫码...
159 |
160 | ## Release Notes
161 |
162 | * 2022090
163 | - 适配 quay.io/keycloak 18.0.2
164 |
165 | * 20180730
166 | - 增加自适应微信登录功能。
167 | - 账号关联默认使用微信unionid,如unionid不存在则使用openId
168 | - pc和wechat使用同一套账号则必须绑定同一个开放平台,否则会绑定不同账号
169 | - wechat信息非必填,默认使用pc方式登录
170 |
171 | * 20200514
172 | - 增加 customizedLoginUrlForPc 功能。
173 |
174 | * 20230820
175 | - 适配 quay.io/keycloak 21.1 的版本(由于 21 既不支持老的配置页,又没有新的方式增加自定义配置页,所以只能通过导入老的 Keycloak 版本中的 微信 identity provider 配置)
176 |
177 | * 20230823
178 | - 适配 quay.io/keycloak 22.0.1 的版本,可以正常显示所有的配置了 
179 | * 20230827
180 | - 新增对微信开放平台的支持。 [【继续更新】尝试在 Keycloak 里打通整个微信生态 - Jeff Tian的文章 - 知乎](https://zhuanlan.zhihu.com/p/652566471)
181 |
182 | * 20240129([0.5.13](https://github.com/Jeff-Tian/keycloak-services-social-weixin/releases/tag/0.5.13))
183 | - 优化关注公众号即登录方案的微信后台配置。 详见《[基于 Keycloak 的关注微信公众号即登录方案再次升级:有意思的成长 - Jeff Tian的文章 - 知乎](https://zhuanlan.zhihu.com/p/680356153)》
184 |
185 | * 20250227([0.6.0]
186 | - 适配 quay.io/keycloak 26.0版本。由于keycloak 对类库org.keycloak.services.HttpRequestImpl进行修订,转而使用类库org.keycloak.quarkus.runtime.integration.resteasy.QuarkusHttpRequest。
187 |
188 |
189 | ## Star History
190 |
191 | 感谢大家的支持!
192 |
193 | [](https://star-history.com/#Jeff-Tian/keycloak-services-social-weixin&jyqq163/keycloak-services-social-weixin&Date)
194 |
195 | ## 致谢
196 |
197 | - 感谢 [jyqq163/keycloak-services-social-weixin](https://github.com/jyqq163/keycloak-services-social-weixin) 提供的基础代码,本仓库从该仓库 fork 而来。
198 | - 感谢 [hhhnnn](https://www.zhihu.com/people/hhhnnn-78) 提供的企业公众号,没有该服务号我没法调通手机端。
199 | - 感谢[各位](https://github.com/Jeff-Tian/keycloak-services-social-weixin/graphs/contributors)发的 pull request 和 issue,让本项目越来越好!
200 |
201 | ## 原理
202 |
203 | 其实任何一个 OAuth2/OIDC 的登录插件都是一样的,都是通过一个授权链接,然后通过 code 换取 access_token,再通过 access_token 换取用户信息。详见《[三步开发社交账号登录(以钉钉登录举例) - Jeff Tian的文章 - 知乎](https://zhuanlan.zhihu.com/p/666423994) 》
204 |
205 | ### 以开放平台微信登录举例
206 |
207 | #### 先构建授权链接
208 |
209 | 链接如下:
210 |
211 | ```
212 | https://open.weixin.qq.com/connect/qrconnect?scope=snsapi_login&state=d3Yvfou3pdgp-UNVZ-i7DTDEbv4rZTWx6Wh7lmxzyvk.98VO-haMdj4.c0L0bnybTEatKpqInU02nQ&response_type=code&appid=wxc09e145146844e43&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Frealms%2Fmaster%2Fbroker%2Fweixin%2Fendpoint
213 | ```
214 |
215 | 用户使用微信扫描以上链接中展示的二维码后,会跳转到微信的授权页面,用户点击同意后,会跳转到我们的回调地址,并且带上 code 和 state 参数,如下:
216 |
217 | ```
218 | https://keycloak.jiwai.win/realms/master/broker/weixin/endpoint?code=011er8000zwPzQ1Fvw200DTBCP1er80K&state=d3Yvfou3pdgp-UNVZ-i7DTDEbv4rZTWx6Wh7lmxzyvk.98VO-haMdj4.c0L0bnybTEatKpqInU02nQ
219 | ```
220 |
221 | #### 通过 code 换取 access_token
222 |
223 | #### 通过 access_token 换取用户信息
224 |
225 | ## 🧧 [其他 Keycloak 社交登录插件](https://afdian.net/album/1270bba089c511eebb825254001e7c00)
226 |
227 | - [钉钉登录](https://github.com/Jeff-Tian/keycloak-services-social-dingding)
228 | - [企业微信](https://github.com/Jeff-Tian/keycloak-services-social-wechatwork)
229 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
105 |