之前已经:
现在需要在,微信后台服务器,Flask中,记住微信用户的登录状态
尤其是此处的
对于一个活动页面,地址被分享出去后,如何在别的用户,
在微信环境下,点击对应的页面链接,进入该页面
如何自动判断:
如果之前用户已经登录后,就无需再登录
如果之前没有登录过,就需要授权登录
另外,如果方便的话,也要去:
判断该微信的账号,是否是已经关注了此处对应的微信公众号
对于没有关注的,如何判断出来,如何处理
对于已关注的,则允许继续操作,比如允许点击关注去关注对应的活动
微信 保持登录 如何
flask 微信 保持登录 如何
关于 python-flask 的上下文功能 在微信(浏览器)中保存 用户会话 的问题 – V2EX
->
用openid和oauth
Python的Flask框架中实现简单的登录功能的教程 – 新客网
使用Flask-OAuthlib实现QQ OAuth2登录 – digwtx – SegmentFault
flask 微信 保持登录 session
Flask web开发 处理Session – 互联网 – IT610.com
“上面的代码,与前面相比。在test请求的响应处理中,判断session中是否存在username,不存在就跳转到login页面,并把test地址在session中保存。
然后在login的post请求中,检查newurl是否存在,存在的话表示是由别的页面跳转到登录页面的,这样登录成功后跳转到相应页面,缺省是跳转到home页面。”
你会做Web上的用户登录功能吗? | 酷 壳 – CoolShell.cn
-》好像可以利用:FLask-Login
-》未必需要单独显示帐号和密码的登录框,
而是页面跳转到微信授权页面?
flask 微信 login_required
Flask-Login — Flask-Login 0.3.2 documentation
记录一次使用Flask开发过程中的bug – 寻寻觅觅 – SegmentFault
Flask学习记录之Flask-Login – agmcs – 博客园
View Decorators — Flask Documentation (0.11)
flask 微信 Flask-Login
flask 微信公众号 登录 Flask-Login
python – 新手学习Flask引用flask-login后登入错误原因不清 – SegmentFault
Flask-Login — Flask-Login 0.3.2 documentation
“用户对象助手
class flask.ext.login.UserMixin[source]
This provides default implementations for the methods that Flask-Login expects user objects to have.”
(SIPEvents) ➜ SIPEvents pip install Flask-Login Collecting Flask-Login Downloading Flask-Login-0.3.2.tar.gz Requirement already satisfied (use –upgrade to upgrade): Flask in /root/Envs/SIPEvents/lib/python2.7/site-packages (from Flask-Login) Requirement already satisfied (use –upgrade to upgrade): itsdangerous>=0.21 in /root/Envs/SIPEvents/lib/python2.7/site-packages (from Flask->Flask-Login) Requirement already satisfied (use –upgrade to upgrade): Jinja2>=2.4 in /root/Envs/SIPEvents/lib/python2.7/site-packages (from Flask->Flask-Login) Requirement already satisfied (use –upgrade to upgrade): Werkzeug>=0.7 in /root/Envs/SIPEvents/lib/python2.7/site-packages (from Flask->Flask-Login) Requirement already satisfied (use –upgrade to upgrade): click>=2.0 in /root/Envs/SIPEvents/lib/python2.7/site-packages (from Flask->Flask-Login) Requirement already satisfied (use –upgrade to upgrade): MarkupSafe in /root/Envs/SIPEvents/lib/python2.7/site-packages (from Jinja2>=2.4->Flask->Flask-Login) Building wheels for collected packages: Flask-Login Running setup.py bdist_wheel for Flask-Login … done Stored in directory: /root/.cache/pip/wheels/87/fe/9e/b88482e88c4cc95c91614eb13225740b4d9e21f1fbcd098b58 Successfully built Flask-Login Installing collected packages: Flask-Login Successfully installed Flask-Login-0.3.2 |
然后去参考一堆的帖子,去试试。
[已解决]Flask-Login运行出错:AttributeError _AppCtxGlobals object has no attribute user
至此,终于搞懂了逻辑了。
整理如下:
想要使用Flask-Login的整套步骤和内部所涉及到的逻辑。
1.安装Flask-Login
这个最简单:
(在虚拟环境中,去安装即可)
pip install Flask-Login |
2.按照官网教程和其他人的教程:
Flask-Login — Flask-Login 0.3.2 documentation
(英文:Flask-Login — Flask-Login 0.3.2 documentation)
View Decorators — Flask Documentation (0.11)
Flask学习记录之Flask-Login – agmcs – 博客园
著名的那个教程:
The Flask Mega-Tutorial, Part V: User Logins – miguelgrinberg.com
如果涉及到blueprint则参考:
使用 Flask 框架写用户登录功能的Demo时碰到的各种坑(三)——使用Flask-Login库实现登录功能 – 博客吧
[总结]
使用Flask-Login去实现微信授权登录后才能访问某些接口的整套方法:
(1)初始化LoginManager
在:
/Users/crifan/dev/dev_root/daryun/SIPEvents/sourcecode/flask/sipevents/__init__.py
添加了对应的初始化LoginManager的代码:
from flask_login import LoginManager loginManager = LoginManager() loginManager.init_app(app) #可以设置None,’basic’,’strong’ 以提供不同的安全等级,一般设置strong,如果发现异常会登出用户 loginManager.session_protection = ‘strong’ loginManager.login_view = ‘login’ #登陆界面的路由 loginManager.login_message = u”请进行微信授权登录以访问此页面” # loginManager.login_message_category = “info” gLog.debug(“type(loginManager)=%s, loginManager=%s”, type(loginManager), loginManager) # type(loginManager)=<class ‘flask_login.LoginManager’>, loginManager=<flask_login.LoginManager object at 0x7f5c27476f10> |
(2)给已有的用户User添加对应的属性
/Users/crifan/dev/dev_root/daryun/SIPEvents/sourcecode/flask/sipevents/models.py
class User(db.Model): __tablename__ = ‘wechat_users’ # Columns openid = db.Column(db.String(64), primary_key=True, nullable=False) province = db.Column(db.String(16)) avatar_url = db.Column(db.String(256)) avatar_static_path = db.Column(db.String(256)) language = db.Column(db.String(8)) city = db.Column(db.String(16)) country = db.Column(db.String(32)) sex = db.Column(db.SmallInteger) nickname = db.Column(db.String(128)) events = db.relationship(‘Event’, backref = ‘creator’, lazy = ‘dynamic’) # is_authenticated 允许用户验证,只返回True def is_authenticated(self): return True # is_active 有效账户都返回True,除非禁止账户 def is_active(self): return True # is_anonymous 伪造用户之外都是True def is_anonymous(self): return False # get_id 返回用户唯一标识符,以unicode格式 def get_id(self): try: return unicode(self.openid) # python 2 except NameError: return str(self.openid) # python 3 def __init__(self, openid, province = “”, avatar_url = “”, avatar_static_path = “”, language = “”, city = “”, country = “”, sex = 0, nickname = “”): self.openid = openid self.province = province self.avatar_url = avatar_url self.avatar_static_path = avatar_static_path self.language = language self.city = city self.country = country self.sex = sex self.nickname = nickname def __repr__(self): #return u'<User %r %s>’ % (self.nickname, self.openid) return ‘<User nickname=%r openid=%s avatar_static_path=%s>’ % (self.nickname, self.openid, self.avatar_static_path) |
其中:
- is_authenticated:这个名字起的容易让人误解,实际上的含义是:是否允许认证,is_allow_authenticate,不是:是否已经认证了==是否已经登录了
- is_active
- is_anonymous
- get_id
是新添加的。
注:
如果你的User中的id是Integer这种:
id = db.Column(db.Integer, primary_key=True) |
那么可以直接借用,官网自带的,已经帮你实现了对应的各个额外的上述方法:
from flask.ext.login import UserMixin class User(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) 。。。 |
此处的UserMixin会自动帮你给已有的User添加上:
- is_authenticated
- is_active
- is_anonymous
- get_id
就不用你自己去实现了。
(3)view中的各种路由,各种逻辑
/Users/crifan/dev/dev_root/daryun/SIPEvents/sourcecode/flask/sipevents/views.py
############################################################ # Flask-Login ############################################################ from functools import wraps # from flask_login import login_required from flask_login import login_user, logout_user from flask_login import current_user from . import app, gLog, loginManager #from YOUR_APP_NAME import app, gLog, loginManager import urllib ############################################################ # app related ############################################################ @app.before_request def before_request(): “”” 这里是全局的方法,在请求开始之前调用。 其中 flask 有个全局的变量 g,它是和 session 一样的用途,可以使用它来保存当前用户的数据 Returns: “”” if current_user.is_authenticated: g.user = current_user else: g.user = None gLog.debug(“g=%s, g.user=%s, current_user=%s”, g, g.user, current_user) # g=<flask.g of ‘sipevents’>, g.user=<User nickname=u’crifan.com’ openid=oswjmv-QiQp-_5pFB0thKxfCZ8Tc avatar_static_path=img/avatar/oswjmv-QiQp-_5pFB0thKxfCZ8Tc.png>, current_user=<User nickname=u’crifan.com’ openid=oswjmv-QiQp-_5pFB0thKxfCZ8Tc avatar_static_path=img/avatar/oswjmv-QiQp-_5pFB0thKxfCZ8Tc.png> pass @loginManager.user_loader def load_user(userOpenid): gLog.debug(“type(userOpenid)=%s, userOpenid=%s”, type(userOpenid), userOpenid) # type(userOpenid)=<type ‘unicode’>, userOpenid=oswjmv-QiQp-_5pFB0thKxfCZ8Tc loadedUser = getUserInfo(userOpenid) gLog.debug(“loadedUser=%s”, loadedUser) # loadedUser=<User nickname=u’crifan.com’ openid=oswjmv-QiQp-_5pFB0thKxfCZ8Tc avatar_static_path=img/avatar/oswjmv-QiQp-_5pFB0thKxfCZ8Tc.png> return loadedUser @app.route(“/login”) def login(): requestArgs = request.args gLog.debug(‘requestArgs=%s’, requestArgs) # requestArgs=ImmutableMultiDict([(‘next’, u’http://hd.webonn.com/’)]) nextRedirectUrl = requestArgs.get(“next”, “”) gLog.debug(“nextRedirectUrl=%s”, nextRedirectUrl) return redirect(url_for(“wechat_auth”, nextRedirectUrl=nextRedirectUrl)) #@app.route(“/wechat_auth”, methods=[‘GET’, ‘POST’]) @app.route(“/wechat_auth”) def wechat_auth(): requestArgs = request.args gLog.debug(‘requestArgs=%s’, requestArgs) nextRedirectUrl = request.args.get(“nextRedirectUrl”, “”) gLog.debug(‘nextRedirectUrl=%s’, nextRedirectUrl) needRedirect = False paraCode = request.args.get(‘code’, ”) gLog.debug(‘paraCode=%s’, paraCode) if not paraCode: needRedirect = True paraState = request.args.get(‘state’, ”) gLog.debug(‘paraState=%s’, paraState) # if not paraState : # needRedirect = True gLog.debug(‘needRedirect=%s’, needRedirect) if needRedirect: # 1. redirect url quotedNextRedirectUrl = urllib.quote(nextRedirectUrl) gLog.debug(“quotedNextRedirectUrl=%s”, quotedNextRedirectUrl) redirectUri = “%s/wechat_auth?nextRedirectUrl=%s” % (BASE_URL, quotedNextRedirectUrl) authorizeUrl = wechat.generate_oauth2_authorize_url(redirectUri) gLog.debug(‘redirectUri=%s, authorizeUrl=%s’, redirectUri, authorizeUrl) # redirectUri=http://hd.webonn.com/wechat_auth # authorizeUrl=https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx9xxxxxxxxxxxxxxd&redirect_uri=http%3A//hd.webonn.com/wechat_auth&response_type=code&scope=snsapi_userinfo&state=#wechat_redirect gLog.info(“for wechat auth, redirect to %s”, authorizeUrl) return redirect(authorizeUrl) else: # 2. get access token respAccessToken = wechat.get_oauth2_access_token(paraCode) gLog.debug(‘respAccessToken=%s’, respAccessToken) # respAccessToken={u’access_token’: u’5Lnjm56ox1djVEFEIAdvp4HdcHVO1WsEWCDXVIS0zLZc3veMB4KXLMu843h_MpLJ6xLjlHGeclhhhSsErowWCeFMBMC3CbWX4zDezvu_D7M’, u’openid’: u’oswxxxxxxxxxxxxxxxxxxxxxxVVY’ u’expires_in’: 7200, u’refresh_token’: u’QE-prEWTMHrDBqZGcJLYOg5fNqvpVL6qrp2YekXlyWq2agtwsGhO5IIxujpIBwXzUMy80n7d9xiWFZEwrNt10ilFpRkTbwP4cBzQ2df7eUw’, u’scope’: u’snsapi_userinfo’} respAccessTokenStr = jsonToStr(respAccessToken) gLog.debug(‘respAccessTokenStr=%s’, respAccessTokenStr) # respAccessTokenStr = { # “access_token”: “K8EMRg7DTYnElLOgLix0yVIKeSg85KWmChnGK4PZ8T5N2EiHevQZEm6hyMBPfkOh5Hl3r0H_koLFmHuMFuGOXoyLMn-A7IT0ztqPKvi6TIY”, # “openid”: “oswxxxxxxxxxxxxxxxxxxxxxxVVY”, # “expires_in”: 7200, # “refresh_token”: “wWfqaQyxbAK4WUKwmPeTuSQ7_Nlnxz35xAZ5fu6ZlQseR526zKyemwA7YozbUmbGYSETIpGCi0T-HXMhgKXKVvi5Kon2_4_uWAwhNcxCRBI”, # “scope”: “snsapi_userinfo” # } oauth2Access_token = respAccessToken[‘access_token’] oauth2Openid = respAccessToken[‘openid’] gLog.debug(‘oauth2Access_token=%s, oauth2Openid=%s’, oauth2Access_token, oauth2Openid) respUserInfoDict = wechat.get_oauth2_userinfo(oauth2Access_token, oauth2Openid) gLog.debug(‘type(respUserInfoDict)=%s, respUserInfoDict=%s’, type(respUserInfoDict), respUserInfoDict) # type(respUserInfoDict) = < type ‘dict’ >, respUserInfoDict = {u’province’: u’\xe6\xb1\x9f\xe8\x8b\x8f’, # u’openid’: u’oswxxxxxxxxxxxxxxxxxxxxxxVVY’, # u’headimgurl’: u’http://wx.qlogo.cn/mmopen/ajNVdqHZLLDYtIJicNl7MjwZK5c1lxAJZ253c9v3JzDib7GeE5OFrWiaRqsK1ruW1HmGaziaYETV5vQhIIbic6wHKFQ/0′, # u’language’: u’zh_CN’, u’city’: u’\xe8\x8b\x8f\xe5\xb7\x9e’, # u’country’: u’\xe4\xb8\xad\xe5\x9b\xbd’, u’sex’: 1, u’privilege’: [], # u’nickname’: u’\xe7\xa4\xbc\xe8\xb2\x8c’} curUser = saveUserInfoToDb(respUserInfoDict) gLog.debug(“curUser=%s”, curUser) gLog.info(“record logged in for user=%s”, curUser) login_user(curUser, remember = True) # 第一个参数传入用户对象,第二个参数 传入 以后是否自动登陆 # flash(u’用户 %s 登录成功’, curUser.nickname) gLog.info(“%s logged in succssfully”, curUser) #TODO: add redirect url validation # next = request.args.get(‘next’) # gLog.debug(“next=%s”, next) # # next_is_valid should check if the user has valid # # permission to access the `next` url # if not next_is_valid(next): # gLog.error(“next is invalid, go abort 400”) # return abort(400) #return redirect(next or url_for(‘index’)) if nextRedirectUrl: gLog.debug(“after wechat auth, redirect to nextRedirectUrl=%s”, nextRedirectUrl) # after wechat auth, redirect to nextRedirectUrl=http://hd.webonn.com/ return redirect(nextRedirectUrl) else: gLog.debug(“after wechat auth, redirect to default router: index”) return redirect(url_for(“index”)) def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): if g.user is None: return redirect(url_for(‘login’, next=request.url)) return f(*args, **kwargs) return decorated_function @app.route(“/index”) @app.route(“/”) @login_required def index(): requestArgs = request.args gLog.debug(‘requestArgs=%s’, requestArgs) curUser = g.user gLog.debug(“g=%s, curUser=%s”, g, curUser) # g=<flask.g of ‘sipevents’>, curUser=<flask_login.AnonymousUserMixin object at 0x7f93ab317390> # g=<flask.g of ‘sipevents’>, curUser=<User nickname=u’crifan.com’ openid=oswjmv-QiQp-_5pFB0thKxfCZ8Tc avatar_static_path=img/avatar/oswjmv-QiQp-_5pFB0thKxfCZ8Tc.png> gLog.debug(“curUser=%s”, curUser) # curUser=<User nickname=u’\u793c\u8c8c’ openid=oswxxxxxxxxxxxxxxxxxxxxxxVVY avatar_static_path=img/avatar/oswxxxxxxxxxxxxxxxxxxxxxxVVY.png> # curUser=<flask_login.AnonymousUserMixin object at 0x7f93ab317390> gLog.debug(“type(curUser)=%s”, type(curUser)) # type(curUser)=<class ‘sipevents.models.User’> # type(curUser)=<class ‘werkzeug.local.LocalProxy’> # type(curUser)=<class ‘werkzeug.local.LocalProxy’> ………….. return render_template(‘index.html’, curUser = curUser, expiredEventList = expiredEventList, todayEventList = todayEventList, tomorrowEventList = tomorrowEventList, futureEventList = futureEventList) |
然后对应的逻辑是:
1./ 之前的 before_request
@app.before_request def before_request(): |
用户访问 / 或 /index 之前,会去访问:
before_request
其中会发现,
此时current_user是没有登录的用户,是AnonymousUserMixin
g=<flask.g of ‘sipevents’>, g.user=None, current_user=<flask_login.AnonymousUserMixin object at 0x7f03cd2e9390> |
所以此时current_user.is_authenticated为False
所以:
g.user设置为None
2./ 的login_required限定
@app.route(“/index”) @app.route(“/”) @login_required def index(): |
用户访问首页 / 或 /index 时,
发现其有对应的限定:
@login_required
所以去访问login_required
中的:decorated_function
if g.user is None: return redirect(url_for(‘login’, next=request.url)) |
里面去判断,发现:
g.user为None
即,用户还没有登录
所以跳转到:
login
且传入了next参数,值为此处要访问的url
requestArgs=ImmutableMultiDict([(‘next’, u’http://hd.webonn.com/’)]) |
3.login的
@app.route(“/login”) def login(): |
login中,获得next参数给nextRedirectUrl
nextRedirectUrl=http://hd.webonn.com/ |
然后去,跳转到,负责处理微信授权的wechat_auth
return redirect(url_for(“wechat_auth”, nextRedirectUrl=nextRedirectUrl)) |
4.进入wechat_auth,此时code为空
@app.route(“/wechat_auth”) def wechat_auth(): |
得到了nextRedirectUrl参数:
requestArgs=ImmutableMultiDict([(‘nextRedirectUrl’, u’http://hd.webonn.com/’)]) |
此时,不是微信回调后,所以code和state都是空的:
paraCode= paraState= |
所以此处需要跳转needRedirect到微信授权
注:
此处,其实有个小难点:
则回调之后,获取用户信息之后,想要跳转回之前的url,就找不到之前的回调地址是多少了
所以此处,把之前的登录成功后回调的地址,传入了,微信授权回调地址中,当作参数:
redirectUri=http://hd.webonn.com/wechat_auth?nextRedirectUrl=http%3A//hd.webonn.com/ |
而对应的微信授权地址是:
authorizeUrl=https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx9xxxxxxxxxxxxxxd&redirect_uri=http%3A//hd.webonn.com/wechat_auth%3FnextRedirectUrl%3Dhttp%253A//hd.webonn.com/&response_type=code&scope=snsapi_userinfo&state=#wechat_redirect |
然后就是:
5.进入微信授权的逻辑:
如果用户已经授权了,则自动就回调(不会出现授权页面)
如果用户还没有授权,则跳转到授权页面,用户点击授权,则跳转到回调地址
6.再次进入wechat_auth,此时code不为空
即返回:
http://hd.webonn.com/wechat_auth?nextRedirectUrl=http%3A//hd.webonn.com/
即:
返回:
http://hd.webonn.com/wechat_auth
参数是:
requestArgs=ImmutableMultiDict([(‘state’, u”), (‘code’, u’011uPpnB13QCl00a5poB1FTunB1uPpn9′), (‘nextRedirectUrl’, u’http://hd.webonn.com/’)]) |
然后此时,发现code不为空,则就不跳转,则进入:
获取到对应的用户信息
解析后,存入数据库
respUserInfoDict = wechat.get_oauth2_userinfo(oauth2Access_token, oauth2Openid) curUser = saveUserInfoToDb(respUserInfoDict) |
输出信息是:
type(respUserInfoDict)=<type ‘dict’>, respUserInfoDict={u’province’: u”, u’openid’: u’oswjmv-QiQp-_5pFB0thKxfCZ8Tc’, u’headimgurl’: u’http://wx.qlogo.cn/mmopen/pER61XSS42q63Aq66EQF8u6ur93JJjShFMHkHWticgdPEOcA9k5nqCQGnXh2WTs4YyRIVc9vbTUHDSQJashJEbGuOI2WgiaaOx/0′, u’language’: u’zh_CN’, u’city’: u”, u’country’: u’\u4e2d\u56fd’, u’sex’: 0, u’privilege’: [], u’nickname’: u’crifan.com’} |
然后,去调用,关键的Flask-Login的login_user:
login_user(curUser, remember = True) # 第一个参数传入用户对象,第二个参数 传入 以后是否自动登陆 |
去记录下来,你已经登录的用户
-》此时,全局的变量current_user,此时就是对应的已登录的用户User类型了
-》就可以去获取你自己的相关的属性了,比如我此处,User有openid,nickname等属性。
curUser = g.user curUser.openid |
-》
执行完login_user,就接着,跳转回到:
需要登录验证的的之前next参数的url中
after wechat auth, redirect to nextRedirectUrl=http://hd.webonn.com/ |
此处即:
nextRedirectUrl=http://hd.webonn.com/ |
7.继续访问 / 之前的before_request
此时,User就是我们所希望的User类型了:
g=<flask.g of ‘sipevents’>, g.user=<User nickname=u’crifan.com’ openid=oswjmv-QiQp-_5pFB0thKxfCZ8Tc avatar_static_path=img/avatar/oswjmv-QiQp-_5pFB0thKxfCZ8Tc.png>, current_user=<User nickname=u’crifan.com’ openid=oswjmv-QiQp-_5pFB0thKxfCZ8Tc avatar_static_path=img/avatar/oswjmv-QiQp-_5pFB0thKxfCZ8Tc.png> |
8.然后真正的,进入到 /
即访问了:
进入到路由 /
此时,把之前在before_request保存到的
g.user拿过来使用
curUser = g.user gLog.debug(“g=%s, curUser=%s”, g, curUser) # g=<flask.g of ‘sipevents’>, curUser=<flask_login.AnonymousUserMixin object at 0x7f93ab317390> # g=<flask.g of ‘sipevents’>, curUser=<User nickname=u’crifan.com’ openid=oswjmv-QiQp-_5pFB0thKxfCZ8Tc avatar_static_path=img/avatar/oswjmv-QiQp-_5pFB0thKxfCZ8Tc.png> gLog.debug(“curUser=%s”, curUser) # curUser=<User nickname=u’\u793c\u8c8c’ openid=oswxxxxxxxxxxxxxxxxxxxxxxVVY avatar_static_path=img/avatar/oswxxxxxxxxxxxxxxxxxxxxxxVVY.png> # curUser=<flask_login.AnonymousUserMixin object at 0x7f93ab317390> gLog.debug(“type(curUser)=%s”, type(curUser)) # type(curUser)=<class ‘sipevents.models.User’> # type(curUser)=<class ‘werkzeug.local.LocalProxy’> # type(curUser)=<class ‘werkzeug.local.LocalProxy’> |
注意到:
之前没有登录,g.user的值是:flask_login.AnonymousUserMixin的类型
登录后,g.user的值是:我们自己的User类型
不论是否登录,g.user的type都属于:
<class ‘werkzeug.local.LocalProxy’>
类型。
至此,才算完整了实现:
Flask-Login,搭配before_request和login_required,去实现:
访问某些路径/接口/路由 之前,需要登录才可以访问。
且此处,通过跳转到微信授权登录页面,实现微信授权,拿到用户信息,实现登录到过程。
转载请注明:在路上 » [记录]如何在Flask中实现记住微信公众号的用户帐户的登录状态