急に自分内Djangoブームが到来したので、Djangoに乗り換えはじめました。で、先日Node.jsで作ったAWSのCognito認証をPythonで作り直したので晒してみます。
事前準備
PythonでAWSにアクセスするにはIAMユーザーが必要となります。ロールにAmazonCognitoPowerUserを設定しておいてください。認証だけならロールをAmazonCognitoReadOnlyとかに落としても動くと思います。
アクセスキーも必要となりますので、認証情報タブを押してアクセスキーを作っておいてください。
つづいてAWSにPythonでアクセスするためのライブラリであるboto3を入れます
pip install boto3
AWS Cognitoで認証する
.awsに設定をしていなくても、boto3を使う時にregion等の設定を行うことができます。アプリの認証用ユーザーは固定すると思いますので、.awsを使うよりboto3を使う時に設定するほうが現実的だろうと思います。
AWS_ACCESS_KEY_IDやAWS_SECRET_ACCESS_KEY等は直接プログラムに埋め込むのは危険です(特にVCSを使ってる場合は注意)。外部ファイルに記述して読み込んで使ってください。
import boto3
def cognito_auth(user, passwd):
# 認証開始
try:
aws_client = boto3.client('cognito-idp',
region_name = "ap-northeast-1",
aws_access_key_id = "ここにAWS_ACCESS_KEY_IDを設定します",
aws_secret_access_key = "ここにAWS_SECRET_ACCESS_KEYを設定します",
)
aws_result = aws_client.admin_initiate_auth(
UserPoolId = "ここにユーザープールIDを設定します",
ClientId = "ここにアプリクライアントIDを設定します",
AuthFlow = "ADMIN_NO_SRP_AUTH",
AuthParameters = {
"USERNAME": user,
"PASSWORD": passwd,
}
)
# 認証完了
return aws_result
except :
# 認証失敗
return None
認証が成功すれば
aws_result["AuthenticationResult"]["AccessToken"]
aws_result["AuthenticationResult"]["RefreshToken"]
aws_result["AuthenticationResult"]["IdToken"]
のようにしてトークンを拾えます。
トークンが有効かチェック
トークンはjwt形式なのでjwtライブラリで分解して……ってやろうとしたら、VC Build Tool入れて、pycrypto入れて、pyjwt入れて、winrandomの参照先変えて、decode命令呼んだら、RS256デコードしようとしてエラー吐いて死んだ/(^o^)\
どうやってもWindowsだと暗号解除部分でエラー出すので、自力で確認するの諦めて、AWSにおまかせする方向にしました。
というわけで、保有しているトークンを使ってAWSからユーザー情報を取得します。トークンが無効ならAWSがエラーを出してくれるはずです。期限切れの確認はやってないので、もしかしたら間違ってるかもしれません。
認証成功時に
# 認証成功
req.session["aws_cognito"] = {
"user": "認証したユーザーの名前",
"access_token": aws_result["AuthenticationResult"]["AccessToken"],
"refresh_token": aws_result["AuthenticationResult"]["RefreshToken"]
}
のようにして、req.sessionにトークンを保存した前提でのプログラムとなります。
import boto3
def cognito_auth_check(req):
# 認証開始
# *********************************************************************
try:
# AWSクライアント認証
aws_client = boto3.client('cognito-idp',
region_name = "ap-northeast-1",
aws_access_key_id = "ここにAWS_ACCESS_KEY_IDを設定します",
aws_secret_access_key = "ここにAWS_SECRET_ACCESS_KEYを設定します",
)
try:
# 認証済みトークンの有効性確認
return aws_client.get_user(AccessToken=req.session["aws_cognito"]["access_token"])
except:
# 失敗。期限切れかもしれないのでREFRESH_TOKENで再認証
try:
aws_result = aws_client.admin_initiate_auth(
UserPoolId = "ここにユーザープールIDを設定します",
ClientId = "ここにアプリクライアントIDを設定します",
AuthFlow = "REFRESH_TOKEN_AUTH",
AuthParameters = {
"USERNAME": req.session["aws_cognito"]["user"],
"REFRESH_TOKEN": req.session["aws_cognito"]["refresh_token"]
}
)
# アクセストークン更新
req.session["aws_cognito"]["access_token"] = aws_result["AuthenticationResult"]["AccessToken"]
# 再度認証確認してみる
return aws_client.get_user(AccessToken=req.session["aws_cognito"]["access_token"])
except:
# REFRESH_TOKENすら期限切れや、接続障害によって失敗する可能性
return None
except:
# AWSへの接続失敗。IAM変えた? AWS接続障害の可能性も
return None
成功すればユーザー情報が取得できます。
Djangoでデコレーターとして使う
デコレーターを作っておくとDjangoのlogin_requiredデコレーターのように
@login_required
def api(req):
return HttpResponse('認証済みページの表示')
と、関数の前にポンッとデコレーターを置いたらページを認証が必要な状態にできるのが便利そうです。なのでデコレーターも作ってみました。
Djangoでのプログラムです。
from django.http import HttpResponse
# AWS Cognitoで認証済みであることを要求するデコレーター
def cognito_required(func):
def wrap(req, *args, **kwargs):
# 認証確認
req.user = cognito_auth_check(req)
if req.user != None:
# デコレータ下の関数を実行
return func(req, *args, **kwargs)
# 失敗終了
return HttpResponse('認証が必要です', status=401)
wrap.__doc__=func.__doc__
wrap.__name__=func.__name__
return wrap
これを、account/aws_auth.pyとかで作っておけば、
from account.aws_auth import cognito_required
@cognito_required
def api(req):
return HttpResponse('認証済みページの表示')
と、簡単に認証が必要なapiにすることができてステキ♡