うっかりエンジニアのメモ

未来の自分に宛てたメモ

Spring Socialについて自分用メモ

概要

SpringアプリケーションからTwitterFacebookなどのSNSサービスを手軽に利用するためのライブラリ。

Spring Socialを使うメリット

  • SNSのユーザ認証が手軽に実装できる(OAuth, OpenID Connectの詳細を知らなくてもOK)
  • Twitter, Facebook, LinkedIn, Githubなど大手SNSなら、それらのREST APIのラッパーSDKが利用できる
  • Spring Securityと連携したユーザ認証機構を手軽に実装できる

おことわり

この記事では、接続するSNSとしてTwitterを例として話します。
Spring Socialのreferenceをもとに話します。
Spring Social Reference

build.gradle

compile("org.springframework.boot:spring-boot-starter-social-twitter")

SNSアカウントでアプリにログインする機能がほしい場合はさらにspring-social-securityが必要。 compile("org.springframework.social:spring-social-security:1.1.0.RELEASE")

用語説明

provider

OAuth認可フローにおける、tokenの発行者。

consumer

OAuth認可フローにおける、tokenの受取者。

つまりSNSを利用するアプリがconsumerで、利用するSNSがproviderとなる。

SNSAPIを呼び出す単純なアプリ(connecting to providers)

SNSとのOAuth認証部分が全てspring-social側で実装されているので、開発者が書くコードは驚くほど少ない。

ConnectController

org.springframework.social.connect.web内で実装されているController
下記エンドポイントが自動的に利用できるようになる。

GET /connect

すべてのproviderについて、アカウント接続状況を一覧で表示する

GET /connect/{providerId}

指定したproviderのアカウント接続状況を表示する。

POST /connect/{providerId}

指定したproviderへの接続処理を開始する。

GET /connect/{providerId}?oauth_token={request token}&oauth_verifier={verifier}

Receives the authorization callback from the provider, accepting a verification code. Exchanges this verification code along with the request token for an access token and completes the connection. The oauth_verifier parameter is optional and is only used for providers implementing OAuth 1.0a. (※リファレンス引用)

DELETE /connect/{providerId}

指定したproviderへの接続情報を削除する。

DELETE /connect/{providerId}/{providerUserId}

ユーザのproviderUserIdに基づいて、指定したproviderへの特定の接続のみ削除する。

{providerId}TwitterであればtwitterFacebookならfacebookのような文字列。

viewはデフォルトで未定義なので、別途用意する必要がある。 サンプルアプリではtemplates.connectフォルダ以下にthymeleafのテンプレートファイルが作られている。

  • twitterConnect.html Twitterに未接続の場合表示されるview
  • twitterConnected.html Twitterに接続済みの場合表示されるview

SNS接続済みかをチェックする

connectionRepository.findPrimaryConnection(Twitter.class)で判別できる。
すでに認証できていれば Connection<Twitter> クラスのコネクションが返ってくるし、未接続ならnullが返る。

Connection<A>はspring-socialの根幹をなすinterface。

public interface Connection<A> extends Serializable {

    ConnectionKey getKey();

    String getDisplayName();

    String getProfileUrl();

    String getImageUrl();

    void sync();

    boolean test();

    boolean hasExpired();

    void refresh();

    UserProfile fetchUserProfile();

    void updateStatus(String message);

    A getApi();

    ConnectionData createData();

}

認証情報の永続化

OAuth認証に必要な情報は以下の4つ。

  • アプリのconsumer ID
  • アプリのconsumer secret
  • ユーザのaccess token
  • ユーザのaccess token secret

上2つはSNSでアプリ登録するとアプリ1つにつき1つ付与されるので、それをPropertiesファイルなどに記載すればよい。 下2つはユーザごとに異なるので、ユーザごとにconnectionから取得し、DBなどに永続化することになる。

access tokenとaccess token secretはそれぞれconnectionから以下のメソッドで取得できる。

  • connection.createData().getAccessToken()
  • connection.createData().getSecret()

providerのAPIコール

