0
0

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 3 years have passed since last update.

HerokuアドオンのAuth0から全ユーザ情報取得 (1000件以内)

Last updated at Posted at 2021-02-03

前提

  1. Herokuの対象アプリにAuth0アドオン導入済
  2. Spring Boot
  3. コンソールアプリ
  4. Maven
  5. テストコードなし
  6. APIコール数制限考慮なし
  7. セキュリティ考慮なし(ログや標準出力にトークンや個人情報出力)
  8. 取得ユーザ数1000までしか取得できない(Auth0の制限)

Auth0に接続アプリ作成

  1. Herokuの対象アプリの画面のアドオン一覧の「Auth0」を押下してAuth0の管理画面に入る
  2. 左サイドバーの「Applications」押下
  3. 右上の「+ CREATE APPLICATION」押下
  4. Nameは適当、typeは「Machine to Machine」を選択して「CREATE」押下
  5. 「Select an API...」欄は「Auth0 Management API」を選択、SCOPESは「read:users」をチェック、「AUTHORIZE」押下
  6. 「Settings」タブのDomain/Client ID/Client Secretの値を取得しておく

アプリ作成、起動

  1. eclipse起動
  2. 新規>Springスターター・プロジェクト
  3. 適当に入力して(思い通りにならないことがあるので、ロケーションだけはここでちゃんと入れた方がいいかも)次へ
  4. 以下選択して完了
  5. Spring Web
  6. Lombok
  7. pom.xmlからspring-boot-starter-testのdependency削除
  8. src/testフォルダ削除
  9. 以下についてeclipseのリファクタリング機能等で好きに修正
  10. プロジェクト名
  11. パッケージ
  12. メインクラス名
  13. pom.xmlのgroupId/artifactId/version/name/description
  14. src/main/<パッケージ>/Auth0Service.javaを作成、後述の内容を記述
  15. メインクラスに「implements CommandLineRunner」追加、実装されていないメソッドがある旨のエラーになるので後述の内容を記述
  16. application.propertiesに後述の内容を記述
  17. eclipseのBootダッシュボードで作成プロジェクトの構成を開き、環境変数AUTH0_DOMAIN/AUTH0_CLIENT_ID/AUTH0_CLIENT_SECRET作成、Auth0から取得しておいた値を設定
  18. eclipseのBootダッシュボードで起動

GitHubに登録

  1. GitHubでリポジトリを作成してURL取得
  2. 以下コマンドプロンプトで対象フォルダに移動してから
  3. git init
  4. git remote add origin <GitHubのURL>
  5. git add .
  6. git commit -m "1st commit"
  7. git push --set-upstream origin master
  8. eclipseでプロジェクト名右クリック、チーム>プロジェクトの共用、Git、完了

ソース

Auth0Service.java
@Component
@Slf4j
public class Auth0Service {
    @Value("${auth0.domain}")
    protected String auth0Domain;

    @Value("${auth0.clientId}")
    protected String auth0ClientId;

    @Value("${auth0.clientSecret}")
    protected String auth0ClientSecret;

    // 全ユーザ取得 ------------------------------------------------------------

    // 全件取るまでやめないのでAPIコール数制限等に注意

    public List<User> getAllUsers() {
        log.info("全ユーザを取得します。");

        List<User> users = new ArrayList<>();
        int page = 0;
        while (true) {
            // URI作成
            URI uri = UriComponentsBuilder.fromHttpUrl("https://" + auth0Domain + "/api/v2/users")
                    .queryParam("page", page)
                    .queryParam("per_page", 30)
                    .queryParam("include_totals", true)
                    .queryParam("sort", "created_at:1")
                    .queryParam("fields", "email,name,user_id,user_metadata,last_login,logins_count") // 許可なく個人情報を取得しないように注意
                    // .queryParam("q", "logins_count:[1 TO *]") // ログイン回数1回以上
                    .build().toUri();

            // ヘッダ作成
            HttpHeaders httpHeaders = new HttpHeaders();
            addAuthorizationHeader(httpHeaders);

            // リクエスト
            ResponseEntity<UsersResponseData> responseEntity = restTemplate.exchange(
                    uri,
                    HttpMethod.GET,
                    new HttpEntity<>(httpHeaders),
                    UsersResponseData.class);

            // 結果取り出し
            UsersResponseData usersResponseData = responseEntity.getBody();
            users.addAll(usersResponseData.getUsers());
            if (usersResponseData.getLength() < usersResponseData.getLimit()) {
                log.info("全ユーザを取得しました。({}件)", users.size());
                return users;
            }

            // 次ページ取得準備
            page++;
        }
    }

    @Data
    private static class UsersResponseData { // staticにしないとnon-static inner classes like this can only by instantiated using default, no-argument constructorエラー
        // レスポンスJSONのうち取得したい項目だけフィールド定義する
        private Integer start;
        private Integer limit;
        private Integer length;
        private List<User> users;
        private Integer total;
    }

    @Data
    public static class User {
        private String email;
        private String name;
        private String user_id; // userIdにしたいがJSONがuser_idなのでやむなし、Lombokで回避できないか?
        private UserMetadata user_metadata;
        private String last_login;
        private Integer logins_count;
    }

    @Data
    public static class UserMetadata {
        private String accountId;
        private String contactId;
    }

    // 認証 --------------------------------------------------------------------

    private OAuthTokenResponseData oauthTokenResponseData;

