QQ、Sina等OAuth2.0接入

来源:互联网 发布:濒死体验知乎 编辑:程序博客网 时间:2024/06/07 01:26

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

0 0