今回は、Spring Boot 2とOAuth2 Auto Configを利用してシンプルにOAuth2クライアント(フロント)アプリを作成する方法について解説します。
OAuth2クライアントで実現する要件は、以下のとおりです。
- OAuth2認可サーバを利用したシングルサインオン(SSO)
- SSOで取得したアクセストークンを利用したOAuth2リソースサーバ(API)へのアクセス
プロバイダとして、今回はGithub APIを利用します。
今回使用するライブラリ
- Spring Boot 2 (Spring & Spring Security 5)
- spring-boot-starter-web 2.0.1.RELEASE
- spring-boot-starter-security 2.0.1.RELEASE
- OAuth2 Autoconfig (Spring Security OAuth2)
- spring-security-oauth2-autoconfigure 2.0.0.RELEASE
- Thymeleaf 3
- spring-boot-starter-thymeleaf 2.0.1.RELEASE
- thymeleaf-extras-springsecurity4 3.0.2.RELEASE
- Coding Support
- lombok 1.16.20
Spring Security OAuth2の設定をシンプルに実現するため、spring-security-oauth2-autoconfigure
を利用するのがポイントです。
Spring Securityによる通常のログイン
まずはSpring Boot 2 (Spring & Spring Security 5)を利用して通常のフォームログインを確認します。
依存ライブラリの設定
- pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot 2 (Spring & Spring Security 5) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Thymeleaf 3 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<!-- Coding Support -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
thymeleaf-extras-springsecurity4
は、ログイン後の画面で認証情報を表示するために利用しています。Spring Security 5用のモジュールがリリースされていないため、Spring Security 4用のもので代用しています。
アプリケーションのエントリポイント
- Application.java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
エントリポイントに@EnableWebSecurity
を付与せずとも、クラスパスにspring-boot-starter-security
を含めるだけで自動的にSpring Securityが有効になり、デフォルトですべてのパスにisAuthenticated()
の認可がかかります。
ホーム画面
- Controller.java
@Controller
public class Controller {
@GetMapping("/")
public String home() {
return "home";
}
}
- home.html
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="utf-8">
<title>Home</title>
</head>
<body>
<h1>Welcome to Home <span sec:authentication="name"></span></h1>
</body>
</html>
この時点でアプリケーションを実行しコンテキストルートにアクセスすると、Spring Securityのフォームログイン機能によりデフォルトのログイン画面が表示されます。ログインしてホーム画面にアクセスすると「Welcome to Home user」と表示され、ログインしたことが確認できます。
Spring Securityのデフォルトでは、ユーザ名
user
と起動時にログ出力されるランダム生成されたパスワードを利用してアクセスすることができます。
OAuth2を利用したシングルサインオン(SSO)
通常のSpring Securityによるログインを確認したら、次にOAuth2によるSSOに変更します。
依存ライブラリにspring-security-oauth2-autoconfigure
を追加
- pom.xml
<dependencies>
<!-- OAuth2 Autoconfig (Spring Security OAuth2) -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
</dependencies>
エントリポイントに@EnableOAuth2Sso
を追加
- Application.java
@SpringBootApplication
@EnableWebSecurity
@EnableOAuth2Sso
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@EnableOAuth2Sso
を追加することで、Spring Securityの認証処理をフックしてOAuth2による認証を行うようになります。
ここでのポイントは、@EnableOAuth2Sso
だけでなく@EnableWebSecurity
も追加することです。
@EnableOAuth2Sso
でOAuth2認証は有効になりますが、Spring Securityの認証情報に引き継がれない(=ログインしない)ため、リクエストごとに認証が必要となり、全く実用的ではありませんでした。SSOするなら必ず@EnableWebSecurity
を追加しましょう。
Githubにアプリケーションを登録
Github APIリファレンスを参考にアプリケーションをGithubに登録し、client-id
とclient-secret
を入手しましょう。
Githubユーザページのユーザアイコンから「Settings>Developer settings>OAuth Apps>New OAuth App」と移動し、指示に従い登録します。
アプリケーションの登録時にはAuthorization callback URL(認証時にコールバックするURL)が必要となるので、アプリケーションのポート番号とコンテキストルートURLの設定方法を示します。
- application.yml
server:
port: 8080
servlet:
context-path: /demo
OAuth2のクライアント認証情報を登録
- application.yml
security:
oauth2:
client:
client-id: xxxx # (1)
client-secret: xxxx
access-token-uri: https://github.com/login/oauth/access_token # (2)
user-authorization-uri: https://github.com/login/oauth/authorize
client-authentication-scheme: form # (3)
resource:
user-info-uri: https://api.github.com/user # (4)
prefer-token-info: false
sso:
login-path: /login # (5)
(1) さきほど入手したclient-id
とclient-secret
を登録します。
(2) Github APIリファレンスを参考に認証URL(user-authorization-uri
)やアクセストークン取得のURL(user-authorization-uri
)を登録します。
(3) 今回はリクエストボディに認証情報を載せるので、client-authentication-scheme
にform
をセットします。
(4) OAuth2クライアントとして利用するだけなら上記のみでOKですが、今回はシングルサインオンのためユーザ情報を取得するURL(user-info-uri
)を登録する必要があります。(加えて、ユーザ情報をtoken-info-uri
から取得せずuser-info-uri
から取得するため、prefer-token-info
にfalse
をセットして無効化しています。)
(5) SSOログインURL(認証していないときにリダイレクトするURL)を変更したい場合は、login-path
を変更します。今回はデフォルト(/login
)で良いので設定する必要はありませんが、プロバイダが複数だったり、認可制御してログインURLを認可から除外したいときに設定します。
この時点でアプリケーションを実行しコンテキストルートにアクセスすると、Githubのログイン画面が表示されます。
Githubでアプリケーションを認可するとホーム画面に遷移し、「Welcome to Home yoshikawaa」と表示され、SSOによりログインしGithubの認証情報がSpring Securityの認証情報に連携されたことが確認できます。
認証情報が
OAuth2Authentication
に置き換わるため、例えばsec:authentication="oAuth2Request.clientId"
で画面上でclient-id
が確認できます。
OAuth2を利用したリソースサーバへのアクセス
SSOによるログインを確認したら、次に取得したアクセストークンを利用してリソースサーバ(Github API)へのアクセスができることを確認します。
今回は、Github APIリファレンスを参考にログインユーザのリポジトリ一覧を取得して画面に表示してみます。
試しにブラウザでURLhttps://api.github.com/user/repos
にアクセスしてみると、Requires authentication
と表示され認証が必要なことが分かります。
エントリポイントにOAuth2RestTemplate
のBean定義を追加
- Application.java
@Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails details) {
return new OAuth2RestTemplate(details, oauth2ClientContext);
}
クラスに@EnableOAuth2Client
を付与することでOAuth2ClientContext
とOAuth2ProtectedResourceDetails
が自動的に設定されますが、@EnableOAuth2Sso
が内部的に@EnableOAuth2Client
を付与するため、ここではアノテーションを付与する必要はありません。
取得したリポジトリ情報を格納するBean
- Repository.java
@Getter
@Setter
public class Repository {
private String name;
@JsonProperty("html_url")
private String htmlUrl;
}
ここで初めてLombokを使います。。。
ログインユーザのリポジトリ一覧を表示する画面
- Controller.java
@Autowired
private OAuth2RestTemplate auth2RestTemplate;
@GetMapping("/repos")
public String repositories(Model model) {
URI uri = UriComponentsBuilder.fromUriString("https://api.github.com/user/repos").build().toUri();
model.addAttribute("repos", auth2RestTemplate.getForEntity(uri, Repository[].class).getBody());
return "repos";
}
- repos.html
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="utf-8">
<title>Repos</title>
</head>
<body>
<h1>Listing Repositories owned <span sec:authentication="name"></span></h1>
<ul>
<li th:each="repo : ${repos}" th:object="${repo}">
<a th:text="*{name}" th:href="*{htmlUrl}"></a>
</li>
</ul>
</body>
</html>
この時点でアプリケーションを実行しコンテキストルートにアクセスし、SSOによりログインします。
その後、パス/repos
にアクセスすると、取得したアクセストークンを利用してGithub APIからリポジトリの一覧を取得できることが確認できます。
ブラウザのネットワーク履歴を見ると、パスrepos
にアクセスする際に再ログインなどはしていないことも確認できますね。
ちなみに、途中でアクセストークンの有効期限が切れると認可サーバへ問い合わせが走るため、現在アクセス中のURLにGETによるリダイレクトが走る可能性があります。その際、POSTで送信していたリクエストパラメータが失われてしまうため、基本的にリソースサーバにアクセスするURLにはGETでアクセスさせる、またはリクエストパラメータが失われても再入力すればOKな作りにしておくのが無難なようです。
まとめ
Spring Security OAuth2は設定がやたら難しくて実装が大変な印象ですが、OAuth2 Autoconfigを利用すると難しい設定はほとんどなく、超シンプルにシングルサインオンとOAuth2クライアントが実現できました。
ただ、Spring Securityと連携する方法がドキュメントのどこにも書いてなく、最初はかなりハマったので、今後改善されるといいなーと思います。