1. 概要

セキュリティは、Springエコシステムの第一級市民です。 したがって、OAuth2がほとんど構成なしでSpringWebMVCで動作できることは驚くべきことではありません。

ただし、ネイティブSpringソリューションは、プレゼンテーション層を実装する唯一の方法ではありません。 JAX-RS準拠の実装であるJerseyは、SpringOAuth2と連携して動作することもできます。

このチュートリアルでは、OAuth2標準を使用して実装されたSpringソーシャルログインを使用してJerseyアプリケーションを保護する方法を説明します。

2. Mavenの依存関係

spring -boot-starter-jersey アーティファクトを追加して、JerseyをSpring Bootアプリケーションに統合しましょう。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jersey</artifactId>
</dependency>

セキュリティOAuth2を構成するには、spring-boot-starter-securityspring-security-oauth2-clientが必要です。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-client</artifactId>
</dependency>

Spring BootStarterParentバージョン2を使用してこれらすべての依存関係を管理します

3. ジャージープレゼンテーション層

プレゼンテーション層としてJerseyを使用するには、いくつかのエンドポイントを持つリソースクラスが必要です。

3.1. リソースクラス

エンドポイント定義を含むクラスは次のとおりです。

@Path("/")
public class JerseyResource {
    // endpoint definitions
}

クラス自体は非常に単純です。@Pathアノテーションが付いているだけです。 このアノテーションの値は、クラスの本体内のすべてのエンドポイントのベースパスを識別します。

このリソースクラスには、コンポーネントスキャン用のステレオタイプアノテーションが含まれていないことに注意してください。 実際、Springbeanである必要はありません。 その理由は、リクエストのマッピングを処理するためにSpringに依存していないためです。

3.2. ログインページ

ログイン要求を処理するメソッドは次のとおりです。

@GET
@Path("login")
@Produces(MediaType.TEXT_HTML)
public String login() {
    return "Log in with <a href=\"/oauth2/authorization/github\">GitHub</a>";
}

このメソッドは、 /loginエンドポイントを対象とするGETリクエストの文字列を返します。 text / html コンテンツタイプは、クリック可能なリンクで応答を表示するようにユーザーのブラウザに指示します。

GitHubをOAuth2プロバイダーとして使用するため、リンク/ oauth2 / authentication / github。このリンクは、GitHub承認ページへのリダイレクトをトリガーします。

3.3. ホームページ

ルートパスへのリクエストを処理する別のメソッドを定義しましょう。

@GET
@Produces(MediaType.TEXT_PLAIN)
public String home(@Context SecurityContext securityContext) {
    OAuth2AuthenticationToken authenticationToken = (OAuth2AuthenticationToken) securityContext.getUserPrincipal();
    OAuth2AuthenticatedPrincipal authenticatedPrincipal = authenticationToken.getPrincipal();
    String userName = authenticatedPrincipal.getAttribute("login");
    return "Hello " + userName;
}

このメソッドは、ログインしたユーザー名を含む文字列であるホームページを返します。 この場合、ログイン属性からユーザー名を抽出したことに注意してください。ただし、別のOAuth2プロバイダーがユーザー名に別の属性を使用している可能性があります。

明らかに、上記の方法は認証されたリクエストに対してのみ機能します。 リクエストが認証されていない場合、ログインエンドポイントにリダイレクトされます。このリダイレクトを構成する方法については、セクション4で説明します。

3.4. ジャージーをSpringContainerに登録する

リソースクラスをサーブレットコンテナに登録して、Jerseyサービスを有効にしましょう。幸い、非常に簡単です。

@Component
public class RestConfig extends ResourceConfig {
    public RestConfig() {
        register(JerseyResource.class);
    }
}

JerseyResourceResourceConfigサブクラスに登録することで、そのリソースクラスのすべてのエンドポイントをサーブレットコンテナに通知しました。

最後のステップは、ResourceConfigサブクラス(この場合はRestConfig)をSpringコンテナーに登録することです。この登録は、@Componentアノテーションを使用して実装しました。

4. SpringSecurityの構成

通常のSpringアプリケーションの場合と同じように、Jerseyのセキュリティを構成できます。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .authorizeRequests()
          .antMatchers("/login")
          .permitAll()
          .anyRequest()
          .authenticated()
          .and()
          .oauth2Login()
          .loginPage("/login");
    }
}