    private void addAuthorizationHeader(HttpHeaders httpHeaders) { // expireには非対応
        if (oauthTokenResponseData == null) {
            oauthTokenResponseData = restTemplate.postForObject("https://" + auth0Domain + "/oauth/token", new OAuthTokenRequestData(auth0ClientId, auth0ClientSecret, "https://" + auth0Domain + "/api/v2/", "client_credentials"), OAuthTokenResponseData.class);
        }
        httpHeaders.add(HttpHeaders.AUTHORIZATION, oauthTokenResponseData.getToken_type() + " " + oauthTokenResponseData.getAccess_token());
    }

    @Data
    private class OAuthTokenRequestData {
        final private String client_id;
        final private String client_secret;
        final private String audience;
        final private String grant_type;
    }

    @Data
    private static class OAuthTokenResponseData {
        private String access_token;
        private String scope;
        private Integer expires_in;
        private String token_type;
    }

    // RestTemplate初期化 ------------------------------------------------------

    @Autowired
    RestTemplate restTemplate;

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
        if (log.isTraceEnabled()) {
            restTemplateBuilder = restTemplateBuilder
                    .requestFactory(() -> new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))
                    .additionalInterceptors(new ClientHttpRequestInterceptorImpl());
        }
        return restTemplateBuilder.build();
    }

    // ログ出力 ----------------------------------------------------------------

    private class ClientHttpRequestInterceptorImpl implements ClientHttpRequestInterceptor {
        @Override
        public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            UUID uuid = UUID.randomUUID(); // リクエストとレスポンスの紐付け用
            logRequest(uuid, httpRequest, body);
            long startTimeMillis = System.currentTimeMillis();
            ClientHttpResponse clientHttpResponse = execution.execute(httpRequest, body);
            long responseTimeMillis = System.currentTimeMillis() - startTimeMillis;
            logResponse(uuid, clientHttpResponse, responseTimeMillis);
            return clientHttpResponse;
        }

        private void logRequest(UUID uuid, HttpRequest httpRequest, byte[] body) throws IOException {
            log.trace("HttpRequest - UUID: {}, Method: {}, URI: {}", uuid, httpRequest.getMethodValue(), httpRequest.getURI());
            logHeaders(httpRequest.getHeaders());
            logBody(httpRequest.getHeaders().getContentType(), new String(body));
        }

        private void logResponse(UUID uuid, ClientHttpResponse clientHttpResponse, long responseTimeMillis) throws IOException {
            log.trace("ClientHttpResponse - UUID: {}, Response Time: {} ms, Status: {} {}", uuid, responseTimeMillis, clientHttpResponse.getRawStatusCode(), clientHttpResponse.getStatusText());
            logHeaders(clientHttpResponse.getHeaders());
            logBody(clientHttpResponse.getHeaders().getContentType(), StreamUtils.copyToString(clientHttpResponse.getBody(), StandardCharsets.UTF_8));
        }

        private void logHeaders(HttpHeaders httpHeaders) {
            StringBuilder stringBuilder = new StringBuilder();
            httpHeaders.forEach((key, values) -> {
                values.forEach((value) -> {
                    stringBuilder.append(key + ": " + value + System.lineSeparator());
                });
            });
            log.trace("-- Headers -----------------------------" + System.lineSeparator() + "{}", stringBuilder.toString().trim());
        }

        private void logBody(MediaType contentType, String body /* バイナリ非対応 */) throws IOException {
            if (body != null && !body.isEmpty()) {
                log.trace("-- Body --------------------------------" + System.lineSeparator() + "{}", body);
            }
            if (MediaType.APPLICATION_JSON.includes(contentType)) {
                log.trace("-- Body formatted ----------------------" + System.lineSeparator() + "{}", ppJson(body));
            }
        }

        private String ppJson(String jsonString) throws JsonProcessingException, JsonMappingException {
            return (new ObjectMapper()).readTree(jsonString).toPrettyString();
        }
    }
}
メインクラス.java
    @Autowired
    private Auth0Service auth0Service;

    @Override
    public void run(String... args) throws Exception {
        List<User> allUsers = auth0Service.getAllUsers();
        System.out.println(allUsers.get(1).getEmail());
        System.out.println(allUsers.get(1).getName());
        System.out.println(allUsers.get(1).getUser_id());
        System.out.println(allUsers.get(1).getUser_metadata().getAccountId());
        System.out.println(allUsers.get(1).getUser_metadata().getContactId());
        System.out.println(allUsers.get(1).getLast_login());
        System.out.println(allUsers.get(1).getLogins_count());
    }
application.properties
# Webサーバ起動抑止
spring.main.web-application-type=none

# ログレベルを環境変数から取得、デフォルトはTRACE
logging.level.<パッケージ>.Auth0Service=${LOG_LEVEL:TRACE}

# Auth0接続情報を環境変数から取得
auth0.domain=${AUTH0_DOMAIN}
auth0.clientId=${AUTH0_CLIENT_ID}
auth0.clientSecret=${AUTH0_CLIENT_SECRET}

1000を超えるユーザを取得しようとした際のレスポンス

400 Bad Requestが返る、ボディは以下

{
  "statusCode" : 400,
  "error" : "Bad Request",
  "message" : "You can only page through the first 1000 records. See https://auth0.com/docs/users/search/v3/view-search-results-by-page#limitation",
  "errorCode" : "invalid_paging"
}

感謝

ネットのいろんな記事を参考にしまくった、多謝

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?