LoginSignup
10
8

More than 3 years have passed since last update.

Spring BootでSNSログイン

Last updated at Posted at 2019-07-17

概要

Spring Bootで作るwebアプリケーションにSNSログイン機能を実装した際のメモです。

環境

Java 8(ごめんなさい)
Spring Boot 2.1.0.RELEASE

Spring SocialはEOL

https://projects.spring.io/spring-social/
https://spring.io/blog/2018/07/03/spring-social-end-of-life-announcement

使用ライブラリ

実装対象SNS

  • Facebook
  • Google

実装

①POM

pom.xml
<!-- OAuth2 authentication-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

②configuration

SecurityConfig

// ログインに関わる部分のみ抜粋しています。
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @NonNull
    SaveAndGenerateUserDetails saveAndGenerateUserDetails

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // SNSログイン
                .oauth2Login()
                    .loginPage("/login")
                    .userInfoEndpoint()
                    // OAuth認証時に実行するServiceクラス
                    .userService(new OAuth2UserService(saveAndGenerateUserDetails))
                    // OpenId認証時に実行するServiceクラス
                    .oidcUserService(new OidcUserDetailsService(saveAndGenerateUserDetails))
                .and()
                    // 必要があればSuccessHandlerを実装
                    .successHandler(new MyAuthenticationSuccessHandler())
                    // 必要があればSuccessHandlerを実装。今回はデフォルト。
                    .failureHandler(new SimpleUrlAuthenticationFailureHandler("/login?error"))
            .and()
                // ID PASSログインを共存できる
                .formLogin()
                    .loginPage("/login")
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .permitAll()
                    .successHandler(new FormLoginSuccessHandler())
                    .failureHandler(new SimpleUrlAuthenticationFailureHandler("/login?error"))
            .and()
                .logout()
                    .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                    .logoutSuccessUrl("/")
                    .invalidateHttpSession(true)
                    .deleteCookies("JSESSIONID")
                    .permitAll()
            .and()
                .exceptionHandling();


}

③application.yml

application.yml
spring:
  security:
    oauth2:
      client:
        registration:
          facebook:
            client-id: 【client-id】
            client-secret: 【client-secret】
            scope:
              - email
              - public_profile
            redirect-uri: 【アプリケーションのURL】/login/oauth2/code/{registrationId}
          google:
            client-id: 【client-id】
            client-secret: 【client-secret】
            scope:
              - email
              - profile
              - openid
            redirect-uri: 【アプリケーションのURL】/login/oauth2/code/{registrationId}
        provider:
          facebook:
            authorizationUri: https://www.facebook.com/v3.3/dialog/oauth
            tokenUri: https://graph.facebook.com/v3.3/oauth/access_token
            userInfoUri: https://graph.facebook.com/v3.3/me

④view

login.html
<!-- 各SNSログインのエンドポイントを設定したリンクをつくります。 
     エンドポイントは固定値で下記の通りです。 -->
<!-- 〜もろもろ省略〜 -->
<a href="/oauth2/authorization/facebook">Facebookログイン</a>
<a href="/oauth2/authorization/google">Googleログイン</a>
<!-- 〜もろもろ省略〜 -->

⑤Service
⑤-1 OAuth用クラス(SecurityConfigの.userService()に設定)

OAuth2UserService.java
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OAuth2UserService
        extends DefaultOAuth2UserService {

    @NonNull
    SaveAndGenerateUserDetails saveAndGenerateUserDetails

    @Override
    @Transactional(readOnly = false)
    public OAuth2User loadUser(OAuth2UserRequest userRequest)
            throws OAuth2AuthenticationException {

        // 実装クラスの紹介は省略します。
        // 概略:
        // registrationIdで分岐して、取得したい情報を取り出す処理をSNSごとにクラスを作成。
        // 取得した情報を元にUser情報をDBに保存
        // 取得した情報を元に認証情報(UserDetails)を返却します。
        // 本クラスの戻り値の型は認証方法によって異なりますが、processメソッドの戻り値の型は、MyUserDetails
        // なのでここで認証方法の差を吸収します。
        return saveAndGenerateUserDetails.process(userRequest,
                super.loadUser(userRequest));
    }
}

⑤-2 OpenId認証後の処理(SecurityConfigの.oidcUserService()に設定)

OidcUserDetailsService.java
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OidcUserDetailsService
        extends OidcUserService {

    @NonNull
    SaveAndGenerateUserDetails saveAndGenerateUserDetails;

    @Override
    @Transactional(readOnly = false)
    public OidcUser loadUser(OidcUserRequest userRequest)
            throws OAuth2AuthenticationException {

        // ⑤-1と同様
        return saveAndGenerateUserDetails.process(userRequest,
                super.loadUser(userRequest));
    }
}

⑥MyUserDetailsクラス

MyUserDetails.java
@SuppressWarnings("serial")
@Data
public class MyUserDetails implements UserDetails, OAuth2User, OidcUser {

    // @Overrideすべきメソッドはよしなに。
    // SNSログインに必要な部分のみ抜粋しています。

    private Map<String, Object> attributes;

    // コンストラクタ
    public MyUserDetails(MyUser user, Map<String, Object> attr) {
        // OAuth、OpenIdの情報のみ記載しています。
        this.attributes = attr;
    }
}

感想

・思ったより自前で用意するクラスは少なくて済んだ
・その分ライブラリ側でよしなにやってくれるのは助かるけどブラックボックスだった
・oauthとopenidを一種類ずつ実装してしまえば容易に横展開可能

参考URL

10
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8