QQ、Sina等OAuth 2.0アクセス

14110 ワード

OAuth2.0技术は比较的に実用的で、私のような登录に反感を持っているユーザーは一般的に1つの役に立たないQQを申请して、それからこのQQで登录してあれらの登录したくないで、少し小さいシステムです.技術は多く言わないで、QQを持って言って、簡単にいくつかの点を列挙します:
1.QQはサードパーティプラットフォームにappkeyとappsecretを割り当てる
2.ユーザーはQQアカウントを使用して第三者プラットフォームにログインすることを選択し、この時、第三者プラットフォームは自分のappkeyを使用してcodeを取得し、codeはユーザーがQQアカウントのパスワードを入力した後に生成したもので、QQ側とインタラクティブで、しかもライフサイクルが短く、安全である
3.サードパーティプラットフォームがcodeを取得した後、code+appkey+appsecretを使用してaccess_を取得するtoken
4.サードパーティプラットフォームからアクセスを取得token後、このaccess_を使用できます.tokenはopenidを取得し、openid+access_を使用します.token+appkeyユーザー情報の取得
5.ユーザーが認証に成功した場合、第三者プラットフォームはそのユーザーの唯一の表示openidを使用してアカウントを作成することができる
tornadoフレームパッケージのOAuth 2 mixin:
class OAuth2Mixin(object):
    """Abstract implementation of OAuth 2.0.

    See `FacebookGraphMixin` below for an example implementation.

    Class attributes:

    * ``_OAUTH_AUTHORIZE_URL``: The service's authorization url.
    * ``_OAUTH_ACCESS_TOKEN_URL``:  The service's access token url.
    """
    @return_future
    def authorize_redirect(self, redirect_uri=None, client_id=None,
                           client_secret=None, extra_params=None,
                           callback=None):
        """Redirects the user to obtain OAuth authorization for this service.

        Some providers require that you register a redirect URL with
        your application instead of passing one via this method. You
        should call this method to log the user in, and then call
        ``get_authenticated_user`` in the handler for your
        redirect URL to complete the authorization process.

        .. versionchanged:: 3.1
           Returns a `.Future` and takes an optional callback.  These are
           not strictly necessary as this method is synchronous,
           but they are supplied for consistency with
           `OAuthMixin.authorize_redirect`.
        """
        args = {
            "redirect_uri": redirect_uri,
            "client_id": client_id
        }
        if extra_params:
            args.update(extra_params)
        self.redirect(
            url_concat(self._OAUTH_AUTHORIZE_URL, args))
        callback()

    def _oauth_request_token_url(self, redirect_uri=None, client_id=None,
                                 client_secret=None, code=None,
                                 extra_params=None):
        url = self._OAUTH_ACCESS_TOKEN_URL
        args = dict(
            redirect_uri=redirect_uri,
            code=code,
            client_id=client_id,
            client_secret=client_secret,
        )
        if extra_params:
            args.update(extra_params)
        return url_concat(url, args)

