[Flask] Authlib를 이용한 oauth server 구현 #1

강버섯·2022년 2월 23일
1

AUTHORIZATION

목록 보기
7/9

👉 oauth server 구성 요소

📁 oauth server(provider)

Login한 사용자에게 authorization code를 보내주고, authorization code를 받아 access token을 내려준다.
즉, 사용자에 대한 validation을 수행한다.

📁 oauth client

client id, (client secret), redirect uri, grant type

  • token : access token, refresh token, expires at, issued at, revoke at
  • code : authorization code
  • user : username, password, email

👉 Authlib

flask auth server(provider)를 제공해준다.
Authlib를 사용하면 flask를 이용해 간단하게 oauth server를 구현해볼 수 있다.

📁 Authlib github 👉 example code

📁 Authorization Server

✏️ User

Resource Owner로 로그인을 하는 사용자와 관련된 객체이다.
인증을 받음을 통해 제공하는 서비스를 이용하려고 하는 주체에 해당한다.

✏️ Client

client_id, client_secret, redirect_uri, grant_type 등의 값 인증 정보를 요청할 때 필요한 값을 가지고 있는 객체로 서비스 제공자에 해당한다.
redirect_uri, response type ,grant_type, scope의 값들은 client_metadata column에 json 형태로 저장된다.

client_matadata 👇

{
  "client_name": "인가 받은 client",
  "client_uri": "인가 받은 client가 사용하는 주소",
  "grant_types": [
    "client가 사용할 수 있는 grant type들",
    "authorization_code",
    "password",
    "refresh_token"
  ],
  "redirect_uris": [
    "callback 주소"
  ],
  "response_types": [
    "응답으로 받을 수 있는 값들"
    "code",
    "password",
    "token"
  ],
  "scope": "client에게 인가된 권한들",
  "skip_consent": 사용자의 동의를 받을 것인지 여부(true / false),
  "token_endpoint_auth_method": "token을 인증하는 method 타입(basic / post)"
}

사용자가 로그인을 시도하면 Client에서는

  1. client id와 (필요한 경우)client secretBasic 인증 방식으로 OAuth Server로부터 인증을 받고,
  2. 이후 사용할 grant 방식에 따라 사용자 인증을 진행한다.

Client 클래스는 Authlib를 이용하면 OAuth2ClientMixin 클래스를 제공해주기 때문에, 상속 받아 활용하면 쉽게 구현할 수 있다.

from authlib.integrations.sqla_oauth2 import OAuth2ClientMixin

# [client] - client_id, client_secret, client_metadata, expires_at
class Client(db.Model, OAuth2ClientMixin):
    id = Column(Integer, primary_key=True)
    user_id = Column(
        Integer, ForeignKey(User.id, ondelete='CASCADE')
    )

✏️ Token

OAuth는 Bearer 방식의 token을 사용한다.
Token은 사용자에 대한 인증이 성공된 경우, 사용자가 resource를 접근하기 위해서 사용된다.
기본적으로 access token, refresh token, client id, expire at, scope 등의 정보로 구성되어있다.

Token 클래스 역시 Authlib에서 제공하는 OAuth2TokenMixin을 통해 쉽게 구현이 가능하다.

from authlib.integrations.sqla_oauth2 import OAuth2TokenMixin

# [token] - access_token, refresh_token, expires_at, scope, client_id
# 발급 받은 token에 대한 정보 저장
class Token(db.Model, OAuth2TokenMixin):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(
        db.Integer, db.ForeignKey(User.id, ondelete='CASCADE')
    )
    user = db.relationship('User')

✏️ Server

Authlib에는 AuthorizationServer라는 인증 요청과 응답을 처리하는 클래스를 제공해주고 있다.
간단하게 instance를 생성하고 flask app에 연결시켜주면 활용이 가능하다.

app/oauth/server.py 👇

from authlib.integrations.flask_oauth2 import AuthorizationServer