REST APIコールをラップするクラスが用意されている。
ConnectionインタフェースのgetApi()メソッドでラッパーのインスタンスを取得できる。型はprovider名と同じ。(TwitterならTwitterクラス)

@Controller
@RequestMapping("/")
public class HelloController {

    private Twitter twitter;

    private ConnectionRepository connectionRepository;

    @Inject
    public HelloController(Twitter twitter, ConnectionRepository connectionRepository) {
        this.twitter = twitter;
        this.connectionRepository = connectionRepository;
    }

    @RequestMapping(method=RequestMethod.GET)
    public String helloTwitter(Model model) {
        if (connectionRepository.findPrimaryConnection(Twitter.class) == null) {
            return "redirect:/connect/twitter";
        }

        model.addAttribute(twitter.userOperations().getUserProfile());
        CursoredList<TwitterProfile> friends = twitter.friendOperations().getFriends();
        model.addAttribute("friends", friends);
        return "hello";
    }

}

上記のコードでは、OAuth認証後にはtwitterインスタンスを用いてTwitterの各種API呼び出しができるようになっている。
例えばtwitter.friendOperations().getFriends()でログインユーザのフォローしている人の情報を取得できる。

SNSアカウントでログインするアプリ(signing in with provider account)

いわゆるソーシャルログイン。

利用例

単純なアプリ例との違い

  • ユーザが同じブラウザでTwitterにログイン中で
  • そのアプリのアクセスをすでに許可している

と、認証を要求されずスムーズにログインできる。 (authorizeせず、authenticateだけ行われる)

先ほどの単純なアプリの場合、上記条件を満たしていても、いちいち「(アプリ名)があなたのアカウントを利用することを許可しますか?」というTwitterの画面にリダイレクトされる。

  • 単純なアプリでエンドポイントを提供するConnectControler
  • SNSアカウントでログインするアプリでエンドポイントを提供するProviderSignInController

どちらのControllerにも定義されているConnectSupportクラスのフィールドにboolean useAuthenticateUrlという値があり、これがいちいちSNSのアプリ認証画面を出すかどうか左右している。

実装方法は2種類ある

SocialAuthenticationFilterを使う方法と ProviderSignInControllerを使う方法がある。

どのように両者を使い分けるかはSpring Social Referenceに記載があり

Although both options will work with Spring Security, we recommend using SocialAuthenticationFilter in applications where Spring Security is in play.

とあるように、Spring Securityを利用しているアプリケーションでは、SocialAuthenticationFilterを使う方法が推奨される。

一方で、

ProviderSignInController, on the other hand, is agnostic to the security mechanism your application employs and can be used in applications that aren’t using Spring Security.

とあるように、ProviderSignInControllerはSpring Securityと独立して(agnostic to security mechanism)動作するので、Spring Securityを利用しないアプリケーションでもProviderSingInControllerを使えば、ソーシャルログイン機能を実装することが可能になる。

SocialAuthenticationFilter

Spring Securityのconfigurationに.apply(new SpringSocialConfigurer())を追加する。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .formLogin()
            .loginPage("/signin")
            .loginProcessingUrl("/signin/authenticate")
            .failureUrl("/signin?param.error=bad_credentials")
        .and()
            .logout()
                .logoutUrl("/signout")
                .deleteCookies("JSESSIONID")
        .and()
            .authorizeRequests()
                .antMatchers("/admin/**", "/favicon.ico", ...).permitAll()
                .antMatchers("/**").authenticated()
        .and()
            .rememberMe()
        .and()
            .apply(new SpringSocialConfigurer());
}

ProviderSignInController

SNSのアカウントによるログイン機能をSpringアプリに追加するにはProviderSignInControllerをbeanとして設定する。

@Bean
public ProviderSignInController providerSignInController(
            ConnectionFactoryLocator connectionFactoryLocator,
            UsersConnectionRepository usersConnectionRepository) {
    return new ProviderSignInController(
        connectionFactoryLocator,
        usersConnectionRepository,
        new SimpleSignInAdapter(new HttpSessionRequestCache()));
}

参考情報