Spring REST API用のOAuth2 – AngularJSの更新トークンを処理する
1概要
このチュートリアルでは、まとめて始めたOAuthパスワードフローを引き続き検討します:/rest-api-spring-oauth2-angularjs AngularJSアプリ。
2アクセストークンの有効期限
まず、ユーザーがアプリケーションにログインしたときにクライアントがアクセストークンを取得していたことを覚えておいてください。
function obtainAccessToken(params) {
var req = {
method: 'POST',
url: "oauth/token",
headers: {"Content-type": "application/x-www-form-urlencoded; charset=utf-8"},
data: $httpParamSerializer(params)
}
$http(req).then(
function(data) {
$http.defaults.headers.common.Authorization= 'Bearer ' + data.data.access__token;
var expireDate = new Date (new Date().getTime() + (1000 ** data.data.expires__in));
$cookies.put("access__token", data.data.access__token, {'expires': expireDate});
window.location.href="index";
},function() {
console.log("error");
window.location.href = "login";
});
}
トークン自体の有効期限が切れるときに基づいて有効期限が切れるCookieに、アクセストークンがどのように格納されるかに注意してください。
理解しておくべき重要な点は、** Cookie自体は保存用にのみ使用され、OAuthフローの他の部分を促進するものではないということです。たとえば、ブラウザがリクエストとともに自動的にクッキーをサーバーに送信することはありません。
また、実際にこの
obtainAccessToken()
関数を呼び出す方法にも注意してください。
$scope.loginData = {
grant__type:"password",
username: "",
password: "",
client__id: "fooClientIdPassword"
};
$scope.login = function() {
obtainAccessToken($scope.loginData);
}
3プロキシ
これで、Zuulプロキシをフロントエンドアプリケーションで実行し、基本的にフロントエンドクライアントと承認サーバーの間に座ることになります。
プロキシのルートを設定しましょう。
zuul:
routes:
oauth:
path:/oauth/** **
url: http://localhost:8081/spring-security-oauth-server/oauth
ここで興味深いのは、トラフィックを承認サーバーにプロキシするだけで、他には何もしていないことです。クライアントが新しいトークンを取得しているときに、プロキシが入ってくることだけが必要です。
Zuulの基本を学びたいのなら、
Zuulのメインの記事
を読んでください。
4基本認証を行うZuulフィルタ
プロキシの最初の使い方は簡単です – 私たちのアプリ“
client secret
”をJavaScriptで公開する代わりに、トークンリクエストにアクセスするためのAuthorizationヘッダを追加するためにZuulプレフィルタを使用します。
@Component
public class CustomPreZuulFilter extends ZuulFilter {
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.getRequest().getRequestURI().contains("oauth/token")) {
byte[]encoded;
try {
encoded = Base64.encode("fooClientIdPassword:secret".getBytes("UTF-8"));
ctx.addZuulRequestHeader("Authorization", "Basic " + new String(encoded));
} catch (UnsupportedEncodingException e) {
logger.error("Error occured in pre filter", e);
}
}
return null;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public int filterOrder() {
return -2;
}
@Override
public String filterType() {
return "pre";
}
}
ただし、これによってセキュリティが強化されるわけではなく、トークンのエンドポイントがクライアント認証情報を使用した基本認証で保護されているためです。
実装の観点からは、フィルタの種類は特に注目に値します。リクエストを渡す前に処理するために、フィルタタイプ「pre」を使用しています。
5リフレッシュトークンをクッキーに入れる
楽しいものに。
ここで計画しているのは、クライアントに更新トークンをCookieとして取得させることです。通常のCookieだけでなく、非常に制限されたパス(
/oauth/token
)を持つセキュアなHTTP専用のcookie。
レスポンスのJSON本文からRefresh Tokenを抽出してcookieに設定するZuulポストフィルタを設定します。
@Component
public class CustomPostZuulFilter extends ZuulFilter {
private ObjectMapper mapper = new ObjectMapper();
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
try {
InputStream is = ctx.getResponseDataStream();
String responseBody = IOUtils.toString(is, "UTF-8");
if (responseBody.contains("refresh__token")) {
Map<String, Object> responseMap = mapper.readValue(
responseBody, new TypeReference<Map<String, Object>>() {});
String refreshToken = responseMap.get("refresh__token").toString();
responseMap.remove("refresh__token");
responseBody = mapper.writeValueAsString(responseMap);
Cookie cookie = new Cookie("refreshToken", refreshToken);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath(ctx.getRequest().getContextPath() + "/oauth/token");
cookie.setMaxAge(2592000);//30 days
ctx.getResponse().addCookie(cookie);
}
ctx.setResponseBody(responseBody);
} catch (IOException e) {
logger.error("Error occured in zuul post filter", e);
}
return null;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public int filterOrder() {
return 10;
}
@Override
public String filterType() {
return "post";
}
}
ここで理解するべきいくつかの興味深いこと:
応答を読み取るためにZuulポストフィルタを使用し、リフレッシュを抽出
トークン
makeのJSONレスポンスから
refresh
token__の値を削除しました
Cookieの外側のフロントエンドにアクセスできないようにする
クッキーの最大保存期間を
30日** に設定します。
トークンの有効期限
6. Cookieからリフレッシュトークンを取得して使用する
CookieにRefresh Tokenがあるので、フロントエンドのAngular JSアプリケーションがトークンの更新を起動しようとすると、
/oauth/token
にリクエストが送信され、ブラウザはもちろんそのcookieを送信します。
これで、プロキシからRefresh Tokenを抽出してHTTPパラメータとして送信する別のフィルタが作成されます。これにより、リクエストが有効になります。
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
...
HttpServletRequest req = ctx.getRequest();
String refreshToken = extractRefreshToken(req);
if (refreshToken != null) {
Map<String, String[]> param = new HashMap<String, String[]>();
param.put("refresh__token", new String[]{ refreshToken });
param.put("grant__type", new String[]{ "refresh__token" });
ctx.setRequest(new CustomHttpServletRequest(req, param));
}
...
}
private String extractRefreshToken(HttpServletRequest req) {
Cookie[]cookies = req.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equalsIgnoreCase("refreshToken")) {
return cookies[i].getValue();
}
}
}
return null;
}
そして、これが私たちの
CustomHttpServletRequest
です。
public class CustomHttpServletRequest extends HttpServletRequestWrapper {
private Map<String, String[]> additionalParams;
private HttpServletRequest request;
public CustomHttpServletRequest(
HttpServletRequest request, Map<String, String[]> additionalParams) {
super(request);
this.request = request;
this.additionalParams = additionalParams;
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> map = request.getParameterMap();
Map<String, String[]> param = new HashMap<String, String[]>();
param.putAll(map);
param.putAll(additionalParams);
return param;
}
}
繰り返しますが、ここには重要な実装に関する多くの注意事項があります。
-
プロキシはCookieから更新トークンを抽出しています
-
それはそれを
refresh
token__パラメータに設定します -
また、
grant
type
を
refresh
token
に設定しています -
refreshToken
cookieがない場合(期限切れまたは最初のログイン) –
その後、アクセストークン要求は変更されずにリダイレクトされます。
7. AngularJS
からのアクセストークンの更新
最後に、単純なフロントエンドアプリケーションを修正して、実際にトークンの更新を利用しましょう。
これが私たちの関数
refreshAccessToken()
です。
$scope.refreshAccessToken = function() {
obtainAccessToken($scope.refreshData);
}
そしてここで私たちの
$ scope.refreshData
:
$scope.refreshData = {grant__type:"refresh__token"};
既存の
obtainAccessToken
関数を単純に使用していることと、さまざまな入力を渡していることに注目してください。
また、
refresh
token__を自分自身で追加しているのではないことにも注意してください。これはZuulフィルタによって処理されます。
8結論
このOAuthチュートリアルでは、更新トークンをAngularJSクライアントアプリケーションに格納する方法、有効期限が切れたアクセストークンを更新する方法、およびそのすべてにZuulプロキシを利用する方法を学びました。
このチュートリアルの
完全な実装
はhttps://github.com/eugenp/spring-security-oauth/[the github project]にあります – これはEclipseベースのプロジェクトなので、インポートと実行が簡単です。そのまま。