指定されたチェーンで最も重要なメソッドはoauth2Loginです。 このメソッドは、OAuth 2.0プロバイダーを使用して認証サポートを構成します。このチュートリアルでは、プロバイダーはGitHubです。

もう1つの注目すべき構成は、ログインページです。 文字列“ / login”loginPage メソッドに提供することにより、Springに認証されていない要求を/loginエンドポイントにリダイレクトするように指示します。

デフォルトのセキュリティ構成では、 /loginに自動生成されたページも提供されることに注意してください。 したがって、ログインページを構成しなかった場合でも、認証されていない要求はそのエンドポイントにリダイレクトされます。

デフォルト設定と明示的な設定の違いは、デフォルトの場合、アプリケーションはカスタム文字列ではなく、生成されたページを返すことです。

5. アプリケーション構成

OAuth2で保護されたアプリケーションを使用するには、クライアントをOAuth2プロバイダーに登録する必要があります。 その後、クライアントの資格情報をアプリケーションに追加します。

5.1. OAuth2クライアントの登録

GitHubアプリを登録して登録プロセスを開始しましょう。 GitHub開発者ページにアクセスしたら、新しいOAuthアプリボタンを押して新しいOAuthアプリケーションの登録フォームを開きます。

次に、表示されたフォームに適切な値を入力します。 アプリケーション名には、アプリを認識できる文字列を入力します。 ホームページのURLはhttp:// localhost:8083、で、認証コールバックURLは http:// localhost:8083 / login / oauth2 / code /githubです。

コールバックURLは、ユーザーがGitHubで認証し、アプリケーションへのアクセスを許可した後にブラウザーがリダイレクトするパスです。

登録フォームは次のようになります。

 

次に、アプリケーションの登録ボタンをクリックします。 次に、ブラウザーはGitHubアプリのホームページにリダイレクトする必要があります。このホームページには、クライアントIDとクライアントシークレットが表示されます。

5.2. SpringBootアプリケーションの構成

jersey-application.propertiesという名前のプロパティファイルをクラスパスに追加しましょう。

server.port=8083
spring.security.oauth2.client.registration.github.client-id=<your-client-id>
spring.security.oauth2.client.registration.github.client-secret=<your-client-secret>

プレースホルダーを置き換えることを忘れないでください独自のGitHubアプリケーションからの値を使用します。

最後に、このファイルをプロパティソースとしてSpringBootアプリケーションに追加します。

@SpringBootApplication
@PropertySource("classpath:jersey-application.properties")
public class JerseyApplication {
    public static void main(String[] args) {
        SpringApplication.run(JerseyApplication.class, args);
    }
}

6. 実行中の認証

GitHubに登録した後、アプリケーションにログインする方法を見てみましょう。

6.1. アプリケーションへのアクセス

アプリケーションを起動して、アドレス localhost:8083のホームページにアクセスしてみましょう。 リクエストは認証されていないため、loginページにリダイレクトされます。

 

これで、GitHubリンクをクリックすると、ブラウザーはGitHub承認ページにリダイレクトされます。

 

URLを見ると、リダイレクトされたリクエストに response_type client_id scopeなどの多くのクエリパラメータが含まれていることがわかります。

https://github.com/login/oauth/authorize?response_type=code&client_id=c30a16c45a9640771af5&scope=read:user&state=dpTme3pB87wA7AZ--XfVRWSkuHD3WIc9Pvn17yeqw38%3D&redirect_uri=http://localhost:8083/login/oauth2/code/github

response_typeの値はcodeです。これは、OAuth2付与タイプが認証コードであることを意味します。  一方、 client_id パラメーターは、アプリケーションの識別に役立ちます。 すべてのパラメーターの意味については、GitHub開発者ページにアクセスしてください。

承認ページが表示されたら、続行するにはアプリケーションを承認する必要があります。 承認が成功すると、ブラウザはいくつかのクエリパラメータとともに、アプリケーションの事前定義されたエンドポイントにリダイレクトします。

http://localhost:8083/login/oauth2/code/github?code=561d99681feeb5d2edd7&state=dpTme3pB87wA7AZ--XfVRWSkuHD3WIc9Pvn17yeqw38%3D

舞台裏では、アプリケーションは認証コードをアクセストークンと交換します。 その後、このトークンを使用して、ログインしているユーザーに関する情報を取得します。

localhost:8083 / login / oauth2 / code / github へのリクエストが戻った後、ブラウザはホームページに戻ります。 今回は、自分のユーザー名のあいさつメッセージが表示されます。

 