QQ Oauth2.0パッケージ:
class QQGraphOAuth2Mixin(OAuth2Mixin):

    _OAUTH_AUTHORIZE_URL = "https://graph.qq.com/oauth2.0/authorize?"
    _OAUTH_ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token?"
    _OAUTH_OPENID_URL = "https://graph.qq.com/oauth2.0/me?"
    _OAUTH_NO_CALLBACKS = False
    _QQ_BASE_URL = "https://graph.qq.com"

    @_auth_return_future
    def get_authenticated_user(self, redirect_uri, client_id, client_secret,
                               code, callback, extra_fields=None, extra_params=None):
        http = self.get_auth_http_client()
        args = {
            "redirect_uri": redirect_uri,
            "code": code,
            "client_id": client_id,
            "client_secret": client_secret,
            "extra_params": extra_params,
        }

        http.fetch(self._oauth_request_token_url(**args),
                   self.async_callback(self._on_access_token, redirect_uri, client_id,
                                       client_secret, callback, extra_fields))

    def _on_access_token(self, redirect_uri, client_id, client_secret,
                         future, extra_fields, response):
        if response.error:
            future.set_exception(AuthError('QQ auth error: %s' % str(response)))
            return

        args = escape.parse_qs_bytes(escape.native_str(response.body))
        session = {
            "access_token": args["access_token"][-1],
            "expires_in": args["expires_in"][-1],
            "refresh_token": args["refresh_token"][-1],
            "client_id": client_id,
        }
        http = self.get_auth_http_client()
        http.fetch(self._oauth_request_openid(session["access_token"]),
                   self.async_callback(self._on_open_id, future, session, extra_fields))

    def _on_open_id(self, future, session, extra_fields, response):
        
        if response.error:
            future.set_exception(AuthError('QQ auth error: %s' % str(response)))
            return
        response = response.body.replace("callback( ", "").replace(" );", "")
        args = escape.json_decode(response)
        session["openid"] = str(args["openid"])
        fields = set(['ret', 'msg', 'nickname'])
        if extra_fields:
            fields.update(extra_fields)
            
        self.qq_request(
            path="/user/get_user_info",
            callback=self.async_callback(
                self._on_get_user_info, future, session, fields),
            access_token=session["access_token"],
            openid=session["openid"],
            oauth_consumer_key=session["client_id"],
            fields=",".join(fields)
        )
    
    def _on_get_user_info(self, future, session, fields, user):
        if user is None:
            future.set_result(None)
            return

        fieldmap = {}
        for field in fields:
            fieldmap[field] = user.get(field)

        fieldmap.update(session)
        future.set_result(fieldmap)

    @_auth_return_future
    def qq_request(self, path, callback, access_token=None,
                         post_args=None, **args):
        url = self._QQ_BASE_URL + path
        all_args = {}
        if access_token:
            all_args["access_token"] = access_token
            all_args.update(args)

        if all_args:
            url += "?" + urllib_parse.urlencode(all_args)
        callback = self.async_callback(self._on_qq_request, callback)
        http = self.get_auth_http_client()
        if post_args is not None:
            http.fetch(url, method="POST", body=urllib_parse.urlencode(post_args),
                       callback=callback)
        else:
            http.fetch(url, callback=callback)

    def _on_qq_request(self, future, response):
        if response.error:
            future.set_exception(AuthError("Error response %s fetching %s" %
                                           (response.error, response.request.url)))
            return
        future.set_result(escape.json_decode(response.body))

    def get_auth_http_client(self):
        return httpclient.AsyncHTTPClient()
    
    def _oauth_request_openid(self, access_token):
        return self._OAUTH_OPENID_URL + "access_token=" + access_token

Sina OAuth 2パッケージ(QQとほぼ一致):
class SinaGraphOAuth2Mixin(OAuth2Mixin):

    _OAUTH_AUTHORIZE_URL = "https://api.weibo.com/oauth2/authorize?"
    _OAUTH_ACCESS_TOKEN_URL = "https://api.weibo.com/oauth2/access_token?"
    _OAUTH_NO_CALLBACKS = False
    _OAUTH_SINA_BASE_URL = "https://api.weibo.com/2"

    @_auth_return_future
    def get_authenticated_user(self, redirect_uri, client_id, client_secret,
                               code, callback, extra_fields=None, extra_params=None):
        
        post_args = {
            "client_id" : client_id,
            "client_secret" : client_secret,
            "grant_type" : "authorization_code",
            "redirect_uri" : redirect_uri,
            "code" : code
        }
        self.sina_request(
            path="https://api.weibo.com/oauth2/access_token",
            callback=self.async_callback(self._on_access_token, callback, extra_fields),
            post_args = post_args,
        )

    def _on_access_token(self, future, extra_fields, response):
        if response is None:
            future.set_result(None)
            return
        
        fields = set(['error_code', 'error', 'id', 'screen_name'])
        if extra_fields:
            fields.update(extra_fields)

        self.sina_request(
            path=self._OAUTH_SINA_BASE_URL+"/users/show.json",
            callback=self.async_callback(self._on_get_user_info, future, fields),
            access_token=response["access_token"],
            uid=response["uid"]
        )

    def _on_get_user_info(self, future, fields, user):
        if user is None:
            future.set_result(None)
            return

        fieldmap = {}
        for field in fields:
            fieldmap[field] = user.get(field, "")

        future.set_result(fieldmap)

    @_auth_return_future
    def sina_request(self, path, callback, access_token=None,
                         post_args=None, **args):
        url = path
        all_args = {}
        if access_token:
            all_args["access_token"] = access_token
            all_args.update(args)

        if all_args:
            url += "?" + urllib_parse.urlencode(all_args)
        callback = self.async_callback(self._on_sina_request, callback)
        http = self.get_auth_http_client()
        if post_args is not None:
            http.fetch(url, method="POST", body=urllib_parse.urlencode(post_args),
                       callback=callback)
        else:
            http.fetch(url, callback=callback)

    def _on_sina_request(self, future, response):
        if response.error:
            future.set_exception(AuthError("Error response %s fetching %s" %
                                           (response.error, response.request.url)))
            return
        future.set_result(escape.json_decode(response.body))

    def get_auth_http_client(self):
        return httpclient.AsyncHTTPClient()

