6.7. 社交网站登录
本章节提到的主要是使用 Facebook,Twitter 和 Google+这三个社交网络,依据网络情况,有些网址可能需要科学上网访问。
社交网站登录也是单点登录(SSO) 的一种形式,可以通过社交网站的账号(比如 Facebook,Twitter 或者 Google+)来登录 CUBA 系统,而不需要为 CUBA 应用程序创建特定的账号。
参考 匿名访问和社交网站登录 指南,了解如何为应用程序的某些界面设置公共访问,以及使用 Google、Facebook 或 GitHub 账号自定义登录的实现。 |
下面将使用 Facebook 来作为社交网络登录的示例。Facebook 使用 OAuth2 认证机制,想了解更多细节请参考 Facebook API 和 Facebook Login Flow: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow 。
示例项目代码在这里: GitHub,以下列出关键点的实现。
-
为了让项目连接到 Facebook,需要创建 App ID (唯一应用程序标识符)和 App Secret (为应用程序项目发送到 Facebook 的请求做认证的一种密码)。按照 介绍 申请,然后在 core 模块的
app.properties
文件中分别以facebook.appId
和facebook.appSecret
这两个属性注册申请到的值。示例:facebook.appId = 123456789101112 facebook.appSecret = 123456789101112abcde131415fghi16
启用 email 权限,允许您的 app 查看用户的主邮箱地址。
然后,在应用程序配置在 Facebook app 注册的 URL,填写在 core 和 web 模块的应用程序属性文件的 cuba.webAppUrl 参数。示例:
cuba.webAppUrl = http://cuba-fb.test:8080/app
-
扩展 登录界面 并添加社交登录按钮。订阅该按钮点击事件,作为社交登录流程的起点。
<linkButton id="facebookBtn" align="MIDDLE_CENTER" caption="Facebook" icon="font-icon:FACEBOOK_SQUARE"/>
-
为了使用 Facebook 用户账号,需要在 CUBA 标准的用户账号中添加一个额外字段。扩展
User
实体并添加字符串类型的属性facebookId
:@Column(name = "FACEBOOK_ID") protected String facebookId;
-
创建一个
FacebookAccessRole
角色,允许用户查看 Help,Settings 和 About 界面:@Role(name = "facebook-access") public class FacebookAccessRole extends AnnotatedRoleDefinition { @ScreenAccess(screenIds = { "help", "aboutWindow", "settings", }) @Override public ScreenPermissionsContainer screenPermissions() { return super.screenPermissions(); } }
-
创建 服务,根据提供的
facebookId
在应用数据库查找用户,然后要么返回已有用户,要么创建新用户:public interface SocialRegistrationService { String NAME = "demo_SocialRegistrationService"; User findOrRegisterUser(String facebookId, String email, String name); }
@Service(SocialRegistrationService.NAME) public class SocialRegistrationServiceBean implements SocialRegistrationService { @Inject private DataManager dataManager; @Inject private Configuration configuration; @Override public User findOrRegisterUser(String facebookId, String email, String name) { User existingUser = dataManager.load(User.class) .query("select u from sec$User u where u.facebookId = :facebookId") .parameter("facebookId", facebookId) .optional() .orElse(null); if (existingUser != null) { return existingUser; } SocialUser user = dataManager.create(SocialUser.class); user.setLogin(email); user.setName(name); user.setGroup(getDefaultGroup()); user.setActive(true); user.setEmail(email); user.setFacebookId(facebookId); UserRole fbUserRole = dataManager.create(UserRole.class); fbUserRole.setRoleName("facebook-access"); fbUserRole.setUser(user); EntitySet eSet = dataManager.commit(user, fbUserRole); return eSet.get(user); } private Group getDefaultGroup() { SocialRegistrationConfig config = configuration.getConfig(SocialRegistrationConfig.class); return dataManager.load(Group.class) .query("select g from sec$Group g where g.id = :defaultGroupId") .parameter("defaultGroupId", config.getDefaultGroupId()) .one(); } }
-
创建服务来管理登录过程。本示例中是: FacebookService 包含两个方法:
getLoginUrl()
和getUserData()
。-
getLoginUrl()
生成登录 URL,基于应用程序 URL 和 OAuth2 返回类型(代码、访问令牌(access token)或者两者都有,参考 Facebook API 文档 了解更多返回类型)。这个方法的实现可以参考 FacebookServiceBean.java 文件。 -
getUserData()
使用提供的应用程序 URL 和代码来查找 Facebook 用户,并且返回已有用户的数据或者创建新用户。在这个例子中,希望获取用户的id
,name
和email
,id
也就是上面创建的facebookId
。
-
-
在 core 模块的
app.properties
文件中定义facebook.fields
应用程序属性:facebook.fields = id,name,email
-
返回扩展登录窗口控制器的 Facebook 登录按钮事件方法。这个控制器的所有代码在 ExtAppLoginWindow.java 文件。
在这个方法中,有针对当前会话的请求处理(request handler),保存当前 URL 并且调用重定向到 Facebook 认证表单:
private RequestHandler facebookCallBackRequestHandler = this::handleFacebookCallBackRequest; private URI redirectUri; @Inject private FacebookService facebookService; @Inject private GlobalConfig globalConfig; @Subscribe("facebookBtn") public void onFacebookBtnClick(Button.ClickEvent event) { VaadinSession.getCurrent() .addRequestHandler(facebookCallBackRequestHandler); this.redirectUri = Page.getCurrent().getLocation(); String loginUrl = facebookService.getLoginUrl(globalConfig.getWebAppUrl(), FacebookService.OAuth2ResponseType.CODE); Page.getCurrent() .setLocation(loginUrl); }
handleFacebookCallBackRequest()
方法会处理 Facebook 认证表单之后的函数回调。首先,使用UIAccessor
实例来锁住 UI 直到登录请求处理完毕。然后,
FacebookService
会获取 facebook 用户账号的email
和id
。在这之后,相应的 CUBA 用户会通过facebookId
被查找到,或者在此过程中被系统创建。接下来,认证会被触发,这个用户的用户会话会被加载,然后 UI 会更新。之后会移除 Facebook 回调处理,因为此时不再需要认证了。
public boolean handleFacebookCallBackRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) throws IOException { if (request.getParameter("code") != null) { uiAccessor.accessSynchronously(() -> { try { String code = request.getParameter("code"); FacebookService.FacebookUserData userData = facebookService.getUserData(globalConfig.getWebAppUrl(), code); User user = socialRegistrationService.findOrRegisterUser( userData.getId(), userData.getEmail(), userData.getName()); Connection connection = app.getConnection(); Locale defaultLocale = messages.getTools().getDefaultLocale(); connection.login(new ExternalUserCredentials(user.getLogin(), defaultLocale)); } catch (Exception e) { log.error("Unable to login using Facebook", e); } finally { session.removeRequestHandler(facebookCallBackRequestHandler); } }); ((VaadinServletResponse) response).getHttpServletResponse(). sendRedirect(ControllerUtils.getLocationWithoutParams(redirectUri)); return true; } return false; }
现在,当用户在登录界面点击 Facebook 按钮时,应用程序会跟用户请求使用 Facebook 账号和邮箱,如果得到用户授权,这个账号登录后会直接跳转到应用程序主界面。
可以通过使用自定义的 LoginProvider
, HttpRequestFilter
或者 Web 登录 章节提到的事件来实现定制化登录机制。