├── change_log.md
├── .gitignore
├── src
└── main
│ ├── resources
│ ├── META-INF
│ │ └── services
│ │ │ ├── org.keycloak.broker.provider.IdentityProviderMapper
│ │ │ └── org.keycloak.broker.social.SocialIdentityProviderFactory
│ └── jboss-deployment-structure.xml
│ └── java
│ └── org
│ └── keycloak
│ └── social
│ └── wechat
│ ├── WechatWorkProviderConfig.java
│ ├── WechatWorkIdentityProviderFactory.java
│ ├── WechatWorkUserAttributeMapper.java
│ └── WechatWorkIdentityProvider.java
├── themes
└── base
│ └── admin
│ └── resources
│ └── partials
│ ├── realm-identity-provider-wechat-work-ext.html
│ └── realm-identity-provider-wechat-work.html
├── LICENSE
├── README.md
└── pom.xml
/change_log.md:
--------------------------------------------------------------------------------
1 | # 2019-8-8
2 | * 修改企业微信登录自动生成用户firstName取邮箱@前一段,lastName取用户中文名
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target/
2 | /.classpath
3 | /.project
4 | /.settings/
5 | .idea/
6 | *.iml
7 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper:
--------------------------------------------------------------------------------
1 | org.keycloak.social.wechat.WechatWorkUserAttributeMapper
2 |
--------------------------------------------------------------------------------
/src/main/resources/jboss-deployment-structure.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/themes/base/admin/resources/partials/realm-identity-provider-wechat-work-ext.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2016 Red Hat, Inc. and/or its affiliates
3 | # and other contributors as indicated by the @author tags.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | org.keycloak.social.wechat.WechatWorkIdentityProviderFactory
18 |
--------------------------------------------------------------------------------
/src/main/java/org/keycloak/social/wechat/WechatWorkProviderConfig.java:
--------------------------------------------------------------------------------
1 | package org.keycloak.social.wechat;
2 |
3 | import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
4 | import org.keycloak.models.IdentityProviderModel;
5 |
6 | public class WechatWorkProviderConfig extends OAuth2IdentityProviderConfig {
7 |
8 | public WechatWorkProviderConfig(IdentityProviderModel model) {
9 | super(model);
10 | }
11 |
12 | public WechatWorkProviderConfig() {
13 | super();
14 | }
15 |
16 | public String getAgentId() {
17 | return getConfig().get("agentId");
18 | }
19 |
20 | public void setAgentId(String agentId) {
21 | getConfig().put("agentId", agentId);
22 | }
23 |
24 | public String getQrcodeAuthorizationUrl() {
25 | return getConfig().get("qrcodeAuthorizationUrl");
26 | }
27 |
28 | public void setQrcodeAuthorizationUrl(String qrcodeAuthorizationUrl) {
29 | getConfig().put("qrcodeAuthorizationUrl", qrcodeAuthorizationUrl);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Xun Zhong
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # keycloak-services-social-wechat-work
2 |
3 | Keycloak企业微信登录插件
4 |
5 | Keycloak 15.0.0 测试通过【感谢[potterhe](https://github.com/potterhe)适配新版】
6 |
7 | ```bash
8 | # build from source
9 | mvn clean package
10 |
11 | # add the jar to the Keycloak server (create `providers` folder if needed)
12 | mkdir -p $KEYCLOAK_HOME/providers/
13 | cp target/keycloak-services-social-wechat-work.jar $KEYCLOAK_HOME/providers/
14 |
15 | # add config page templates to the Keycloak server
16 | cp themes/base/admin/resources/partials/realm-identity-provider-wechat-work.html $KEYCLOAK_HOME/themes/base/admin/resources/partials/
17 | cp themes/base/admin/resources/partials/realm-identity-provider-wechat-work-ext.html $KEYCLOAK_HOME/themes/base/admin/resources/partials/
18 |
19 | # add `` to dependencies
20 | sed -ie 's###' $KEYCLOAK_HOME/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
21 | ```
22 |
23 | ## Dev
24 |
25 | ```bash
26 | # format code
27 | mvn com.coveo:fmt-maven-plugin:format
28 | ```
29 |
30 | ## Reference
31 |
32 | - Based on [jyqq163/keycloak-services-social-weixin](https://github.com/jyqq163/keycloak-services-social-weixin)
33 |
--------------------------------------------------------------------------------
/src/main/java/org/keycloak/social/wechat/WechatWorkIdentityProviderFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Red Hat, Inc. and/or its affiliates
3 | * and other contributors as indicated by the @author tags.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package org.keycloak.social.wechat;
18 |
19 | import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
20 | import org.keycloak.broker.social.SocialIdentityProviderFactory;
21 | import org.keycloak.models.IdentityProviderModel;
22 | import org.keycloak.models.KeycloakSession;
23 |
24 | public class WechatWorkIdentityProviderFactory
25 | extends AbstractIdentityProviderFactory
26 | implements SocialIdentityProviderFactory {
27 |
28 | public static final String PROVIDER_ID = "wechat-work";
29 |
30 | @Override
31 | public String getName() {
32 | return "WechatWork";
33 | }
34 |
35 | @Override
36 | public WechatWorkIdentityProvider create(KeycloakSession session, IdentityProviderModel model) {
37 | return new WechatWorkIdentityProvider(session, new WechatWorkProviderConfig(model));
38 | }
39 |
40 | @Override
41 | public IdentityProviderModel createConfig() {
42 | return new WechatWorkProviderConfig();
43 | }
44 |
45 | @Override
46 | public String getId() {
47 | return PROVIDER_ID;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/org/keycloak/social/wechat/WechatWorkUserAttributeMapper.java:
--------------------------------------------------------------------------------
1 | package org.keycloak.social.wechat;
2 |
3 | import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
4 | import org.keycloak.broker.provider.BrokeredIdentityContext;
5 | import org.keycloak.models.IdentityProviderMapperModel;
6 | import org.keycloak.models.KeycloakSession;
7 | import org.keycloak.models.RealmModel;
8 | import org.keycloak.models.UserModel;
9 |
10 | /** User attribute mapper. */
11 | public class WechatWorkUserAttributeMapper extends AbstractJsonUserAttributeMapper {
12 | private static final String PROFILE_MOBILE = WechatWorkIdentityProvider.PROFILE_MOBILE;
13 | private static final String PROFILE_GENDER = WechatWorkIdentityProvider.PROFILE_GENDER;
14 | private static final String PROFILE_STATUS = WechatWorkIdentityProvider.PROFILE_STATUS;
15 | private static final String PROFILE_ENABLE = WechatWorkIdentityProvider.PROFILE_ENABLE;
16 | private static final String[] cp = new String[] {WechatWorkIdentityProviderFactory.PROVIDER_ID};
17 |
18 | @Override
19 | public String[] getCompatibleProviders() {
20 | return cp;
21 | }
22 |
23 | @Override
24 | public String getId() {
25 | return "wechat-work-user-attribute-mapper";
26 | }
27 |
28 | @Override
29 | public void updateBrokeredUser(
30 | KeycloakSession session,
31 | RealmModel realm,
32 | UserModel user,
33 | IdentityProviderMapperModel mapperModel,
34 | BrokeredIdentityContext context) {
35 | user.setSingleAttribute(PROFILE_MOBILE, context.getUserAttribute(PROFILE_MOBILE));
36 | user.setSingleAttribute(PROFILE_GENDER, context.getUserAttribute(PROFILE_GENDER));
37 | user.setSingleAttribute(PROFILE_STATUS, context.getUserAttribute(PROFILE_STATUS));
38 | user.setSingleAttribute(PROFILE_ENABLE, context.getUserAttribute(PROFILE_ENABLE));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | org.keycloak
5 | keycloak-parent
6 | 15.0.1
7 |
8 | 4.0.0
9 | keycloak-services-social-wechat-work
10 | Keycloak Services Social Wechat Work
11 | jar
12 |
13 | 1.8
14 | 1.8
15 |
16 |
17 |
18 | keycloak-services-social-wechat-work
19 |
20 |
21 | com.coveo
22 | fmt-maven-plugin
23 | 2.11
24 |
25 |
26 |
27 | format
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | maven-assembly-plugin
37 |
38 |
39 | jar-with-dependencies
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | make-assembly
50 | package
51 |
52 | single
53 |
54 |
55 |
56 |
57 |
58 | org.apache.maven.plugins
59 | maven-compiler-plugin
60 |
61 | 1.8
62 | 1.8
63 |
64 |
65 |
66 | org.wildfly.plugins
67 | wildfly-maven-plugin
68 |
69 | false
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | org.keycloak
79 | keycloak-core
80 | provided
81 |
82 |
83 | org.keycloak
84 | keycloak-server-spi-private
85 | provided
86 |
87 |
88 | org.keycloak
89 | keycloak-services
90 | provided
91 |
92 |
93 | org.keycloak
94 | keycloak-server-spi
95 | provided
96 |
97 |
98 | org.infinispan
99 | infinispan-core
100 | provided
101 |
102 |
103 | org.infinispan
104 | infinispan-commons
105 | ${infinispan.version}
106 | provided
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/themes/base/admin/resources/partials/realm-identity-provider-wechat-work.html:
--------------------------------------------------------------------------------
1 |
143 |
144 |
--------------------------------------------------------------------------------
/src/main/java/org/keycloak/social/wechat/WechatWorkIdentityProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Red Hat, Inc. and/or its affiliates
3 | * and other contributors as indicated by the @author tags.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package org.keycloak.social.wechat;
18 |
19 | import com.fasterxml.jackson.databind.JsonNode;
20 | import java.util.concurrent.ConcurrentHashMap;
21 | import java.util.concurrent.ConcurrentMap;
22 | import java.util.concurrent.TimeUnit;
23 | import javax.ws.rs.GET;
24 | import javax.ws.rs.QueryParam;
25 | import javax.ws.rs.WebApplicationException;
26 | import javax.ws.rs.core.*;
27 | import org.infinispan.Cache;
28 | import org.infinispan.configuration.cache.ConfigurationBuilder;
29 | import org.infinispan.manager.DefaultCacheManager;
30 | import org.keycloak.OAuth2Constants;
31 | import org.keycloak.OAuthErrorException;
32 | import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
33 | import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
34 | import org.keycloak.broker.provider.AuthenticationRequest;
35 | import org.keycloak.broker.provider.BrokeredIdentityContext;
36 | import org.keycloak.broker.provider.IdentityBrokerException;
37 | import org.keycloak.broker.provider.util.SimpleHttp;
38 | import org.keycloak.broker.social.SocialIdentityProvider;
39 | import org.keycloak.common.ClientConnection;
40 | import org.keycloak.events.Errors;
41 | import org.keycloak.events.EventBuilder;
42 | import org.keycloak.events.EventType;
43 | import org.keycloak.models.KeycloakSession;
44 | import org.keycloak.models.RealmModel;
45 | import org.keycloak.models.UserModel;
46 | import org.keycloak.services.ErrorPage;
47 | import org.keycloak.services.messages.Messages;
48 | import org.keycloak.sessions.AuthenticationSessionModel;
49 |
50 | public class WechatWorkIdentityProvider
51 | extends AbstractOAuth2IdentityProvider
52 | implements SocialIdentityProvider {
53 |
54 | public static final String AUTH_URL = "https://open.weixin.qq.com/connect/oauth2/authorize";
55 | public static final String QRCODE_AUTH_URL =
56 | "https://open.work.weixin.qq.com/wwopen/sso/qrConnect"; // 企业微信外使用
57 | public static final String TOKEN_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken";
58 |
59 | public static final String DEFAULT_SCOPE = "snsapi_base";
60 | public static final String DEFAULT_RESPONSE_TYPE = "code";
61 | public static final String WEIXIN_REDIRECT_FRAGMENT = "wechat_redirect";
62 |
63 | public static final String PROFILE_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo";
64 | public static final String PROFILE_DETAIL_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/get";
65 |
66 | public static final String OAUTH2_PARAMETER_CLIENT_ID = "appid";
67 | public static final String OAUTH2_PARAMETER_AGENT_ID = "agentid";
68 | public static final String OAUTH2_PARAMETER_RESPONSE_TYPE = "response_type";
69 |
70 | public static final String WEIXIN_CORP_ID = "corpid";
71 | public static final String WEIXIN_CORP_SECRET = "corpsecret";
72 | public static final String PROFILE_MOBILE = "mobile";
73 | public static final String PROFILE_GENDER = "gender";
74 | public static final String PROFILE_STATUS = "status";
75 | public static final String PROFILE_ENABLE = "enable";
76 | public static final String PROFILE_USERID = "userid";
77 |
78 | private final String ACCESS_TOKEN_KEY = "access_token";
79 | private final String ACCESS_TOKEN_CACHE_KEY = "wechat_work_sso_access_token";
80 |
81 | private static final DefaultCacheManager cacheManager = new DefaultCacheManager();
82 | private static final String WECHAT_WORK_CACHE_NAME = "wechat_work_sso";
83 | private static final ConcurrentMap> caches =
84 | new ConcurrentHashMap<>();
85 |
86 | private static Cache createCache(String suffix) {
87 | try {
88 | String cacheName = WECHAT_WORK_CACHE_NAME + ":" + suffix;
89 |
90 | ConfigurationBuilder config = new ConfigurationBuilder();
91 | cacheManager.defineConfiguration(cacheName, config.build());
92 |
93 | Cache cache = cacheManager.getCache(cacheName);
94 | logger.info(cache);
95 | return cache;
96 | } catch (Exception e) {
97 | logger.error(e);
98 | e.printStackTrace(System.out);
99 | throw e;
100 | }
101 | }
102 |
103 | private Cache getCache() {
104 | return caches.computeIfAbsent(
105 | getConfig().getClientId() + ":" + getConfig().getAgentId(),
106 | WechatWorkIdentityProvider::createCache);
107 | }
108 |
109 | private String getAccessToken() {
110 | try {
111 | String token = getCache().get(ACCESS_TOKEN_CACHE_KEY);
112 | if (token == null) {
113 | JsonNode j = renewAccessToken();
114 | if (j == null) {
115 | j = renewAccessToken();
116 | if (j == null) {
117 | throw new Exception("renew access token error");
118 | }
119 | logger.debug("retry in renew access token " + j);
120 | }
121 | token = getJsonProperty(j, ACCESS_TOKEN_KEY);
122 | long timeout = Integer.parseInt(getJsonProperty(j, "expires_in"));
123 | getCache().put(ACCESS_TOKEN_CACHE_KEY, token, timeout, TimeUnit.SECONDS);
124 | }
125 | return token;
126 | } catch (Exception e) {
127 | logger.error(e);
128 | e.printStackTrace(System.out);
129 | }
130 | return null;
131 | }
132 |
133 | private JsonNode renewAccessToken() {
134 | try {
135 | return SimpleHttp.doGet(TOKEN_URL, session)
136 | .param(WEIXIN_CORP_ID, getConfig().getClientId())
137 | .param(WEIXIN_CORP_SECRET, getConfig().getClientSecret())
138 | .asJson();
139 | } catch (Exception e) {
140 | logger.error(e);
141 | e.printStackTrace(System.out);
142 | }
143 | return null;
144 | }
145 |
146 | private String resetAccessToken() {
147 | getCache().remove(ACCESS_TOKEN_CACHE_KEY);
148 | return getAccessToken();
149 | }
150 |
151 | public WechatWorkIdentityProvider(KeycloakSession session, WechatWorkProviderConfig config) {
152 | super(session, config);
153 | config.setAuthorizationUrl(AUTH_URL);
154 | config.setQrcodeAuthorizationUrl(QRCODE_AUTH_URL);
155 | config.setTokenUrl(TOKEN_URL);
156 | }
157 |
158 | @Override
159 | public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
160 | return new Endpoint(callback, realm, event);
161 | }
162 |
163 | @Override
164 | protected boolean supportsExternalExchange() {
165 | return true;
166 | }
167 |
168 | @Override
169 | protected BrokeredIdentityContext extractIdentityFromProfile(
170 | EventBuilder event, JsonNode profile) {
171 | logger.info(profile.toString());
172 | // profile: see https://work.weixin.qq.com/api/doc#90000/90135/90196
173 | BrokeredIdentityContext identity =
174 | new BrokeredIdentityContext((getJsonProperty(profile, "userid")));
175 |
176 | identity.setUsername(getJsonProperty(profile, "userid").toLowerCase());
177 | identity.setBrokerUserId(getJsonProperty(profile, "userid").toLowerCase());
178 | identity.setModelUsername(getJsonProperty(profile, "userid").toLowerCase());
179 | String email = getJsonProperty(profile, "biz_mail");
180 | if (email == null || email.length() == 0) {
181 | email = getJsonProperty(profile, "email");
182 | }
183 | identity.setFirstName(email.split("@")[0].toLowerCase());
184 | identity.setLastName(getJsonProperty(profile, "name"));
185 | identity.setEmail(email);
186 | // 手机号码,第三方仅通讯录应用可获取
187 | identity.setUserAttribute(PROFILE_MOBILE, getJsonProperty(profile, "mobile"));
188 | // 性别。0表示未定义,1表示男性,2表示女性
189 | identity.setUserAttribute(PROFILE_GENDER, getJsonProperty(profile, "gender"));
190 | // 激活状态: 1=已激活,2=已禁用,4=未激活。
191 | // 已激活代表已激活企业微信或已关注微工作台(原企业号)。未激活代表既未激活企业微信又未关注微工作台(原企业号)。
192 | identity.setUserAttribute(PROFILE_STATUS, getJsonProperty(profile, "status"));
193 | // 成员启用状态。1表示启用的成员,0表示被禁用。注意,服务商调用接口不会返回此字段
194 | identity.setUserAttribute(PROFILE_ENABLE, getJsonProperty(profile, "enable"));
195 | identity.setUserAttribute(PROFILE_USERID, getJsonProperty(profile, "userid"));
196 |
197 | identity.setIdpConfig(getConfig());
198 | identity.setIdp(this);
199 | AbstractJsonUserAttributeMapper.storeUserProfileForMapper(
200 | identity, profile, getConfig().getAlias());
201 | return identity;
202 | }
203 |
204 | public BrokeredIdentityContext getFederatedIdentity(String authorizationCode) {
205 | String accessToken = getAccessToken();
206 | if (accessToken == null) {
207 | throw new IdentityBrokerException("No access token available");
208 | }
209 | BrokeredIdentityContext context = null;
210 | try {
211 | JsonNode profile;
212 | profile =
213 | SimpleHttp.doGet(PROFILE_URL, session)
214 | .param(ACCESS_TOKEN_KEY, accessToken)
215 | .param("code", authorizationCode)
216 | .asJson();
217 | // {"UserId":"ZhongXun","DeviceId":"10000556333395ZN","errcode":0,"errmsg":"ok"}
218 | // 全局错误码 https://work.weixin.qq.com/api/doc/90001/90148/90455
219 | // 42001 access_token已过期
220 | // 40014 不合法的access_token
221 | logger.info("profile first " + profile.toString());
222 | long errorCode = profile.get("errcode").asInt();
223 | if (errorCode == 42001 || errorCode == 40014) {
224 | accessToken = resetAccessToken();
225 | profile =
226 | SimpleHttp.doGet(PROFILE_URL, session)
227 | .param(ACCESS_TOKEN_KEY, accessToken)
228 | .param("code", authorizationCode)
229 | .asJson();
230 | logger.info("profile retried " + profile.toString());
231 | }
232 | if (errorCode != 0) {
233 | throw new IdentityBrokerException("get user info failed, please retry");
234 | }
235 | profile =
236 | SimpleHttp.doGet(PROFILE_DETAIL_URL, session)
237 | .param(ACCESS_TOKEN_KEY, accessToken)
238 | .param("userid", getJsonProperty(profile, "UserId"))
239 | .asJson();
240 | // logger.info("get userInfo =" + profile.toString());
241 | context = extractIdentityFromProfile(null, profile);
242 | context.getContextData().put(FEDERATED_ACCESS_TOKEN, accessToken);
243 | } catch (Exception e) {
244 | logger.error(e);
245 | e.printStackTrace(System.out);
246 | }
247 | return context;
248 | }
249 |
250 | @Override
251 | protected String getDefaultScopes() {
252 | return DEFAULT_SCOPE;
253 | }
254 |
255 | @Override
256 | protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
257 |
258 | final UriBuilder uriBuilder;
259 |
260 | String ua =
261 | request.getHttpRequest().getHttpHeaders().getHeaderString("user-agent").toLowerCase();
262 | if (ua.contains("wxwork")) {
263 | uriBuilder = UriBuilder.fromUri(getConfig().getAuthorizationUrl());
264 | uriBuilder
265 | .queryParam(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
266 | .queryParam(OAUTH2_PARAMETER_REDIRECT_URI, request.getRedirectUri())
267 | .queryParam(OAUTH2_PARAMETER_RESPONSE_TYPE, DEFAULT_RESPONSE_TYPE)
268 | .queryParam(OAUTH2_PARAMETER_SCOPE, getConfig().getDefaultScope())
269 | .queryParam(OAUTH2_PARAMETER_STATE, request.getState().getEncoded());
270 | uriBuilder.fragment(WEIXIN_REDIRECT_FRAGMENT);
271 | } else {
272 | uriBuilder = UriBuilder.fromUri(getConfig().getQrcodeAuthorizationUrl());
273 | uriBuilder
274 | .queryParam(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
275 | .queryParam(OAUTH2_PARAMETER_AGENT_ID, getConfig().getAgentId())
276 | .queryParam(OAUTH2_PARAMETER_REDIRECT_URI, request.getRedirectUri())
277 | .queryParam(OAUTH2_PARAMETER_STATE, request.getState().getEncoded());
278 | }
279 | return uriBuilder;
280 | }
281 |
282 | protected class Endpoint {
283 | protected AuthenticationCallback callback;
284 | protected RealmModel realm;
285 | protected EventBuilder event;
286 |
287 | @Context protected KeycloakSession session;
288 |
289 | @Context protected ClientConnection clientConnection;
290 |
291 | @Context protected HttpHeaders headers;
292 |
293 | @Context protected UriInfo uriInfo;
294 |
295 | public Endpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
296 | this.callback = callback;
297 | this.realm = realm;
298 | this.event = event;
299 | }
300 |
301 | @GET
302 | public Response authResponse(
303 | @QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_STATE) String state,
304 | @QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_CODE) String authorizationCode,
305 | @QueryParam(OAuth2Constants.ERROR) String error,
306 | @QueryParam("appid") String client_id) {
307 | logger.info("OAUTH2_PARAMETER_CODE=" + authorizationCode);
308 |
309 | // 以下样版代码从 AbstractOAuth2IdentityProvider 里获取的。
310 | if (state == null) {
311 | return errorIdentityProviderLogin(Messages.IDENTITY_PROVIDER_MISSING_STATE_ERROR);
312 | }
313 | try {
314 | AuthenticationSessionModel authSession =
315 | this.callback.getAndVerifyAuthenticationSession(state);
316 | session.getContext().setAuthenticationSession(authSession);
317 |
318 | if (error != null) {
319 | logger.error(error + " for broker login " + getConfig().getProviderId());
320 | if (error.equals(ACCESS_DENIED)) {
321 | return callback.cancelled();
322 | } else if (error.equals(OAuthErrorException.LOGIN_REQUIRED)
323 | || error.equals(OAuthErrorException.INTERACTION_REQUIRED)) {
324 | return callback.error(error);
325 | } else {
326 | return callback.error(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
327 | }
328 | }
329 |
330 | if (authorizationCode != null) {
331 | BrokeredIdentityContext federatedIdentity = getFederatedIdentity(authorizationCode);
332 |
333 | federatedIdentity.setIdpConfig(getConfig());
334 | federatedIdentity.setIdp(WechatWorkIdentityProvider.this);
335 | federatedIdentity.setAuthenticationSession(authSession);
336 |
337 | return callback.authenticated(federatedIdentity);
338 | }
339 | } catch (WebApplicationException e) {
340 | e.printStackTrace(System.out);
341 | return e.getResponse();
342 | } catch (Exception e) {
343 | logger.error("Failed to make identity provider oauth callback", e);
344 | e.printStackTrace(System.out);
345 | }
346 | return errorIdentityProviderLogin(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
347 | }
348 |
349 | private Response errorIdentityProviderLogin(String message) {
350 | event.event(EventType.IDENTITY_PROVIDER_LOGIN);
351 | event.error(Errors.IDENTITY_PROVIDER_LOGIN_FAILURE);
352 | return ErrorPage.error(session, null, Response.Status.BAD_GATEWAY, message);
353 | }
354 | }
355 |
356 | @Override
357 | public void updateBrokeredUser(
358 | KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
359 | user.setSingleAttribute(PROFILE_MOBILE, context.getUserAttribute(PROFILE_MOBILE));
360 | user.setSingleAttribute(PROFILE_GENDER, context.getUserAttribute(PROFILE_GENDER));
361 | user.setSingleAttribute(PROFILE_STATUS, context.getUserAttribute(PROFILE_STATUS));
362 | user.setSingleAttribute(PROFILE_ENABLE, context.getUserAttribute(PROFILE_ENABLE));
363 | user.setSingleAttribute(PROFILE_USERID, context.getUserAttribute(PROFILE_USERID));
364 |
365 | user.setUsername(context.getUsername());
366 | user.setFirstName(context.getFirstName());
367 | user.setLastName(context.getLastName());
368 | user.setEmail(context.getEmail());
369 | }
370 | }
371 |
--------------------------------------------------------------------------------