6.6. 社交网站登录

本章节提到的主要是使用 Facebook,Twitter 和 Google+这三个社交网络,依据网络情况,有些网址可能需要科学上网访问。

社交网站登录也是单点登录(SSO) 的一种形式,可以通过社交网站的账号(比如 Facebook,Twitter 或者 Google+)来登录 CUBA 系统,而不需要为 CUBA 应用程序创建特定的账号。

下面将使用 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.appIdfacebook.appSecret 这两个属性注册申请到的值。示例:

app.properties

  1. facebook.appId = 123456789101112
  2. facebook.appSecret = 123456789101112abcde131415fghi16

同样也需要配置在 Facebook app 注册的 URL,填写在 coreweb 模块的应用程序属性文件的 cuba.webAppUrl 参数。示例:

  1. cuba.webAppUrl = http://cuba-fb.test:8080/app
  • 扩展 登录界面 添加社交网络登录按钮。这个按钮将调用 loginFacebook() 方法 - 社交网络登录流程的入口。

  • 为了使用 Facebook 用户账号,需要在 CUBA 标准的用户账号中添加一个额外字段。扩展 User 实体并添加字符串类型的属性 facebookId

SocialUser.java

  1. @Column(name = "FACEBOOK_ID")
  2. protected String facebookId;
  • 创建服务,根据提供的 facebookId 在应用数据库查找用户,然后要么返回已有用户,要么创建新用户:

SocialRegistrationService.java

  1. public interface SocialRegistrationService {
  2. String NAME = "demo_SocialRegistrationService";
  3. User findOrRegisterUser(String facebookId, String email, String name);
  4. }

SocialRegistrationServiceBean.java

  1. @Service(SocialRegistrationService.NAME)
  2. public class SocialRegistrationServiceBean implements SocialRegistrationService {
  3. @Inject
  4. private Metadata metadata;
  5. @Inject
  6. private Persistence persistence;
  7. @Inject
  8. private Configuration configuration;
  9. @Override
  10. @Transactional
  11. public User findOrRegisterUser(String facebookId, String email, String name) {
  12. EntityManager em = persistence.getEntityManager();
  13. TypedQuery<SocialUser> query = em.createQuery("select u from sec$User u where u.facebookId = :facebookId",
  14. SocialUser.class);
  15. query.setParameter("facebookId", facebookId);
  16. query.setViewName(View.LOCAL);
  17. SocialUser existingUser = query.getFirstResult();
  18. if (existingUser != null) {
  19. return existingUser;
  20. }
  21. SocialRegistrationConfig config = configuration.getConfig(SocialRegistrationConfig.class);
  22. Group defaultGroup = em.find(Group.class, config.getDefaultGroupId(), View.MINIMAL);
  23. SocialUser user = metadata.create(SocialUser.class);
  24. user.setFacebookId(facebookId);
  25. user.setEmail(email);
  26. user.setName(name);
  27. user.setGroup(defaultGroup);
  28. user.setActive(true);
  29. user.setLogin(email);
  30. em.persist(user);
  31. return user;
  32. }
  33. }
  • 创建服务来管理登录过程。本示例中是: FacebookService 包含两个方法: getLoginUrl()getUserData()
  • getLoginUrl() 生成登录 URL,基于应用程序 URL 和 OAuth2 返回类型(代码、访问令牌(access token)或者两者都有,参考 Facebook API 文档 了解更多返回类型)。这个方法的实现可以参考 FacebookServiceBean.java 文件。

  • getUserData() 使用提供的应用程序 URL 和代码来查找 Facebook 用户,并且返回已有用户的数据或者创建新用户。在这个例子中,希望获取用户的 id,姓名和 email,id 也就是上面创建的 facebookId

  • core 模块的 app.properties 文件中定义 facebook.fieldsfacebook.scope 应用程序属性:

app.properties

  1. facebook.fields = id,name,email
  2. facebook.scope = email
  • 返回扩展登录窗口控制器的 loginFacebook() 方法。这个控制器的所有代码在 ExtAppLoginWindow.java 文件。

在这个方法中,有针对当前会话的请求处理(request handler),保存当前 URL 并且调用重定向到 Facebook 认证表单:

ExtAppLoginWindow.java

  1. private RequestHandler facebookCallBackRequestHandler =
  2. this::handleFacebookCallBackRequest;
  3. private URI redirectUri;
  4. @Inject
  5. private FacebookService facebookService;
  6. @Inject
  7. private GlobalConfig globalConfig;
  8. public void loginFacebook() {
  9. VaadinSession.getCurrent()
  10. .addRequestHandler(facebookCallBackRequestHandler);
  11. this.redirectUri = Page.getCurrent().getLocation();
  12. String loginUrl = facebookService.getLoginUrl(globalConfig.getWebAppUrl(), OAuth2ResponseType.CODE);
  13. Page.getCurrent()
  14. .setLocation(loginUrl);
  15. }

handleFacebookCallBackRequest() 方法会处理 Facebook 认证表单之后的函数回调。首先,使用 UIAccessor 实例来锁住 UI 直到登录请求处理完毕。

然后,FacebookService 会获取 facebook 用户账号的 email 和 id。在这之后,相应的 CUBA 用户会通过 facebookId 被查找到,或者在此过程中被系统创建。

接下来,认证会被触发,这个用户的用户会话会被加载,然后 UI 会更新。之后会移除 Facebook 回调处理,因为此时不再需要认证了。

ExtAppLoginWindow.java

  1. public boolean handleFacebookCallBackRequest(VaadinSession session, VaadinRequest request,
  2. VaadinResponse response) throws IOException {
  3. if (request.getParameter("code") != null) {
  4. uiAccessor.accessSynchronously(() -> {
  5. try {
  6. String code = request.getParameter("code");
  7. FacebookUserData userData = facebookService.getUserData(globalConfig.getWebAppUrl(), code);
  8. User user = socialRegistrationService.findOrRegisterUser(
  9. userData.getId(), userData.getEmail(), userData.getName());
  10. App app = App.getInstance();
  11. Connection connection = app.getConnection();
  12. Locale defaultLocale = messages.getTools().getDefaultLocale();
  13. connection.login(new ExternalUserCredentials(user.getLogin(), defaultLocale));
  14. } catch (Exception e) {
  15. log.error("Unable to login using Facebook", e);
  16. } finally {
  17. session.removeRequestHandler(facebookCallBackRequestHandler);
  18. }
  19. });
  20. ((VaadinServletResponse) response).getHttpServletResponse().
  21. sendRedirect(ControllerUtils.getLocationWithoutParams(redirectUri));
  22. return true;
  23. }
  24. return false;
  25. }

现在,当用户在登录界面点击 Facebook 按钮时,应用程序会跟用户请求使用 Facebook 账号和邮箱,如果得到用户授权,这个账号登录后会直接跳转到应用程序主界面。

可以通过使用自定义的 LoginProvider, HttpRequestFilter 或者 Web 登录 章节提到的事件来实现定制化登录机制。