# 인증을 요청한 client 찾기
def query_client(client_id):
    return db.session.query(Client).filter_by(client_id=client_id).first()


# authorization code를 받고 token을 생성하여 저장
def save_token(token_data, request):
    if request.user:
        user_id = request.user.get_user_id()
    else:
        # client_credentials grant_type
        user_id = request.client.user_id
        # or, depending on how you treat client_credentials
        # user_id = None

    token = Token(
        client_id=request.client.client_id,
        user_id=user_id,
        **token_data
    )
    db.session.add(token)
    db.session.commit()


oauth_server = AuthorizationServer()

추가적으로 필요한 로직이 있다면 제공되는 query_client(), save_token()을 수정하고, AuthorizationServer instance도 생성을 했다면 flask app을 연결시켜준다.

app/init.py 👇

from flask import Flask

app = Flask(__name__)

oauth_server.init_app(app, query_client=query_client, save_token=save_token)

config의 값을 변경하지 않아도 정상 동작하지만, 세부적인 설정을 하고 싶다면 config의 값을 변경시켜주면 된다.

  • OAUTH2_TOKEN_EXPIRES_IN : 발행하는 token의 유효 기간 설정
OAUTH2_TOKEN_EXPIRES_IN = {
	'authorization_code': 864000,
    'implicit': 3600,
    'password': 864000,
    'client_credentials': 864000
}
  • OAUTH2_ACCESS_TOKEN_GENERATOR : access token에 관한 설정
  • OAUTH2_REFRESH_TOKEN_GENERATOR : refresh token 관련 설정
    -> True / False 값으로 token 생성 시 refresh token을 줄 것인가를 설정할 수 있다.

✏️ endpoint

Authorization Server에대한 설정을 마쳤으면, 인증을 받기 위한 endpoint를 작성해주면 된다.

from flask import Blueprint, request, render_template
from flask_login import current_user, login_required

from login.oauth.server import oauth_server

oauth = Blueprint('oauth', __name__)


# authorize code를 받아오는 부분 >> auth code를 받기 위해서는 login을 필요로 함
@oauth.route('/authorize', methods=['GET', 'POST'])
@login_required
def authorize():
        if request.method == 'GET':
        # get으로 들어오는 경우 >> 사용자로부터 consent 받기
        # 사용자에게 consent를 받는 부분
        try:
            # grant = oauth_server.get_consent_grant(end_user=current_user)
            grant = oauth_server.validate_consent_request(end_user=current_user)
            client = grant.client
            scope = client.get_allowed_scope(grant.request.scope)
        except OAuth2Error as error:
            current_app.logger.exception('oauth-error')
            return error.error

        # You may add a function to extract scope into a list of scopes
        # with rich information, e.g.
        # scopes = describe_scope(scope)  # returns [{'key': 'email', 'icon': '...'}]
        return render_template(
            'oauth/authorize.html',
            grant=grant,
            user=current_user,
            client=client,
            scopes=scope,
        )
        
    # post로 들어오는 경우 >> 사용자의 동의 여부를 전송
    # form에 동의하시겠습니까? -> 동의했는지의 여부를 서버로 보내줘야하기 때문에
    # client setting >> consent를 받는지 여부에 따라서 달라지는 부분
    # option : skip_content = true 이런 식으로
    confirmed = request.form['confirm']
    if confirmed:
        # granted by resource owner
        # 동의한 경우
        return oauth_server.create_authorization_response(grant_user=current_user)
    # 동의하지 않은 경우
    return oauth_server.create_authorization_response(grant_user=None)


# auth code를 받아 token 발급
@oauth.route('/token', methods=['POST'])
def issue_token():
    return oauth_server.create_token_response()

client는 접근에 대한 인가를 받기 위해 사용자를 /authorize endpoint로 보내 authorization code를 받는다.
받은 authorization code를 이용해 /token endpoint로 token 요청을 보내면 client는 resource에 대한 접근 권한을 인가받게 된다.

profile
무럭무럭 버섯농장

0개의 댓글