前回、SwaggerでRESTful環境をスマートに構築しました。
使いこなしていく上で、手間取ったことの一つに、SecurityDefinitionsの定義があります。
SwaggerにおけるSecurityDefinitionsとは、HTTPヘッダのAuthorizationを使って、ユーザ・パスワードによるベーシック認証やOpenID Connectのトークン認証するための定義です。
swagger-nodeを使ったRESTful環境でもちゃんと対応しているんです!
せっかくなので、使ってみます。
Swagger SecurityDefitions定義
Swagger 2.0では、以下の3つの方式が定義可能です。
- Basic認証
- API Key
- OAuth2 認証フロー
[参考情報]
https://swagger.io/docs/specification/2-0/authentication/basic-authentication/
このうち、OAuth2 認証フローは、細かな設定ができるようなのですが、設定が面倒だったり、Swagger 3.0でまた仕様がかわるので、使うのをやめて、API Keyで代用します。トークンはどうなっちゃうの?と心配されるかもしれませんが、以降で、それっぽくトークン検証していますので、お楽しみに。
それでは早速、定義方法を示します。
共通の定義をしてから、各APIにどの定義を使うかという順番です。
まず定義から。
securityDefinitions:
basicAuth:
type: basic
tokenAuth:
type: "apiKey"
name: "Authorization"
in: "header"
見ての通りですね。
名前は何でもよいのですが、それぞれ「basicAuth」と「tokenAuth」という定義の名前を付けてみました。
それらを実際に使うAPIの定義では、以下のように指定します。security: のところです。
これだけです。
paths:
/test_auth:
post:
x-swagger-router-controller: test
operationId: test_post
security:
- basicAuth: [] もしくは – tokenAuth: []
tags:
- test
description: Post message to test
parameters:
- in: body
name: body
required: true
schema:
$ref: '#/definitions/SimpleRequest'
responses:
'200':
description: Success
schema:
$ref: '#/definitions/SimpleResponse'
default:
description: Error
schema:
$ref: "#/definitions/ErrorResponse"
それではさっそくSwagger-EditorでSwagger定義ファイルを見てみましょう。
ページの先頭あたりに、Authorize というボタンが増えています。
押してみると、
上記のように、ベーシック認証では、ユーザ名とパスワードが入れられるようになりました。
トークン認証においても、Authorizationヘッダに値を指定できるようになりました。
これらは、Swagger定義ファイルのSecurityDefinitionsの定義部分に該当します。
次は、API定義の右端の所に、錠のマークがついているのがわかりますでしょうか?
「Try it out」で実行してみると、basicAuthなのかtokenAuthなのかに従って、Authorizationヘッダに値を勝手につけてくれます。
しかも、ベーシック認証の場合には、ユーザ名・パスワードが以下のようにエンコードしてくれますので、至れり尽くせりです。
Authorization: Basic base64Encode("ユーザ名:パスワード”)
ここまでやってくれるSwagger-Editorは素晴らしいとしか言いようがないですね。
Swagger実行環境の整備
実は、上記のSwagger定義ファイルにSecurityDefinitionsを定義した状態でSwagger実行環境を立ち上げると、実行に失敗してしまいます。定義が足りないという理由です。
そこで、以下の定義を追加することで、Swaggerを実行できる状態になります。
config.swaggerSecurityHandlersの部分です。
・・・・
var config = {
appRoot: __dirname // required config
};
config.swaggerSecurityHandlers = {
basicAuth : function (req, authOrSecDef, scopesOrApiKey, cb) {
// todo
// cb() for success
// cb(error) for error
},
tokenAuth : function (req, authOrSecDef, scopesOrApiKey, cb) {
// todo
// cb() for success
// cb(error) for error
}
};
SwaggerExpress.create(config, function(err, swaggerExpress) {
・・・
basicAuthとtokenAuthという名称は、SecurityDefinitionsで定義したあの名前です。
これでエラーにはならなくなるのですが、ついでに便利なロジックを埋め込んどいてあげましょう。
Authorizationヘッダに入力された内容を解析して、コントローラに渡してあげる、というものです。そうすることで、コントローラ側で、ユーザ名とパスワードが合っているか、など判断することができるようになります。
最終的にはこの通りです。
var jwt_decode = require('jwt-decode');
config.swaggerSecurityHandlers = {
basicAuth : function (req, authOrSecDef, scopesOrApiKey, cb) {
try{
var basic = req.headers.authorization.trim();
if(basic.toLowerCase().startsWith('basic '))
basic = basic.slice(6).trim();
var buf = new Buffer(basic, 'base64');
var ascii = buf.toString('ascii');
req.requestContext = {
authorizer : {
basic : ascii.split(':')
}
};
cb();
}catch(error){
cb(error);
}
},
tokenAuth : function (req, authOrSecDef, scopesOrApiKey, cb) {
try{
var decoded = jwt_decode(scopesOrApiKey);
req.requestContext = {
authorizer : {
claims : decoded
}
};
cb();
}catch(error){
cb(error);
}
}
};
まずは、ベーシック認証(basicAuth)から。
AuthorizationヘッダにはBase64エンコードされてきますので、それをデコードします。
デコードした結果を、req.requestContext.authorizer.basicに、ユーザ名とパスワードの配列として、コントローラに渡します。
次は、トークン認証(tokenAuth)。
トークンは、JWTでエンコードされています。それを解析するのに便利なモジュール「jwt-decode」を使わせていただきました。
これを使ってデコードした結果をreq.requestContext.authorizer.claimsに、subやらaudやらissやら、トークンの内容をそのまま設定してコントローラに渡しています。
以下も忘れずに実行しておいてください。
npm install jwt-decode --save
本来であれば、JWTの公開鍵を使って検証するのですが、今回は単純にデコードだけにしました。ですので、上記のコードでは、トークンの生成元が正しいかどうかは確認していないので、そのまま運用には使わないで下さい。正しくは、ここら辺を参考にしてください。
以上で、Swagger-Editor上から、ユーザパスワードやトークンを指定して、API呼び出しができるようになりました。
swagger-nodeは本当に素晴らしいですね。
ご参考
最終系はこちらです。
SwaggerでLambdaのデバッグ環境を作る(1)
以上