6.2. ユーザー名を取得する方法は?

グリーティングメッセージのユーザー名がGitHubのユーザー名であることは明らかです。 この時点で、疑問が生じる可能性があります。認証されたユーザーからユーザー名やその他の情報を取得するにはどうすればよいですか。

この例では、login属性からユーザー名を抽出しました。 ただし、これはすべてのOAuth2プロバイダーで同じではありません。 言い換えれば、プロバイダーは独自の裁量で特定の属性のデータを提供する場合があります。したがって、この点に関しては単に標準がないと言えます。

GitHubの場合、リファレンスドキュメントで必要な属性を見つけることができます。 同様に、他のOAuth2プロバイダーは独自の参照を提供します。

もう1つの解決策は、 OAuth2AuthenticatedPrincipalオブジェクトが作成された後、デバッグモードでアプリケーションを起動し、ブレークポイントを設定できることです。このオブジェクトのすべての属性を調べると、ユーザーの情報を把握できます。

7. テスト

アプリケーションの動作を検証するためのテストをいくつか書いてみましょう。

7.1. 環境のセットアップ

テストメソッドを保持するクラスは次のとおりです。

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@TestPropertySource(properties = "spring.security.oauth2.client.registration.github.client-id:test-id")
public class JerseyResourceUnitTest {
    @Autowired
    private TestRestTemplate restTemplate;

    @LocalServerPort
    private int port;

    private String basePath;

    @Before
    public void setup() {
        basePath = "http://localhost:" + port + "/";
    }

    // test methods
}

実際のGitHubクライアントIDを使用する代わりに、 OAuth2クライアントのテストIDを定義しました。このIDは、spring.security.oauth2.client.registration.github.client-idに設定されます。 プロパティ。

このテストクラスのすべてのアノテーションはSpring Bootテストで共通であるため、このチュートリアルでは取り上げません。 これらのアノテーションのいずれかが不明な場合は、 Spring Bootでのテスト Springでの統合テスト、または Spring BootTestRestTemplateの調査に進んでください。

7.2. ホームページ

認証されていないユーザーがホームページにアクセスしようとすると、認証のためにログインページにリダイレクトされることを証明します:

@Test
public void whenUserIsUnauthenticated_thenTheyAreRedirectedToLoginPage() {
    ResponseEntity<Object> response = restTemplate.getForEntity(basePath, Object.class);
    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND);
    assertThat(response.getBody()).isNull();

    URI redirectLocation = response.getHeaders().getLocation();
    assertThat(redirectLocation).isNotNull();
    assertThat(redirectLocation.toString()).isEqualTo(basePath + "login");
}

7.3. ログインページ

ログインページにアクセスすると、認証パスが返されることを確認しましょう:

@Test
public void whenUserAttemptsToLogin_thenAuthorizationPathIsReturned() {
    ResponseEntity response = restTemplate.getForEntity(basePath + "login", String.class);
    assertThat(response.getHeaders().getContentType()).isEqualTo(TEXT_HTML);
    assertThat(response.getBody()).isEqualTo("Log in with <a href="\"/oauth2/authorization/github\"">GitHub</a>");
}

7.4. 承認エンドポイント

最後に、承認エンドポイントにリクエストを送信すると、ブラウザは適切なパラメータを使用してOAuth2プロバイダーの承認ページにリダイレクトします:

@Test
public void whenUserAccessesAuthorizationEndpoint_thenTheyAresRedirectedToProvider() {
    ResponseEntity response = restTemplate.getForEntity(basePath + "oauth2/authorization/github", String.class);
    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND);
    assertThat(response.getBody()).isNull();

    URI redirectLocation = response.getHeaders().getLocation();
    assertThat(redirectLocation).isNotNull();
    assertThat(redirectLocation.getHost()).isEqualTo("github.com");
    assertThat(redirectLocation.getPath()).isEqualTo("/login/oauth/authorize");

    String redirectionQuery = redirectLocation.getQuery();
    assertThat(redirectionQuery.contains("response_type=code"));
    assertThat(redirectionQuery.contains("client_id=test-id"));
    assertThat(redirectionQuery.contains("scope=read:user"));
}

8. 結論

このチュートリアルでは、Jerseyアプリケーションを使用してSpringSocialLoginを設定しました。 チュートリアルには、GitHubOAuth2プロバイダーにアプリケーションを登録するための手順も含まれていました。

完全なソースコードは、GitHubにあります。