keycloak~关于社区登录的过程说明
keycloak将第三方登录(社区登录)进行了封装,大体主要会经历以下三个过程:
- 打开社区认证页面,输入账号密码或者扫码,完成社区上的认证
- 由社区进行302重定向,回到keycloak页面
- keycloak与社区完成一次oauth2授权码认证,通过社区返回的code来获取token,再通过token来获取社区上的用户信息,在这个过程中,社区不需要向keycloak公开用户的密码,这也是oauth2的安全性的表现
- keycloak检查用户是否与自己本地用户绑定,如果未绑定,进入
第一认证流进行注册或者绑定现在有用户,完成与社区的对应关系,在这个过程中,keycloak对发出FEDERATED_IDENTITY_LINK事件 - 用户完成绑定之后,进行
后一认证流,完成登录之后再做的事,如果用户已经完成绑定,那么第一认证流就不会进入了
从登录到回调
- 打开keycloak登录页
/{realm}/protocol/openid-connect/auth - 检查到client_id或者idp是从社区过来的,重定向到
{realm}/broker/{idp_provider}/login - 重定向到第三方社区认证页面,用户在第三方(微信,google,github)完成登录之后,重定向回keycloak
- 重定向回到keycloak的社区回调页
/{realm}/broker/{idp_provider}/endpoint,处理后续逻辑 - 社区帐号已绑定keycloak用户
- 重定向到
/{realm}/login-actions/post-broker-login(LoginActionsService),验证state,初始化SerializedBrokeredIdentityContext上下文,包含get,post - 重定向到
/{realm}/broker/after-post-broker-login(IdentityBrokerService),执行认证成功后的流程
- 重定向到
- 社区帐号未绑定keycloak用户
- 重定向到第一认证流页面,完成绑定或者注册绑定,
/{realm}/login-actions/first-broker-login(LoginActionsService),包含get,post - 重定向到
/{realm}/broker/after-first-broker-login(IdentityBrokerService)行绑定后的流程 - 重定向到
/{realm}/login-actions/post-broker-login - 重定向到
/{realm}/broker/after-post-broker-login
- 重定向到第一认证流页面,完成绑定或者注册绑定,
回调地址的扩展
- 当社区认证成功后,会跳转到keycloak的社区认证流
- 当keycloak社区认证流完成后,会走到标准认证流
- 标准认证流完成后,会重写向到来源页,并带上keycloak的code码
- 这时,来源页上有且只有code码这个参数,如果希望扩展url上的参数,我们需要以下步骤
在社区回调地址上添加loginType参数
- org.keycloak.services.resources.IdentityBrokerService.finishBrokerAuthentication()方法添加对loginType的操作
private Response finishBrokerAuthentication(BrokeredIdentityContext context, UserModel federatedUser,
AuthenticationSessionModel authSession, String providerId) {
authSession.setAuthNote(AuthenticationProcessor.BROKER_SESSION_ID, context.getBrokerSessionId());
authSession.setAuthNote(AuthenticationProcessor.BROKER_USER_ID, context.getBrokerUserId());
this.event.user(federatedUser);
context.getIdp().authenticationFinished(authSession, context);
authSession.setUserSessionNote("loginType", providerId);
...
}
- org.keycloak.protocol.oidc.OIDCLoginProtocol.authenticated()方法中,获取loginType,并添加到回调路径的URL参数中
code = OAuth2CodeParser.persistCode(session, clientSession, codeData);
redirectUri.addParam(OAuth2Constants.CODE, code);
// TODO: 登录成功后,将用户登录方式追加到回调页面上
if (authSession.getUserSessionNotes().containsKey("loginType")) {
String loginType = authSession.getUserSessionNotes().get("loginType");
redirectUri.addParam("loginType", loginType);
}
FEDERATED_IDENTITY_LINK的完善
- 默认的绑定消息,内容比较少,不满足我们的需求
{
"time": 1723099954167,
"type": "FEDERATED_IDENTITY_LINK",
"realmId": "fabao",
"clientId": "pkulaw",
"userId": "e62a4ea6-c1c3-4f10-9136-8ceebba45339",
"sessionId": null,
"ipAddress": "111.198.143.194",
"error": null,
"details": {
"identity_provider": "carsi",
"identity_provider_identity": "student@pku.edu.cn",
"code_id": "6668189e-4cd6-488e-8582-d28b87636b41",
"username": "phone202408081431274571"
}
}
扩展消息,需要按以下步骤操作
- 在org.keycloak.services.resources.IdentityBrokerService.afterFirstBrokerLogin方法中添加以下代码
// 社区绑定现在有用户后,发的事件FEDERATED_IDENTITY_LINK,我们需要添加一些扩展信息
event.detail(Details.IDENTITY_PROVIDER, providerId);
event.detail(Details.IDENTITY_PROVIDER_USERNAME, context.getBrokerUserId()); //event.detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
event.detail("identity_provider_username", context.getUsername());
- 添加之后,我们为FEDERATED_IDENTITY_LINK事件消息添加identity_provider_username
{
"time": 1723101725866,
"type": "FEDERATED_IDENTITY_LINK",
"realmId": "fabao",
"clientId": "pkulaw",
"userId": "347c9e9e-076c-45e3-be74-c482fffcc6e5",
"sessionId": null,
"ipAddress": "10.10.80.81",
"error": null,
"details": {
"identity_provider": "carsi",
"identity_provider_username": "student@pku.edu.cn",
"identity_provider_identity": "6zETJRPrWiBi7B85cCHPoVD7dyI\u003d",
"code_id": "c344f279-9786-468b-a67e-fecf39c531b0",
"username": "test"
}
}
第一认证流first broken flow获取社区认证的用户信息
- 在认证提供者中,通过
extractIdentityFromProfile方法将社区用户返回的JsonNode对象进行解析 - 将解析后的字段,赋值并建立
BrokeredIdentityContext对象,在方法setUserAttribute()添加对象的用户属性 - 在第一认证流中,
authenticateImpl方法中,参数BrokeredIdentityContext brokerContext就是之前你赋过值的对象
// SocialIdentityProvider认证子类的代码
@Override
protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event, JsonNode profile) {
BrokeredIdentityContext user = new BrokeredIdentityContext(unionId);
user.setUsername(unionId);
user.setBrokerUserId(unionId);
//将内容更新到用户表,未绑定本地用户时,这些信息可以传递到first-broker-flow里
user.setUserAttribute("weichatUnionId", unionId);
//...
}
// AbstractIdpAuthenticator子类实现的第一认证流,在社区用户没有和本地keycloak用户绑定时,会走这个类
protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx,
BrokeredIdentityContext brokerContext) {
if (brokerContext.getUserAttribute("weichatUnionId") != null) {
// 用户属性
userModel.setSingleAttribute("weichatUnionId", brokerContext.getUserAttribute("weichatUnionId"));
// token属性
context.getAuthenticationSession()
.setUserSessionNote("weichatUnionId", brokerContext.getUserAttribute("weichatUnionId"));
}
}
执行过程截图
社区用户未绑定kc用户

社区用户已经绑定了kc用户

浙公网安备 33010602011771号