简体   繁体   English

如何在Tornado中使用OpenID进行身份验证?

[英]How to authenticate using OpenID in Tornado?

I'm using Tornado web server for a simple web application, and want to authenticate the user using OpenID. 我将Tornado Web服务器用于简单的Web应用程序,并希望使用OpenID对用户进行身份验证。 I'm new to Tornado, and I managed to work it using Node.js' Passport package (was testing on Node.js first), which I was able to get the id_token in the callback. 我是Tornado的新手,我设法使用Node.js的Passport包(首先在Node.js上进行了测试)来使用它,我能够在回调id_token中获取id_token

I'm using OAuth2Mixin from tornado.auth to authorize the access using user credential grant, then on the redirect, I'm getting the from code get param. 我使用OAuth2Mixintornado.auth授权使用用户证书授予访问,然后重定向,我得到了从code获取PARAM。 I don't know how to continue from there :D 我不知道如何从那里继续:D

from tornado.auth import OpenIdMixin, OAuth2Mixin
from .base import BaseHandler

class LoginHandler(BaseHandler, OAuth2Mixin, OpenIdMixin):
  def get(self):
    self._OAUTH_AUTHORIZE_URL = 'https://authserver.io/uas/oauth2/authorization'
    self._OAUTH_ACCESS_TOKEN_URL = 'https://authserver.io/uas/oauth2/token'
    self.authorize_redirect(
      redirect_uri='http://localhost:3001/success-login',
      client_id='abcd',
      client_secret='1234',
    )

Then on the other handler. 然后在另一个处理程序上。

from tornado.auth import OpenIdMixin, OAuth2Mixin
import tornado.httpclient
from .base import BaseHandler

class SuccessLoginHandler(BaseHandler, OpenIdMixin, OAuth2Mixin):
  async def get(self):
    code = self.get_argument('code', None)
    if code is not None:
      return self.write(code)

    self.write('no code')

I would expect the id_token back; 我希望id_token回来; which is a JWT. 这是JWT。 I can decode it and get the data needed. 我可以解码并获取所需的数据。

UPDATE: In case the configurations are needed. 更新:如果需要配置。

{"issuer":"https://authserver.io/uas","authorization_endpoint":"https://authserver.io/uas/oauth2/authorization","token_endpoint":"https://authserver.io/uas/oauth2/token","userinfo_endpoint":"https://authserver.io/uas/oauth2/userinfo","jwks_uri":"https://authserver.io/uas/oauth2/metadata.jwks","tokeninfo_endpoint":"https://authserver.io/uas/oauth2/introspection","introspection_endpoint":"https://authserver.io/uas/oauth2/introspection","revocation_endpoint":"https://authserver.io/uas/oauth2/revocation","response_types_supported":["code"],"grant_types_supported":["authorization_code","password","refresh_token","urn:ietf:params:oauth:grant-type:saml2-bearer","http://globalsign.com/iam/sso/oauth2/grant-type/sms-mt-otp","http://globalsign.com/iam/sso/oauth2/grant-type/smtp-otp"],"subject_types_supported":["public"],"request_object_signing_alg_values_supported":["RS256","HS256"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA1_5","A128KW"],"request_object_encryption_enc_values_supported":["A128GCM","A128CBC-HS256"],"id_token_signing_alg_values_supported":["RS256","HS256"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA1_5","A128KW"],"id_token_encryption_enc_values_supported":["A128GCM","A128CBC-HS256"],"userinfo_signing_alg_values_supported":["RS256","HS256"],"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA1_5","A128KW"],"userinfo_encryption_enc_values_supported":["A128GCM","A128CBC-HS256"],"token_endpoint_auth_methods_supported":["client_secret_post","client_secret_basic","client_secret_jwt","private_key_jwt"],"token_endpoint_auth_signing_alg_values_supported":["RS256","HS256"],"introspection_endpoint_auth_methods_supported":["client_secret_post","client_secret_basic","client_secret_jwt","private_key_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["RS256","HS256"],"revocation_endpoint_auth_methods_supported":["client_secret_post","client_secret_basic","client_secret_jwt","private_key_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["RS256","HS256"],"scopes_supported":["openid","userinfo"]}

