1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AWS Cognitoのカスタム属性を使ってセキュリティ診断会社の指摘に対応した

Posted at

はじめに

セキュリティ診断会社から指摘された点と対策内容をまとめました
参考になれば幸いです

前提となる開発環境

本記事で想定する開発環境は以下になります。

  • フロントエンド - JavaScript(Nuxt.js)
  • バックエンド - Java(Spring)
  • ユーザー管理 - AWS Cognito

指摘されたこと

■ 他人の情報を取得・変更するようなAPIを実行できないようにしましょう

URLにIDなど含んでる場合は他のユーザーが叩けないように工夫しましょう、というものでした。
Nuxt.js のルーティングだとやりたくなっちゃう「動的なルーティング」ですね。

Nuxt.js | 動的なルーティング
https://develop365.gitlab.io/nuxtjs-2.8.X-doc/ja/guide/routing/#%E5%8B%95%E7%9A%84%E3%81%AA%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0

/pages/users/_id.vue のように、URLにIDを含んだものですね。
このようなページでURLに含まれているIDを使ってAPIを呼び出すことはよくあると思います。

/pages/users/_id.vue
axios.get(`http://localhost:3000/users/${this.$route.params.id}`)
.then((res) => {
  // 取得した情報を何かしらする!
});

URLに含まれているIDをそのまま使う場合、ブラウザ上のURLを直接書き換えることで他人の情報を取得できてしまう恐れがあります。
(そもそも他人のIDを知っている必要があるので、ハードルは高い気がしますが…)

■ 管理者用のAPIを一般ユーザーが実行できないようにしましょう

管理者用APIのような重要な処理をするものには権限チェックをつけ、一般ユーザーが呼び出せないようにしましょう。

一般ユーザー用の画面上からは呼び出せなくても、Postmanのようなツールから直接APIを実行できてしまっては本末転倒です。

対応した内容

■ ユーザーにカスタム属性を付与した

AWS Cognitoを使ってユーザー管理をしている場合、カスタム属性にユーザー識別用のIDを含めることで対応が可能です。

AWS Cognito | ユーザープール属性
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/user-pool-settings-attributes.html

Spring Securityでの認証時にカスタム属性を読み込んでおきます。

色々端折っていますが、例えばこのようなコードになります。

MyUserDetailsService.java
public class MyUserDetailsService implements UserDetailsService {
  @Override
  public UserDetails loadUserByUsername(String username) {
    // Cognitoからユーザーを検索
    AdminGetUserResult getUserResult = new AdminGetUserRequest()
      .withUserPoolId("UserPoolId")
      .withUsername("email");

    String userId = "";
    Set<Role> role = new HashSet<>();

    // カスタム属性を読み込む
    for (AttributeType item : getUserResult.getUserAttributes()) {
      switch (item.getName()) {
        case "custom:user_id":
          userId = item.getValue();
          break;
        case "custom:role":
          role.add(item.getValue());
          break;
      }
    }

    // 整形して返却
    UserDto user = UserDto.builder()
      .user_id(userId).roles(role).build();
    return MyUserDetails.build(userDto);
  }
}
MyUserDetails.java
public class MyUserDetails implements UserDetails {
  private String userId;
  private Collection<? extends GrantedAuthority> authorities;

  // roleを整形してセットする
  public static MyUserDetails build(UserDto userDto) {
    List<GrantedAuthority> authorities =
        userDto.getRoles().stream()
            .map(role -> new SimpleGrantedAuthority(role.name()))
            .collect(Collectors.toList());

    return new MyUserDetails(userDto.getId(), userDto.getUsername(), userDto.getCompany_id(), authorities);
  }

  // ...Getter, Setterと続く
}

これでセッションユーザーにカスタム属性をセットできました。

■ 各種APIに本人チェックを追加した

先ほどセッションユーザーに仕込んだuserId属性を利用します。

パスパラメータに含まれるユーザーIDとCognitoのトークンにカスタム属性として設定されているユーザーIDを比較し、本人のみが情報を取得できるようにしました。

スクリーンショット 2023-01-15 17.01.39.png

以下のようなコードになります。
@AuthenticationPrincipal でセッションユーザーの情報を読み込んでいます。

UserController.java
@PostMapping("/users/{user_id}")
public User getUser(
    @PathVariable("user_id") String userId,
    // CognitoのIDトークンから取得したユーザー情報
    @AuthenticationPrincipal MyUserDetails user) {

  // パスパラメータとCognitoの情報を比較して本人確認
  if (!user.getUserId().equals(userId)) {
    // 本人ではない場合はエラーを返却
    throw new Exception();
  }

  // 本人ならデータを取得して返却する
  return service.getUser(userId);
}

この例ではユーザーIDをカスタム属性に含めていますが、例えばグループIDをカスタム属性にし「自分が所属しているグループの情報のみ閲覧可能」のような使い方ができるかと思います。

■ 各種APIに権限チェックを追加した

先ほどセッションユーザーに仕込んだGrantedAuthorityを利用します。
Cognito上の権限をチェックし、セッションユーザーがそのAPIを呼び出しても問題ないかチェックします。

スクリーンショット 2023-01-15 20.28.46.png

以下のようなコードになります。
GrantedAuthorityが付与されていれば、@PreAuthorizeを使って権限チェックが可能です。

UserController.java
// adminのみ実行可能
@PreAuthorize("hasRole('ROLE_admin')")
@DeleteMapping("/users/{user_id}")
public void deleteUser(
    @PathVariable("user_id") String userId) {
  service.deleteUser(userId);
}

まとめ

AWS Cognitoのカスタム属性を使って本人識別用のIDや権限情報を管理し、Spring Securityでの認証時に保持する仕組みを実装しました。

各APIに本人チェック・権限チェックする処理を挟むことで、指摘されたセキュリティリスクに対応しました。

カスタム属性、超便利ですね…
Azure ADでも似たようなことができるそうですよ。

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?