OAuth2.0の認証とアクセストークン取得までのサンプル（Java）

OAuth2.0で認証してアクセストークンを取得するまでのサンプルコード。
認証サーバ側の設定等は今回は記載していない。

## 仕組（超概要）

① 事前に認証サーバ側で発行したクライアントIDとシークレットKeyを取得
② 認証エンドポイントへリクエスト（URLアクセス）
③ 認証を行う（ユーザIDとパスワードの入力）
④ リダイレクトでコールバックページにリクエストが来る
⑤ 認証が成功していれば許可コードがクエリパラメタ等で貰える
⑥ 許可コードとクライアントID、シークレットKeyをトークンエンドポイントに送信（POST等）
⑦ アクセストークン（とリフレッシュトークン）を取得
⑧ あとはAPIを投げるなり、IDトークンも貰ってSSOするとかご自由に

## 環境

認証サーバにはOpenAMを使って検証した（このあたりの設定は今回は触れない）。
クライアント側はgoogleのoauth-clientを利用した。
あと、簡単に試すためspark-frameworkを利用している。

```xml:pom.xml
        <dependency>
          <groupId>com.sparkjava</groupId>
          <artifactId>spark-core</artifactId>
          <version>2.7.1</version>
        </dependency>
        <dependency>
            <groupId>com.google.oauth-client</groupId>
            <artifactId>google-oauth-client</artifactId>
            <version>1.23.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.http-client</groupId>
            <artifactId>google-http-client-jackson2</artifactId>
            <version>1.23.0</version>
        </dependency>
```

## 実装サンプル

```java:MyOAuthServiceProviderSample.java
import java.util.Arrays;
import spark.Request;
import spark.Response;
import spark.Route;
import static spark.Spark.*;
import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import java.io.IOException;

/**
 * OAuth2.0で認証を行いアクセストークンを取得
 * @author owl
 */
public class MyOAuthServiceProviderSample {
 
    public static void main(String[] args) {

        /**
         * ① 認証要求
         */
        get("/auth", new Route() {
            @Override
            public Object handle(Request req, Response resp) throws Exception {
               
                /**
                 * 認証エンドポイントと、クライアントIDを指定
                 * 今回はOpenAMで構築した認証サーバを利用
                 */
                AuthorizationCodeRequestUrl codeUrl = new AuthorizationCodeRequestUrl("http://www1.openamexam.co.jp:18080/openam/oauth2/authorize?", "oauth_client_key");

                codeUrl.setScopes(Arrays.asList("openid profile email"));       // 取得したい情報を指定
                codeUrl.setResponseTypes(Arrays.asList("code"));                // 許可コードをもらう
                codeUrl.setRedirectUri("http://localhost:4567/callback");       // CallBackUrlを指定
                codeUrl.setState("this_is_test_state_code");                    // ステートコードを指定、CSRF対策用
                codeUrl.set("nonce", "this_is_one_time_phrase");                // nonceを指定、リプレイアタック対策用
                codeUrl.set("access_type", "offline");                          // リフレッシュトークンを要求する
                codeUrl.set("realm", "/api");                                   // realmを指定
                
                resp.redirect(codeUrl.build());                                 // 認証エンドポイントに飛ばす
                
                return null;
            }
        });

        /**
         * ② アクセストークンの取得
         * -- 認証エンドポイントで認証が行われると、リダイレクトでこちらにCallBackしてくる
         */
        get("/callback", new Route() {
            @Override
            public Object handle(Request req, Response resp) throws Exception {
                
                /**
                 * 許可コードからアクセストークンを要求
                 * この辺は認証サーバの設定にも依存する。今回はQueryStringで取得できるように設定している。
                 */
                String allowCode = req.queryParams("code");                     // 許可コードを取得
                // ステートコードを取得
                // ここで要求時と値が変わらないかチェックすべき（今回は割愛）
                String stateCode = req.queryParams("state");                    
                
                /**
                 * 許可コードで、アクセストークンの取得要求を行う
                 * この辺は認証サーバの設定にも依存する。今回はPOST方式でシークレットキーを送る
                 */
                AuthorizationCodeTokenRequest tokenUrl = new AuthorizationCodeTokenRequest(
                    new NetHttpTransport(),
                    new JacksonFactory(),
                    new GenericUrl("http://www1.openamexam.co.jp:18080/openam/oauth2/access_token?realm=/api"),
                    allowCode                        
                );

                tokenUrl.setGrantType("authorization_code");
                tokenUrl.setRedirectUri("http://localhost:4567/callback");      // 再度自身のURLを指定しないとエラーになる
                tokenUrl.set("client_id", "oauth_client_key");                  // クライアントIDを設定
                tokenUrl.set("client_secret", "oauth_secret_key");              // シークレットIDを設定
                
                TokenResponse tr = null;
                String accessToken = null;
                String refreshToken = null;
                try {
                    tr = tokenUrl.execute();
                    accessToken = tr.getAccessToken();
                    refreshToken = tr.getRefreshToken() == null ? "null" : tr.getRefreshToken();
                    // idTokenもらって解析してその情報信用して自社サービスとかにログインさせればSSOとすることも可
                    String idToken = (String)tr.get("id_token");
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    tr = null;
                }                
                
                return "AccessToken = " + accessToken
                        + "<br>RefreshToken = " + refreshToken;
            }
        });        
        
    }
    
}
```