使用(ユーザー情報を取得して直接ログイン):
class QQGraphLoginHandler(BaseHandler, QQGraphOAuth2Mixin):
    @tornado.web.asynchronous
    def get(self):
        my_url = (self.request.host.replace("localhost", "127.0.0.1") +
                  "/qqlogin?next=" +
                  tornado.escape.url_escape(self.get_argument("next", "/")))
        if self.get_argument("code", False):
            self.get_authenticated_user(
                redirect_uri=my_url,
                client_id=self.settings["qq_api_key"],
                client_secret=self.settings["qq_api_secret"],
                code=self.get_argument("code"),
                extra_params={"grant_type": "authorization_code"},
                callback=self._on_auth)
            return
        self.authorize_redirect(redirect_uri=my_url,
                                client_id=self.settings["qq_api_key"],
                                extra_params={"response_type": "code"})

    def _on_auth(self, user):
        if not user:
            raise tornado.web.HTTPError(500, "qq auth failed")
        #user: openid, nickname
        uid = user.get("openid", 0)
        nick = user.get("nickname", uid)#default uid
        if user.get("ret", 0) or not uid:
            self.render('error.html', msg = user.get('msg', 'error'))
        else:
            ZQ_Account().login(uid, nick, "QQ", self._on_login)

    def _on_login(self, result, ex):
        if not ex:
            self.set_secure_cookie(settings.MGR_USER_COOKIE_KEY, json_encode(result) , expires_days = 1)
            self.redirect(self.get_argument("next", "/"))
        else:
            self.writeError(result)


class SinaGraphLoginHandler(BaseHandler, SinaGraphOAuth2Mixin):
    @tornado.web.asynchronous
    def get(self):
        my_url = ("http://" + self.request.host.replace("localhost", "127.0.0.1") +#   http://
                  "/sinalogin")
        if self.get_argument("code", False):
            self.get_authenticated_user(
                redirect_uri=my_url,
                client_id=self.settings["sina_api_key"],
                client_secret=self.settings["sina_api_secret"],
                code=self.get_argument("code"),
                extra_params={"grant_type": "authorization_code"},
                callback=self._on_auth)
            return
        self.authorize_redirect(redirect_uri=my_url,
                                client_id=self.settings["sina_api_key"],
                                extra_params={"response_type": "code"})

    def _on_auth(self, user):
        if not user:
            raise tornado.web.HTTPError(500, "sina auth failed")
        #user: id, screen_name
        uid = user.get("id", 0)
        nick = user.get("screen_name", uid)#default uid
        if user.get("error_code", 0):
            self.render('error.html', msg = user.get('error'))
        else:
            ZQ_Account().login(uid, nick, "SINA", self._on_login)
    
    def _on_login(self, result, ex):
        if not ex:
            self.set_secure_cookie(settings.MGR_USER_COOKIE_KEY, json_encode(result) , expires_days = 1)
            self.redirect(self.get_argument("next", "/"))
        else:
            self.writeError(result)

QQ OAuthは公式ドキュメントを直接参照すればいいので、詳しくは:http://wiki.connect.qq.com/
Sina OAuth公式ドキュメントは少し乱れています.参考にしてください.http://jingyan.baidu.com/article/455a99508c91c8a166277893.htmlhttp://rsj217.diandian.com/post/2013-04-17/40050093587