精华内容
下载资源
问答
  • Token 进行刷新续期,我们要解决并发请求导致重复刷新 Token问题,这也是设计刷新 Token 的难点。这里我会分别介绍前端和后端各自的处理方案。后端方案:利用 Redis 缓存当同时发起多个请求时,第一个接口刷新...

    对 Token 进行刷新续期,我们要解决并发请求导致重复刷新 Token 的问题,这也是设计刷新 Token 的难点。这里我会分别介绍前端和后端各自的处理方案。

    后端方案:利用 Redis 缓存

    当同时发起多个请求时,第一个接口刷新了 Token,后面的请求仍然能通过请求,且不造成 Token 重复刷新。那么,后端在用户第一次登录时,需要将生成的 Token 数据(token 和 createTime)缓存一份到 Redis 中。

    当 Token 过期时,重新生成新的 Token 数据并更新 Redis 缓存,同时在 Redis 中设置一条 Token 过渡数据并设置一个很短的过期时间(比如 30s)。如果后面的请求发现 Token 已经被刷新了,就判断 Redis 中是否存在 Token 过渡数据,存在就放行,这样同一时间的请求都可以通过。

    源码地址:https://github.com/yifanzheng/spring-security-jwt/tree/refresh-token-redis

    Token 刷新流程图

    45d9b73f661920f46f746493ace774c5.png

    前端方案:请求拦截

    由于前端请求都是异步的,只有一个请求的时候,刷新 Token 是比较好处理的,但并发请求下刷新 Token 处理起来有点麻烦。我们需要考虑在多个请求几乎同时发起并且 Token 都失效的情况,当第一个请求进入 Token 刷新流程时,其他请求必须等待第一个请求完成 Token 刷新后再使用新 Token 进行重试。
    简单地讲,就是同一时间有多个请求且 Token 都失效,在第一个请求进行 Token 刷新时,其他请求必须处于等待状态,直到 Token 刷新完成,才能携带新 Token 进行重试。

    下面,我使用了 Angular 的请求拦截器,利用 BehaviorSubject 进行 Token 刷新状态的监听,当 Token 刷新成功,放行后面的请求进行重试。

    除此之外,前端还可以利用 Promise,将请求存进队列中后,同时返回一个 Promise,让这个 Promise 一直处于 Pending 状态(即不调用 resolve),此时这个请求就会一直等待,只要我们不执行 resolve,这个请求就会一直在等待。当刷新 Token 的请求完成后 ,我们再调用 resolve,逐个重试。

    Github 地址:https://github.com/yifanzheng/spring-security-jwt/tree/refresh-token-frontend

    Angular 代码示列

    import { Injectable } from "@angular/core";
    import {
      HttpEvent,
      HttpInterceptor,
      HttpHandler,
      HttpRequest,
      HttpErrorResponse
    } from "@angular/common/http";
    import { throwError, Observable, BehaviorSubject, of } from "rxjs";
    import { catchError, filter, finalize, take, switchMap, mergeMap } from "rxjs/operators";
    
    @Injectable()
    export class AuthInterceptor implements HttpInterceptor {
    
      private refreshTokenInProgress = false;
      private refreshTokenSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
    
      intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (!req.headers.has("Content-Type")) {
          req = req.clone({
            headers: req.headers.set("Content-Type", "application/json")
          });
        }
        // 统一加上服务端前缀
        let url = req.url;
        if (!url.startsWith('https://') && !url.startsWith('http://')) {
          url = "./" + url;
        }
        req = req.clone({ url });
        req = this.setAuthenticationToken(req);
    
        return next.handle(req).pipe(
          mergeMap((event: any) => {
            // 若一切都正常,则后续操作
            return of(event);
          }),
          catchError((error: HttpErrorResponse) => {
            // 当是 401 错误时,表示 Token 已经过期,需要进行 Token 刷新
            if (error && error.status === 401) {
              if (this.refreshTokenInProgress) {
                // 如果 refreshTokenInProgress 为 true,我们将等到 refreshTokenSubject 是 true 时,才可以再次重试该请求
                // 这表示刷新 Token 动作已完成,新 Token 已准备就绪
                return this.refreshTokenSubject.pipe(
                  filter(result => result),
                  take(1),
                  switchMap(() => next.handle(this.setAuthenticationToken(req)))
                );
              } else {
                this.refreshTokenInProgress = true;
                // 将 refreshTokenSubject 设置为 false,以便后面的请求调用时将处于等待状态,直到检索到新 Token 为止
                this.refreshTokenSubject.next(false);
                return this.refreshToken().pipe(
                  switchMap((newToken: string) => {
                    this.refreshTokenSubject.next(true);
                    // 重新设置新的 Token
                    localStorage.setItem("token", newToken);
                    return next.handle(this.setAuthenticationToken(req));
                  }),
                  // 当刷新 Token 请求完成后,需要将 refreshTokenInProgress 设置为 false,用于下次刷新 Token
                  finalize(() => (this.refreshTokenInProgress = false))
                );
              }
            } else {
              return throwError(error);
            }
          })
        );
      }
    
      private refreshToken(): Observable<any> {
        // 这里需要换成实际的 Token 刷新接口
        return of("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdGFyIiwicm9sZSI6WyJST0xFX1VTRVIiXSwiaXNzIjoic2VjdXJpdHkiLCJpYXQiOjE2MDY4MjczMDAsImF1ZCI6InNlY3VyaXR5LWFsbCIsImV4cCI6MTYwNjgzNDUwMH0.Hiq2DsH6j4XFd_v87lDWGlYembTLck7DjMLRLWdyvOo");
      }
    
      private setAuthenticationToken(request: HttpRequest<any>): HttpRequest<any> {
        return request.clone({
          headers: request.headers.set("Authorization", "Bearer " + localStorage.getItem("token"))
        });
      }
    }
    
    展开全文
  • Token 进行刷新续期,我们要解决并发请求导致重复刷新 Token问题,这也是设计刷新 Token 的难点。这里我会分别介绍前端和后端各自的处理方案。 后端方案:利用 Redis 缓存 当同时发起多个请求时,第一个接口...

    对 Token 进行刷新续期,我们要解决并发请求导致重复刷新 Token 的问题,这也是设计刷新 Token 的难点。这里我会分别介绍前端和后端各自的处理方案。

    后端方案:利用 Redis 缓存

    当同时发起多个请求时,第一个接口刷新了 Token,后面的请求仍然能通过请求,且不造成 Token 重复刷新。那么,后端在用户第一次登录时,需要将生成的 Token 数据(token 和 createTime)缓存一份到 Redis 中。

    当 Token 过期时,重新生成新的 Token 数据并更新 Redis 缓存,同时在 Redis 中设置一条 Token 过渡数据并设置一个很短的过期时间(比如 30s)。如果后面的请求发现 Token 已经被刷新了,就判断 Redis 中是否存在 Token 过渡数据,存在就放行,这样同一时间的请求都可以通过。

    源码地址:https://github.com/yifanzheng/spring-security-jwt/tree/refresh-token-redis

    Token 刷新流程图

    在这里插入图片描述

    前端方案:请求拦截

    由于前端请求都是异步的,只有一个请求的时候,刷新 Token 是比较好处理的,但并发请求下刷新 Token 处理起来有点麻烦。我们需要考虑在多个请求几乎同时发起并且 Token 都失效的情况,当第一个请求进入 Token 刷新流程时,其他请求必须等待第一个请求完成 Token 刷新后再使用新 Token 进行重试。
    简单地讲,就是同一时间有多个请求且 Token 都失效,在第一个请求进行 Token 刷新时,其他请求必须处于等待状态,直到 Token 刷新完成,才能携带新 Token 进行重试。

    下面,我使用了 Angular 的请求拦截器,利用 BehaviorSubject 进行 Token 刷新状态的监听,当 Token 刷新成功,放行后面的请求进行重试。

    除此之外,前端还可以利用 Promise,将请求存进队列中后,同时返回一个 Promise,让这个 Promise 一直处于 Pending 状态(即不调用 resolve),此时这个请求就会一直等待,只要我们不执行 resolve,这个请求就会一直在等待。当刷新 Token 的请求完成后 ,我们再调用 resolve,逐个重试。

    Github 地址:https://github.com/yifanzheng/spring-security-jwt/tree/refresh-token-frontend

    Angular 代码示列

    import { Injectable } from "@angular/core";
    import {
      HttpEvent,
      HttpInterceptor,
      HttpHandler,
      HttpRequest,
      HttpErrorResponse
    } from "@angular/common/http";
    import { throwError, Observable, BehaviorSubject, of } from "rxjs";
    import { catchError, filter, finalize, take, switchMap, mergeMap } from "rxjs/operators";
    
    @Injectable()
    export class AuthInterceptor implements HttpInterceptor {
    
      private refreshTokenInProgress = false;
      private refreshTokenSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
    
      intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (!req.headers.has("Content-Type")) {
          req = req.clone({
            headers: req.headers.set("Content-Type", "application/json")
          });
        }
        // 统一加上服务端前缀
        let url = req.url;
        if (!url.startsWith('https://') && !url.startsWith('http://')) {
          url = "./" + url;
        }
        req = req.clone({ url });
        req = this.setAuthenticationToken(req);
    
        return next.handle(req).pipe(
          mergeMap((event: any) => {
            // 若一切都正常,则后续操作
            return of(event);
          }),
          catchError((error: HttpErrorResponse) => {
            // 当是 401 错误时,表示 Token 已经过期,需要进行 Token 刷新
            if (error && error.status === 401) {
              if (this.refreshTokenInProgress) {
                // 如果 refreshTokenInProgress 为 true,我们将等到 refreshTokenSubject 是 true 时,才可以再次重试该请求
                // 这表示刷新 Token 动作已完成,新 Token 已准备就绪
                return this.refreshTokenSubject.pipe(
                  filter(result => result),
                  take(1),
                  switchMap(() => next.handle(this.setAuthenticationToken(req)))
                );
              } else {
                this.refreshTokenInProgress = true;
                // 将 refreshTokenSubject 设置为 false,以便后面的请求调用时将处于等待状态,直到检索到新 Token 为止
                this.refreshTokenSubject.next(false);
                return this.refreshToken().pipe(
                  switchMap((newToken: string) => {
                    this.refreshTokenSubject.next(true);
                    // 重新设置新的 Token
                    localStorage.setItem("token", newToken);
                    return next.handle(this.setAuthenticationToken(req));
                  }),
                  // 当刷新 Token 请求完成后,需要将 refreshTokenInProgress 设置为 false,用于下次刷新 Token
                  finalize(() => (this.refreshTokenInProgress = false))
                );
              }
            } else {
              return throwError(error);
            }
          })
        );
      }
    
      private refreshToken(): Observable<any> {
        // 这里需要换成实际的 Token 刷新接口
        return of("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdGFyIiwicm9sZSI6WyJST0xFX1VTRVIiXSwiaXNzIjoic2VjdXJpdHkiLCJpYXQiOjE2MDY4MjczMDAsImF1ZCI6InNlY3VyaXR5LWFsbCIsImV4cCI6MTYwNjgzNDUwMH0.Hiq2DsH6j4XFd_v87lDWGlYembTLck7DjMLRLWdyvOo");
      }
    
      private setAuthenticationToken(request: HttpRequest<any>): HttpRequest<any> {
        return request.clone({
          headers: request.headers.set("Authorization", "Bearer " + localStorage.getItem("token"))
        });
      }
    }
    
    展开全文
  • 添加中间件,处理多个前端来的请求时,如果token需要刷新,先查看缓存,如果没有就在...这样就避免了重复刷新token问题。 中间件代码 <?php namespace App\Http\Middleware; use Closure; use...

    添加中间件,处理多个前端来的请求时,如果token需要刷新,先查看缓存,如果没有就在redis中做个标志位进行短期缓存,其他的请求发现缓存中的token,就不再刷新token了。这样就避免了重复刷新token的问题。

    中间件代码

       <?php
       
       namespace App\Http\Middleware;
       
       use Closure;
       use JWTAuth;
       use Tymon\JWTAuth\Exceptions\JWTException;
       use Tymon\JWTAuth\Exceptions\TokenExpiredException;
       use Tymon\JWTAuth\Exceptions\TokenInvalidException;
       use Illuminate\Support\Facades\Redis;
       
       class GetUserFromToken
       {
           
           public function handle($request, Closure $next)
           {
               $newToken = null;
               $auth = JWTAuth::parseToken();
               if (! $token = $auth->setRequest($request)->getToken()) {
                   return response()->json([
                       'code' => '2',
                       'msg' => '无参数token',
                       'data' => '',
                   ]);
               }
       
               try {
                   $user = $auth->authenticate($token);
                   if (! $user) {
                       return response()->json([
                           'code' => '2',
                           'msg' => '未查询到该用户信息',
                           'data' => '',
                        ]);
                   }
                   $request->headers->set('Authorization','Bearer '.$token);
               } catch (TokenExpiredException $e) {
                   try {
                       sleep(rand(1,5)/100);
                       $newToken = JWTAuth::refresh($token);
                       $request->headers->set('Authorization','Bearer '.$newToken); // 给当前的请求设置性的token,以备在本次请求中需要调用用户信息
                       // 将旧token存储在redis中,30秒内再次请求是有效的
                       Redis::setex('token_blacklist:'.$token,30,$newToken);
                   } catch (JWTException $e) {
                       // 在黑名单的有效期,放行
                       if($newToken = Redis::get('token_blacklist:'.$token)){
                           $request->headers->set('Authorization','Bearer '.$newToken); // 给当前的请求设置性的token,以备在本次请求中需要调用用户信息
                           return $next($request);
                       }
                       // 过期用户
                       return response()->json([
                           'code' => '2',
                           'msg' => '账号信息过期了,请重新登录',
                       ]);
                   }
               } catch (JWTException $e) {
                   return response()->json([
                       'code' => '2',
                       'msg' => '无效token',
                       'data' => '',
                    ]);
               }
               $response = $next($request);
       
               if ($newToken) {
                   $response->headers->set('Authorization', 'Bearer '.$newToken);
               }
       
               return $response;
           }
       }
       

     

    转载于:https://www.cnblogs.com/fenle/p/10666559.html

    展开全文
  • python 多并发竞争微信token刷新问题的解决方案
    看日志:
    正常时候的日志:
    2017-09-24 07:35:30,723 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 07:35:31,342 views.py[line:24] [INFO]  【获取token】
    2017-09-24 07:35:31,343 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 07:35:35,156 views.py[line:24] [INFO]  【获取token】
    2017-09-24 07:35:35,157 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 07:35:40,285 views.py[line:24] [INFO]  【获取token】
    2017-09-24 07:35:40,286 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 07:35:52,522 views.py[line:24] [INFO]  【获取token】
    2017-09-24 07:35:52,523 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 07:35:52,523 views.py[line:51] [INFO]  【重置Token】 getToken中竟然拿到了过期的token,Ok...!
    2017-09-24 07:35:52,524 tools.py[line:45] [INFO]  【生成新的token】
    2017-09-24 07:35:52,934 tools.py[line:66] [INFO]  token=uX-Ss9sgpfcK5fAmxOomQevy4FZTQXB_FX6G0JjoNWGjws5ZJtK-QVXLcgXLooIcN4zutB8KehLQPV-0ZR3BhiD31jOy77M_d306XlIxqlbMrBuYYyrQg4xFHvNJW8MPSCAhABAWGE, expire_at=expire: 2017-09-24 09:35:52, ticket=kgt8ON7yVITDhtdwci0qeYKnTxnRCJqsQusUs77nYwUaOBEr--EY31LjMYstkPp15zQ0KTyT84KANjsx2UEu-A
    2017-09-24 07:35:52,935 views.py[line:61] [INFO]  写到redis中...
    2017-09-24 07:35:52,935 views.py[line:65] [INFO]  写到文件中...
    2017-09-24 07:36:11,051 views.py[line:24] [INFO]  【获取token】
    2017-09-24 07:36:11,052 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 07:36:27,335 views.py[line:24] [INFO]  【获取token】
    2017-09-24 07:36:27,335 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 07:36:28,813 views.py[line:24] [INFO]  【获取token】
    2017-09-24 07:36:28,814 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 07:36:32,783 views.py[line:24] [INFO]  【获取token】
    
    错误时候的日志:
    2017-09-24 09:35:48,320 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 09:35:48,992 views.py[line:24] [INFO]  【获取token】
    2017-09-24 09:35:48,993 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 09:35:51,360 views.py[line:24] [INFO]  【获取token】
    2017-09-24 09:35:51,361 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 09:35:51,814 views.py[line:24] [INFO]  【获取token】
    2017-09-24 09:35:51,814 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 09:35:53,318 views.py[line:24] [INFO]  【获取token】
    2017-09-24 09:35:53,319 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 09:35:53,319 views.py[line:51] [INFO]  【重置Token】 getToken中竟然拿到了过期的token,Ok...!
    2017-09-24 09:35:53,319 tools.py[line:45] [INFO]  【生成新的token】
    2017-09-24 09:52:03,673 tools.py[line:32] [INFO]  Current log level is : DEBUG
    2017-09-24 09:52:03,796 MyCache.py[line:17] [INFO]  ===>redis畅通,切换到缓存模式!cache_flag = TRUE.
    2017-09-24 09:52:03,797 wsgi.py[line:22] [INFO]  【初始化一个token】
    2017-09-24 09:52:03,797 tools.py[line:45] [INFO]  【生成新的token】
    2017-09-24 09:52:05,620 tools.py[line:32] [INFO]  Current log level is : DEBUG
    2017-09-24 09:52:05,645 MyCache.py[line:17] [INFO]  ===>redis畅通,切换到缓存模式!cache_flag = TRUE.
    2017-09-24 09:52:05,646 wsgi.py[line:22] [INFO]  【初始化一个token】
    2017-09-24 09:52:05,646 tools.py[line:45] [INFO]  【生成新的token】
    2017-09-24 09:52:08,796 tools.py[line:66] [INFO]  token=CSgJq-aPPLUYr_RoFbljb_Dia42HtEgQj77g55TWW1sVAIuOEvn5jjMOPwohmaTBQ73SDjBx2L1L0AifX0QNH3Rxvsb7YRlomapkypc9J7tVBnqo4w_izu-JWXN0Fs5XWZChAFAADG, expire_at=expire: 2017-09-24 11:52:04, ticket=kgt8ON7yVITDhtdwci0qeYKnTxnRCJqsQusUs77nYwVrkjNNRqAVnEJhMznAJIRjvn93qY1duo-sEO-gQlYr8A
    2017-09-24 09:52:08,796 wsgi.py[line:25] [INFO]  写到文件中...
    2017-09-24 09:52:08,797 wsgi.py[line:29] [INFO]  写到redis中...
    2017-09-24 09:52:09,458 views.py[line:24] [INFO]  【获取token】
    2017-09-24 09:52:09,460 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 09:52:09,462 views.py[line:24] [INFO]  【获取token】
    2017-09-24 09:52:09,463 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 09:52:11,236 views.py[line:24] [INFO]  【获取token】
    2017-09-24 09:52:11,237 views.py[line:34] [INFO]  GetToken from Redis.
    2017-09-24 09:52:11,280 views.py[line:24] [INFO]  【获取token】
    

    以下1,2,3,4... 是我的思考过程。。

    1、
    明显感觉到,在切换token的那一瞬间,正常情况下,是一个用户来请求,然后就完美度过这个切换token情况。
    但是如果那一瞬间是3个用户来请求,则有问题啦。。

    2、
    为什么会冒出这句话:Current log level is : DEBUG   ===>redis畅通,切换到缓存模式!cache_flag = TRUE.
    这是项目启动的时候才会说的话。而且还是重启两次?

    后来发现是因为我用supervisor手动重启了wxtoken这个项目才打印这个日志(尴尬),然而为啥子是两次呢,是因为uwsgi就开启了两个进程。
    root      8882 13690  0 10:23 ?        00:00:00 uwsgi /data/xxxx/wxtoken/uwsgi.ini --plugin Python
    root      8888  8882  0 10:23 ?        00:00:00 uwsgi
    可是我的uwsgi配置如下:
    [uwsgi]
    processes = 1
    vhost = false
    plugins = python
    socket = 127.0.0.1:xxxx
    master = true
    enable-threads = true
    workers = 1
    wsgi-file = /data/xxxx/wxtoken/wxtoken/wsgi.py
    chdir = /data/xxxx/wxtoken
    home=/data/python_venv/wxtoken_venv/
    listen=1024
    workers=1 并且 processes =1, 就是单进程呀,为啥子有2个呢?
    哦哦哦,原来是因为master=true,会有一个master进程+单个子进程=2个进程。爸爸管理n个孩子,如果kill爸爸就是杀了所有孩子。
    先让master=false。因为我就是要单个进程即可。
    附上uwsgi.ini参数说明(当然有些和我的配置出入,比如home就是程序运行的python环境目录):
    socket:uwsgi监听的socket,可以为socket文件或ip地址+端口号(如0.0.0.0:9000),取决于nginx中upstream的设置
    processes:同时启动uwsgi进程的个数,这个进程与nginx中的workers是不一样的,uwsgi中的每个进程每次只能处理一个请求(进程越多可以同时处理的请求越多),nginx采用的异步非阻塞的方式来处理请求的,每个进程可以接受处理多个请求。
    chdir:在app加载前切换到当前目录
    pythonpath:给PYTHONPATH 增加一个目录(或者一个egg),最多可以使用该选项64次。
    module:加载指定的python WSGI模块(模块路径必须在PYTHONPATH里)
    master:相当于master=true,启动一个master进程来管理其他进程,以上述配置为例,其中的4个uwsgi进程都是这个master进程的子进程,如果kill这个master进程,相当于重启所有的uwsgi进程
    pidfile:在失去权限前,将master的pid写到当前文件中
    daemonize:使进程在后台运行,并将日志打到指定的日志文件或者udp

    3、
    回到我的错误日志:
    2017-09-24 09:35:53,319 tools.py[line:45] [INFO]  【生成新的token】
    2017-09-24 09:52:03,673 tools.py[line:32] [INFO]  Current log level is : DEBUG
    两句话差了快20分钟,在生成新的token这里就一直挂着了呢。我大概知道是网络请求有问题,要不把urllib改成request吧。
    改为python更加推荐的requests库,加入超时参数,加入https不验证参数(有些时候验证https会报SSL错误,麻烦得紧)
    # wp = urllib.urlopen(url)
    # ret = json.loads(wp.read())
    r = requests.get(url, timeout=3, verify=False)
    ret = r.json()
    
    以及
    
    # jsapiTicketRequestData = {'type': 'jsapi', 'access_token': access_token}
    # jsapiTicketRequestDataUrlencode = urllib.urlencode(jsapiTicketRequestData)
    # jsapiTicketRequest = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"
    # jsapiTicketRequestGet = urllib2.Request(url=jsapiTicketRequest, data=jsapiTicketRequestDataUrlencode)
    # jsapiTicketRequestGetData = urllib2.urlopen(jsapiTicketRequestGet)
    # jsapiTicketRequestGetResult = jsapiTicketRequestGetData.read()
    # ticket = json.loads(jsapiTicketRequestGetResult)['ticket']
    
    payload = {'type': 'jsapi', 'access_token': access_token}
    url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"
    r = requests.get(url, params=payload, timeout=3, verify=False)
    content = r.json()
    ticket = content['ticket']
    

    4、
    修改完毕之后,手动让redis里面的token失效,因为是pickle dumps过的(如果是json dumps的话,能直接修改呀),我还得用程序去修改,修改。测试通过。
    # coding=utf-8
    import logging
    import cPickle as pickle
    
    import redis
    redis_client = redis.Redis(host='localhost', port=6379, db=1, password='xxx')
    
    value = {
        'access_token': 'e4ZUdlQQGRknsN7UfaBruFBKhj8Kj5_6kq7MhlkHscz5DiQSlT0RzQdMs-woooa-FW7JXlAzjUVPen4xTJrgWz6AohKY6KhO3aaPFVVnVz2sW7ATrUUgQtyj-GPrO6iWNDOaAJATJU',
        'access_token_expires_at':'1501228165',
        'ticket': 'kgt8ON7yVITDhtdwci0qeYKnTxnRCJqsQusUs77nYwUL-QgOjkakVZKqMbEctIhcpt',
    }
    name = 'wx_access_token'
    value = pickle.dumps(value)
    redis_client.set(name, value)
    

    5、
    ok,最后一步,还没解决如下问题,如果失效的那一瞬间,同时有10个请求过来。程序会发生什么事呢?
    排个号: 1,2,3,4,5,6,7,8,9,10
    因为这10个兄弟失效了嘛,依次处理,处理3的那一瞬间,token恢复正常,也写进了redis。
    可是4-10这7个兄弟还不知道呀,它们依旧会走完流程,也就是不断地刷新token,不断得使之前的token失效,并且重新写入redis,写7次。
    那么如果有11-30很多其他人这个时候来访问,其实他们只会拿到刚刚被这7个兄弟弄失效的token,但是不会重新去刷新token,因为我判断的依据是超时时间是否超过2小时,哈哈哈。
    所以如果不加锁的话,影响也许就是十几二十的用户吧。

    6、
    ab测试下,上面的想法,结果大体一致,发现影响用户可能只有2,3人。
    ab.exe -n 1200 -c 20 http://xxx.com/getToken/
    
    结果:
    [root@iZ9458z0ss9Z wxtoken]# cat wxtoken.log | grep 生成新     
    2017-09-24 11:17:29,917 10818-140683547019072-MainThread tools.py[line:46] [INFO]  【生成新的token】
    2017-09-24 11:17:41,358 10818-140683547019072-uWSGIWorker1Core0 tools.py[line:46] [INFO]  【生成新的token】
    [root@iZ9458z0ss9Z wxtoken]#
    

    7、
    也有人问我为啥子不加定时器,其实之前是加了的,apscheduler,但是偶尔会报一些奇奇怪怪的错误,要么就是项目没有报错了但是定时器也不工作,很让人烦躁,索性去掉了,毕竟又不是非要用定时器,用定时器也不是非要用这货。
    其实我别的项目用到了更加靠谱的定时器:celery,但是我不想在这个简简单单的地方引入这么重型的哥们。
    (当然如果哪一天项目重要程度升级,并发很高balabala,我就换celery咯。)
    至于现在,我想用python自己去解决这个刷新token的事。

    8、
    加个锁吧。python的threading,condition。
    大致代码如下:
    def get_token_from_srouce():
        """
        从数据源获取token
        要么是缓存,要么是文件。。
        :return:
        """
        response_data = {}
        try:
            item = mycache.get('wx_access_token')
        except Exception, ex:
            logging.error(ex)
            item = None
    
        # 从redis拿
        if item:
            logging.info("GetToken from Redis.")
            dic = item
            response_data['access_token'] = dic['access_token']
            response_data['access_token_expires_at'] = dic['access_token_expires_at']
            response_data['ticket'] = dic['ticket']
        # 从文件中拿
        else:
            logging.info("GetToken from %s." % settings.accessTokenFile)
            with open(settings.accessTokenFile, 'r') as f:
                response_data['access_token'] = f.readline().strip('\n')
                response_data['access_token_expires_at'] = f.readline().strip('\n')
                response_data['ticket'] = f.readline().strip('\n')
        return response_data
    
    def set_token_to_soruce(dic):
        """
        把数据写入数据源
        :param dic:
        :return:
        """
        # 写到redis中
        logging.info("写到redis中...")
        mycache.set('wx_access_token', dic)
    
        # 写到文件中
        logging.info("写到文件中...")
        with open(settings.accessTokenFile, 'w') as tokenFile:
            tokenFile.write(dic['access_token'] + '\n')
            tokenFile.write(str(dic['access_token_expires_at']) + '\n')
            tokenFile.write(dic['ticket'] + '\n')
    
    # 返回access_token
    def get_token(request):
        logging.info(u"【获取token】")
        my_token_dic = get_token_from_srouce()
    
        # 如果获取的时间戳显示token过期,则reset一下
        expires_at = int(my_token_dic['access_token_expires_at'])
        now = int(time.time())
        if now > expires_at:
            logging.info(u"【重置Token】 getToken中竟然拿到了过期的token,Ok...!")
    
            settings.condition.acquire()
    
            # 双重判断
            my_token_dic = get_token_from_srouce()
            expires_at = int(my_token_dic['access_token_expires_at'])
            now = int(time.time())
            if now > expires_at:
                my_token_dic = get_new_token()
                set_token_to_soruce(my_token_dic)
    
            settings.condition.notify_all()
            settings.condition.release()
    
        # 顺便更新返回的信息
        response_data = {}
        response_data['access_token'] = my_token_dic['access_token']
        response_data['access_token_expires_at'] = my_token_dic['access_token_expires_at']
        response_data['ticket'] = my_token_dic['ticket']
        response_data['code'] = 1
        response_data['msg'] = 'Ok!'
    
        return HttpResponse(json.dumps(response_data), content_type="application/json")
    




    总结: 
    1、加个锁机制,保证在失效的那一瞬间,那并发的几十个请求都等着,而且必须是双重检查锁机呦。。
    2、完美解决方案是定时器和getToken服务分离,定时器每个小时去刷一次token,getToken服务不管别的,来了就返回redis或者文件里面的value即可。
    但我就不!

    ps: 印象笔记负责过来的内容排版怎么这么难看呀。

    以上

    展开全文
  • retrofit 刷新token并发处理

    千次阅读 2017-07-04 10:07:51
    主要是解决token刷新并发情况的处理,并且对retrofit的使用进行简单的介绍 对多线程同步问题进行简易说明。
  • 前言: 最近在尝试做前后端分离的项目,由于之前一直听过token和jwt,似乎在前后端分离中也很常用。于是便了解了一番,结果扯出一大堆问题,折腾了好久,所以想把一些问题和...token刷新策略 传统的token认证,...
  • 背景: access_token是公众号的全局唯一票据,公众号...access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。 1、为了保密appsecrect,第三方需要一个access_token获取和...
  • 前端无感刷新token

    千次阅读 2020-03-19 10:31:25
    单个接口的刷新token很好解决,难点是多接口并发 首先将token过期后的请求存到一个数组队列中,想办法让这个请求处于等待中,一直等到刷新token后再逐个重试清空请求队列。 那么如何做到让这个请求处于等待中呢?...
  • 则这两个处理都会进行access_token刷新操作, 2. 由于这两个操作是异步处理,并没有同步机制,如果先刷新的access_token后保存,则会将已经失效的access_token保存下来 3. 后来所有实例化...
  • 通常在普通的操作当中,我们...或者使用token,隐藏在表单中,当提交时进行token验证,验证失败也不让提交。这都是一般的做法。  我们这次碰到的问题是重复提交本身就是一个错误,重复提交会导致一些相关数据
  • 或者使用token,隐藏在表单中,当提交时进行token验证,验证失败也不让提交。这都是一般的做法。我们这次碰到的问题是重复提交本身就是一个错误,重复提交会导致一些相关数据的逻辑不再正确。而这些重复提...
  • @lsxiao 你提出这个并发问题我才意识到jwt的这个缺陷,不过读到https://segmentfault.com/q/10... 这里@我只想帮妈妈洗碗 的回答我突然想到:如果不是持续刷新token,而是过期前3分钟刷新token刷新token把旧的...
  • 1.同一用户并发刷新Token 2.服务端已经产生了新的Token后,但并发过来的请求还在用老的Token请求数据 四.附件 一.问题 App 安装后,第一次启动时需要登录(在某些页面提示需要登录或者直接启动在登录界面)。而...
  • 而管理token刷新token就是一个问题了,比如判断过期时间到了去刷新token之后再请求,同时并发的请求你不能每个都去刷新一次token,只刷新一个,那并发的请求还是老的token。 解决的方法有两种,一种是请求之前处理...
  • 随着业务的增长,线上系统qps越来越高,起初市场同事和客户偶尔会反馈用户看到的信息出现的不是自己的信息,但是重新刷新之后就好了,当时我们就对这个问题进行分析,但是都是无疾而终,没有找到问题所在,随着用户...
  • 问题起源:后台刷新token的时候,会有高并发问题。 即:A发起请求的时候,刷新token时,还未存入redis,此时B发起请求,问题就出现了。 由于Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,所以...
  • 问题起源:后台刷新token的时候,会有高并发问题。 即:A发起请求的时候,刷新token时,还未存入redis,此时B发起请求,问题就出现了。 由于Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,所以...
  • 认识JWT

    2018-08-17 17:08:00
    5.并发问题 使用 jwt 也有一段时间了,感觉还是蛮方便的。写一篇文章总结一下。 JWT 是什么 JWT(JSON Web Token)是一个字符串,我们在发起网络请求时,将其放在header或者url中,这样可以保证...
  • 推荐 在线阅读 (Github 访问速度比较慢可能会导致部分图片无法刷新出来) 推荐2021最新实战项目源码下载 书单已经被移动到awesome-cs 这个仓库。 介绍:关于 JavaGuide 的相关介绍请看:关于 JavaGuide 的一些说明...
  • 推荐 在线阅读 (Github 访问速度比较慢可能会导致部分图片无法刷新出来) 推荐2021最新实战项目源码下载 书单已经被移动到awesome-cs 这个仓库。 介绍:关于 JavaGuide 的相关介绍请看:关于 JavaGuide 的一些说明...
  • 推荐 在线阅读 (Github 访问速度比较慢可能会导致部分图片无法刷新出来) 推荐2021最新实战项目源码下载 书单已经被移动到awesome-cs 这个仓库。 介绍:关于 JavaGuide 的相关介绍请看:关于 JavaGuide 的一些说明...
  • 公司年会大屏幕抽奖

    2018-09-25 15:06:14
    更新:某些服务器上安装会出现微信token验证失败的一个问题 更新:数据库文件中的一个语法错误 2016.5.28 更新:支持本地缓存memcache,大幅提高摇一摇性能 2016.5.27 新增:微信菜单管理 更新:增加默认设置,一键触发...
  • 本地执行python login.py获取到sessionToken,acw_tc,MOD_AUTH_CAS和CpdailyInfo,填入currency/index.py对应位置 配置腾讯云函数,依赖层配置和上面一样,提交方法选择提交文件夹,请选择currency文件夹,触发管理...
  • 浅说 XSS 和 CSRF

    2021-01-09 07:07:28
    本文将会简单介绍 XSS 和 CSRF 的攻防问题。 声明:本文的示例仅用于演示相关的攻击原理 <h2>XSS <p>XSS,即 Cross Site Script,中译是跨站脚本攻击;其原本缩写是 CSS,但为了和...
  • 自定义RecyclerView下拉刷新上拉加载,支持加载loading,空页面,异常界面,有数据界面状态切换 缓存使用Realm数据库,做数据的增删改查 状态管理库与Activity和Fragment结合,可以自由切换不同的状态 3.3 项目...
  • 数据源支持模拟数据(默认)、数据库采集、串口通信(需定制)、网络通信(需定制)、网络请求等,可自由设定每个子界面的采集间隔即数据刷新频率。 采用纯QWidget编写,亲测Qt4.6到Qt5.15任意版本,理论上支持后续...

空空如也

空空如也

1 2
收藏数 30
精华内容 12
关键字:

token刷新并发问题