QQ、Sina等OAuth2.0接入

OAuth2.0技术比较实用,像我这种反感注册的用户一般都是申请一个没用的QQ,然后用这个QQ登录那些不想注册,有点小用的系统。技术不多说,拿QQ来说,简单列几点:

1.QQ给第三方平台分配一个appkey和appsecret

2.用户选择使用QQ账号登录第三方平台,此时,第三方平台使用自己的appkey获取code,code是在用户输入QQ账号密码后生成的,是与QQ那边交互的,并且生命周期很短,是安全的

3.第三方平台获取到code后,再使用code+appkey+appsecret获取access_token

4.第三方平台获取到access_token后,便可使用该access_token获取openid,进而使用openid+access_token+appkey获取用户信息

5.此时用户已认证成功,第三方平台可使用该用户的唯一标示openid来创建账号


tornado框架封装的OAuth2Mixin:

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 OAuth2封装(与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.html
http://rsj217.diandian.com/post/2013-04-17/40050093587

你可能感兴趣的:(氓之蚩蚩OAuth2)