前提
- Herokuの対象アプリにAuth0アドオン導入済
- Spring Boot
- コンソールアプリ
- Maven
- テストコードなし
- APIコール数制限考慮なし
- セキュリティ考慮なし(ログや標準出力にトークンや個人情報出力)
- 取得ユーザ数1000までしか取得できない(Auth0の制限)
Auth0に接続アプリ作成
- Herokuの対象アプリの画面のアドオン一覧の「Auth0」を押下してAuth0の管理画面に入る
- 左サイドバーの「Applications」押下
- 右上の「+ CREATE APPLICATION」押下
- Nameは適当、typeは「Machine to Machine」を選択して「CREATE」押下
- 「Select an API...」欄は「Auth0 Management API」を選択、SCOPESは「read:users」をチェック、「AUTHORIZE」押下
- 「Settings」タブのDomain/Client ID/Client Secretの値を取得しておく
アプリ作成、起動
- eclipse起動
- 新規>Springスターター・プロジェクト
- 適当に入力して(思い通りにならないことがあるので、ロケーションだけはここでちゃんと入れた方がいいかも)次へ
- 以下選択して完了
- Spring Web
- Lombok
- pom.xmlからspring-boot-starter-testのdependency削除
- src/testフォルダ削除
- 以下についてeclipseのリファクタリング機能等で好きに修正
- プロジェクト名
- パッケージ
- メインクラス名
- pom.xmlのgroupId/artifactId/version/name/description
- src/main/<パッケージ>/Auth0Service.javaを作成、後述の内容を記述
- メインクラスに「implements CommandLineRunner」追加、実装されていないメソッドがある旨のエラーになるので後述の内容を記述
- application.propertiesに後述の内容を記述
- eclipseのBootダッシュボードで作成プロジェクトの構成を開き、環境変数AUTH0_DOMAIN/AUTH0_CLIENT_ID/AUTH0_CLIENT_SECRET作成、Auth0から取得しておいた値を設定
- eclipseのBootダッシュボードで起動
GitHubに登録
- GitHubでリポジトリを作成してURL取得
- 以下コマンドプロンプトで対象フォルダに移動してから
- git init
- git remote add origin <GitHubのURL>
- git add .
- git commit -m "1st commit"
- git push --set-upstream origin master
- 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"
}
感謝
ネットのいろんな記事を参考にしまくった、多謝