【Java】OAuth 1.0 の認可処理について
JavaでOAuth 1.0を使用して認可処理を実装する機会があったのですがシグネチャの生成に少し手間取ったので、備忘録として残しておきます。
ユーザーからのリクエストがGadgetサーバーを経由してクライアントサーバーに転送され、更にAPIサーバーにリクエストする際の認証ヘッダー生成処理となります。
API通信部にはHttpURLConnectionクラスを使用しています。
⬇️ リバースプロキシ やキャッシュの使い方が詳しく記載されていて、お勧めです
OAuth1.0
ドキュメントはこちらです。
openid-foundation-japan.github.io
Java
以下のコードでは、クライアントから受け取ったOAuth tokenなどを元にAuthorizationヘッダを生成、付与してAPIサーバーにリクエストを送信しています。
公開する訳にはいかないので値は適当に変えて抜粋しています
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import java.nio.charset.StandardCharsets;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.apache.commons.codec.binary.Base64;
@Controller
@RequestMapping("/test")
public class TestController {
@RequestMapping("/oauth-proxy")
@ResponseBody
public String oauthTest(HttpServletRequest request, HttpServletResponse response) {
// Authorization headerから oauthToken と oauthTokenSecret を抽出
String header = request.getHeader("Authorization");
Map<String, String> authMap = getHeaderMap(header);
String baseUrl = "https://test.com/api/rest/v1/xxxxx";
String apiUrl = baseUrl + "?_method=check";
String method = "POST";
String response = "";
// OAuthで利用するパラメータ
String consumerSecret = "xxxxxxxxxx";
String oauthConsumerKey = "xxxxxxxxxx";
String oauthSignatureMethod = "HMAC-SHA1";
String oauthVersion = "1.0";
String oauthToken = authMap.get("oauth_token");
String oauthTokenSecret = authMap.get("oauth_token_secret");
HttpURLConnection huc = null;
InputStream stream = null;
OutputStream os = null;
try {
// 昇順ソートが必要なのでSortedMap
SortedMap<String, String> params = new TreeMap<String, String>();
params.put("oauth_consumer_key", oauthConsumerKey);
params.put("oauth_signature_method", oauthSignatureMethod);
params.put("oauth_timestamp", String.valueOf(getUnixTime()));
params.put("oauth_nonce", String.valueOf(Math.random()));
params.put("oauth_token", oauthToken);
params.put("oauth_version", oauthVersion);
// クエリパラメータがある場合は含める
params.put("_method", "check");
// baseStringに含めるURLにはクエリパラメータは含めない
String baseString = makeBaseString(params, baseUrl, method);
// baseStringからsignatureを生成
String signature = makeSignature(consumerSecret, oauthTokenSecret, baseString);
params.put("oauth_signature", signature);
// Authorizationヘッダを生成、クエリパラメータは含めないので削除
params.remove("_method");
String authHeader = makeAuthHeader(params);
URL url = new URL(apiUrl);
huc = (HttpURLConnection) url.openConnection();
huc.setRequestMethod(method);
huc.addRequestProperty("Content-Type", "application/json; charset=UTF-8");
huc.setRequestProperty("Authorization", authHeader);
huc.setDoOutput(true);
os = huc.getOutputStream();
// リクエストボディ
PrintStream ps = new PrintStream(os, true, StandardCharsets.UTF_8.toString());
ps.print("{\"data\": \"test\"}");
ps.close();
huc.connect();
// レスポンス取得
System.out.println(huc.getResponseCode());
stream = huc.getInputStream();
response = getResponse(stream, huc);
System.out.println(response);
} catch (Exception e) {
System.out.println(e.getMessage());
try {
response = getResponse(stream, huc);
System.out.println(response);
} catch (Exception ex) {}
} finally {
if (stream != null) {
try {
stream.close();
} catch (Exception e) {}
}
if (os != null) {
try {
os.close();
} catch (Exception e) {}
}
}
return response;
}
private Map<String, String> getHeaderMap(String header) {
String[] split = header.replace("\"", "").replace(" ", "").split(",");
Map<String, String> map = new HashMap<>();
for (int i = 1; i < split.length; i++) {
String[] split1 = split[i].split("=");
map.put(split1[0], split1[1]);
}
return map;
}
private String urlEncodeUTF8(String s) throws UnsupportedEncodingException {
if (s == null || s.isEmpty()) {
return "";
}
return URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
}
private String makeBaseString(Map<String, String> params, String baseUrl, String method) throws UnsupportedEncodingException {
String baseParamString = "";
for (Entry<String, String> param : params.entrySet()) {
baseParamString += "&" + param.getKey() + "=" + param.getValue();
}
baseParamString = baseParamString.substring(1);
// 各パラメータをurlEncode
return method.toUpperCase() + "&" + urlEncodeUTF8(baseUrl) + "&" + urlEncodeUTF8(baseParamString);
}
private String makeSignature(String consumerSecret, String oauthTokenSecret, String baseString)
throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
String signatureKey = urlEncodeUTF8(consumerSecret) + "&" + urlEncodeUTF8(oauthTokenSecret);
SecretKeySpec signingKey = new SecretKeySpec(signatureKey.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance(signingKey.getAlgorithm());
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(baseString.getBytes());
// HmacSHA1形式でハッシュ化してbase64エンコード
return new String(Base64.encodeBase64(rawHmac), StandardCharsets.UTF_8);
}
private String makeAuthHeader(Map<String, String> params) throws UnsupportedEncodingException {
String paramStr = "";
// ヘッダ項目もurlEncode
for (Entry<String, String> param : params.entrySet()) {
paramStr += ", " + param.getKey() + "=\"" + urlEncodeUTF8(param.getValue()) + "\"";
}
return "OAuth realm=\"\"" + paramStr;
}
private int getUnixTime() {
return (int) (System.currentTimeMillis() / 1000L);
}
private String getResponse(InputStream stream, HttpURLConnection huc) throws IOException {
StringBuffer sb = new StringBuffer();
String line = "";
BufferedReader br = null;
InputStreamReader reader = null;
try {
reader = new InputStreamReader(stream, StandardCharsets.UTF_8);
br = new BufferedReader(reader);
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (Exception e) {
System.out.println(e.getMessage());
throw e;
} finally {
if (reader != null) {
try {
reader.close();
} catch (Exception e) {}
}
if (br != null) {
try {
br.close();
} catch (Exception e) {}
}
}
return sb.toString();
}
}
Java、久しぶりに書きました😝