概要
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
実装
①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を一種類ずつ実装してしまえば容易に横展開可能