You'll need to call get_authenticated_user from SuccessLoginHandler to get the access token. 您需要从SuccessLoginHandler调用get_authenticated_user来获取访问令牌。

But, I'd rather write everything in a single handler to keep code shorter and non repetitive. 但是,我宁愿将所有内容都写在一个处理程序中,以使代码更短且不重复。 You can rewrite LoginHandler like this: 您可以这样重写LoginHandler

class LoginHandler(BaseHandler, OAuth2Mixin, OpenIdMixin):

    _OAUTH_AUTHORIZE_URL = 'https://authserver.io/uas/oauth2/authorization'
    _OAUTH_ACCESS_TOKEN_URL = 'https://authserver.io/uas/oauth2/token'

    async def get(self):
        redirect_uri = 'http://localhost:3001/login'

        code = self.get_argument('code', None)

        if code:
            # if there's `code`, get access token
            user = await self.get_authenticated_user()

            # the `user` variable now contains the returned data
            # from the oauth server.
            # you'll probably want to `set_secure_cookie`
            # or do something else to save the user

            # then redirect the user to some page
            self.redirect("/") # redirects to home page
            return 

        else:
            # otherwise get authorization `code`       
            self.authorize_redirect(
                redirect_uri=redirec_uri,
                client_id='abcd',
                client_secret='1234',
            )

I ended up using Tornado's httpclient to send the requests to the OpenID server. 我最终使用了Tornado的httpclient将请求发送到OpenID服务器。

import base64
import urllib.parse
import json
import tornado.httpclient

from .base import BaseHandler
from settings import OID_AUTH_API, OID_REDIRECT_URI, OID_CLIENT_ID, OID_CLIENT_PASSWORD
from lib import logger

class LoginHandler(BaseHandler):
    _redirect_uri = urllib.parse.quote(OID_REDIRECT_URI, safe='')
    _scope = 'openid+profile+email'
    _response_type = 'code'
    _http_client = tornado.httpclient.AsyncHTTPClient()

    async def get(self):
        try:
            code = self.get_argument('code', None)

            if (code is None):
                self.redirect('%s/authorization?client_id=%s&scope=%s&response_type=%s&redirect_uri=%s' % (
                    OID_AUTH_API, OID_CLIENT_ID, self._scope, self._response_type, self._redirect_uri), self.request.uri)
                return

            # exchange the authorization code with the access token
            grant_type = 'authorization_code'
            redirect_uri = self._redirect_uri
            authorization_header = '%s:%s' % (
                OID_CLIENT_ID, OID_CLIENT_PASSWORD)
            authorization_header_encoded = base64.b64encode(
                authorization_header.encode('UTF-8')).decode('UTF-8')
            url = '%s/token?grant_type=%s&code=%s&redirect_uri=%s' % (
                OID_AUTH_API, grant_type, code, redirect_uri)
            token_exchange_response = await self._http_client.fetch(
                url,
                method='POST',
                headers={
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Authorization': 'Basic %s' % authorization_header_encoded,
                    'Accept': 'application/json'
                },
                body='')

            token_exchange_response_body_dict = json.loads(
                token_exchange_response.body)

            access_token = token_exchange_response_body_dict.get(
                'access_token')

            self.send_response({
                'access_token': access_token
            })
        except tornado.httpclient.HTTPClientError as error:
            logger.log_error(error.response.body.decode('UTF-8'))

            self.send_response({
                'success': False,
                'message': 'Error occurred while trying to obtain the access token'
            }, 500)
        except Exception as error:
            logger.log_error_with_traceback(error)
            self.send_response({
                'success': False,
                'message': 'Internal server error. Please try again later.'
            }, 500)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM