token 订阅
在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。 展开全文
在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。
信息
学    科
信息技术(IT)
外文名
Token
词    性
名词
中文名
令牌,标记
Token令牌
(信息安全术语)Token, 令牌,代表执行某些操作的权利的对象访问令牌(Access token)表示访问控制操作主体的系统对象邀请码,在邀请系统中使用Token, Petri 网(Petri net)理论中的Token密保令牌(Security token),或者硬件令牌,例如U盾,或者叫做认证令牌或者加密令牌,一种计算机身份校验的物理设备会话令牌(Session token),交互会话中唯一身份标识符令牌化技术 (Tokenization), 取代敏感信息条目的处理过程
收起全文
精华内容
下载资源
问答
  • JWT格式的Token动态库封装,包括获取token,验证token,获取token中保存的内容,验证了Token是否正确,验证了Token的ip是否相同,验证了Token的过期时间
  • token

    千次阅读 2019-07-24 17:28:40
    token 身份验证 http 请求的无状态性 JWT (jsonwebtoken) 用户登录 服务器端产生一个token (加密字符串) 发送给前端 前端将token 进行保存 前端发起数据请求的时候携带token 服务端 验证token 是否合法 如果...

    token

    • 身份验证
      http 请求的无状态性

    • JWT (jsonwebtoken)

    1. 用户登录 服务器端产生一个token (加密字符串) 发送给前端
    2. 前端将token 进行保存
    3. 前端发起数据请求的时候携带token
    4. 服务端 验证token 是否合法 如果合法继续操作 不合法终止操作
    5. token 的使用场景 无状态请求 保持用户的登录状态 第三方登录(token+auth2.0)
    • 小案例:登录页面
    1. 先打开终端,运行下面代码:
      在这里插入图片描述
    2. cd 进入token_demo,再安装依赖性文件:
      在这里插入图片描述
    3. 打造一个简单的接口文件(login.js):
      在这里插入图片描述
    4. 在app.js 中引入 路由模块 :

    在这里插入图片描述
    在这里插入图片描述
    5. 在 views 文件夹中新增一个 login.ejs 文件:
    在这里插入图片描述
    6. 启动项目( npm run start ), 测试接口:
    在这里插入图片描述
    7. 新建前端文件 token_fe,新建 index.html,样式布局。

    <body onload="loadFn()">
      <div class="container">
        <div class="row">
          <div class="form-group">
            <label for=""> 用户名 </label>
            <input type="text" class="form-control username" name="" id="" aria-describedby="emailHelpId" placeholder="">
          </div>
        </div>
        <div class="row">
          <div class="form-group">
            <label for=""> 密码 </label>
            <input type="password" class="form-control password" name="" id="" placeholder="">
          </div>
        </div>
        <div class="row">
          <button type="button" class="btn btn-primary login"> 登录 </button>
        </div>
      </div>
    </body>
    
    1. (通过go live)打开静态服务器
    2. 点击登录得到用户名和密码,发送请求:
      在这里插入图片描述
    3. 添加 onload 事件, 通过判断 cookie或是 本地存储是否有token值。如果有,那么不需要登录,自动跳转首页; 如果没有, 那么就登录。
     function loadFn() {
        var token = localStorage.getItem('token')
        if (token) {
          //有
          location.href = './index.html'
        } else {
          alert('你需要登录')
        }
      }
    
    1. 解决跨域,在 login.js 里面设置请求头:
      在这里插入图片描述
    2. 前端发请求,在data里面加上token:
      在这里插入图片描述
    3. 产生公钥和私钥,在终端分别运行以下代码:
      在这里插入图片描述
    4. 后端判断 token 有没有值,安装 jsonwebtoken(npm i jsonwebtoken -S)
    //引入文件系统
    const fs = require('fs')
    const path = require('path')
    const jwt = require('jsonwebtoken')
    
    
       const { token, username, password } = req.body
        if (token) {
          //证明有值
          res.render('login', {
            data: JSON.stringify({
              info: '登录成功',
              status: 1
            })
          })
        } else {
          // 第一次登录 或是 token失效 
          // 重新生成token  返回给前台
          // 1. 通过文件系统读取私钥
          let private_key = fs.readFileSync(path.join(__dirname, '../rsa/private_key.pem'))
          var use_token = jwt.sign(username, private_key, { algorithm: 'RS256' });
          /* 
            payload: 负载也就是数据,这里是值用户名
            private_key: 通过openSSL 生成的私钥 
            RS256: 算法
          */
          res.render('login', {
            data: JSON.stringify({
              info: '登录成功',
              status: 1,
              token: use_token
            })
          })
        }
    
    1. 存 token:
    success(res) {
            const result = JSON.parse(res)
            if (result.status === 1) {
              localStorage.setItem('token', result.token)
            }
          }
    
    

    测试!

    展开全文
  • springboot+redis+token保持登录

    热门讨论 2017-08-03 11:46:57
    通过redis 设置session过期时间,实现token 登录机制
  • JWT Token生成及验证

    热门讨论 2017-11-03 15:07:34
    请参考博客:http://www.cnblogs.com/chenwolong/p/Token.html
  • WebApi安全性 使用TOKEN+签名验证
  • webapi token验证例子

    热门讨论 2017-06-05 01:10:04
    webapi token验证例子
  • 七牛的Token生成

    热门讨论 2015-04-01 20:51:35
    七牛上传图片到服务器,需要一个长久的Token,目前在七牛的说明文档中没有直接说明。我现在提供一个七牛的生存方式,希望能帮助有需要的人。
  • JAVA微信公众平台Token验证

    热门讨论 2014-12-08 11:34:48
    首先要开启开发模式必须要进行Token的一个验证,你给出一个地址,微信发送请求,然后你给出相应,就这么简单。虽然说是简单,但是这是事后才说的,官方只有PHP的DEMO,我用JAVA开发的时候各种蛋疼不会弄,不过好在...
  • 前后端分离下的token机制

    千次阅读 2019-06-01 19:38:51
    一、 什么是token ? Token 是在服务端产生的,如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。 二、token...

    一、 什么是 token ?

    Token 是在服务端产生的,如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。

    二、token 的适用场景

    登录:

     业务请求:

    Token 过期,刷新 Token:

     三、token的使用

    1.创建:token 的实质其实就是一个唯一标识的字符串,一般来说由UUID生成,或者一些复杂的可以由时间戳或者用户的ID根据某一算法进行加密生成,取到token之后再进行解密,取出用户的ID。我这里以简单的UUID进行举例:

    String token = UUID.randomUUID().toString();// 生成token

    2.保存:很多常见的做法是将token保存进数据库或者缓存,但是放进数据库会严重消耗服务器资源,所以本人不建议这样做,我比较喜欢把token 存进redis缓存里,最主要的好处就是简单易操作,还大大加快了数据查询速度,我把 redis 理解为一张庞大的Map 表。

    3.将 token 添加到响应头

    response.setHeader("Access-Control-Expose-Headers",
    				"Cache-Control,Content-Type,Expires,Pragma,Content-Language,Last-Modified,token");
    response.addHeader("token", token);

    4.从请求头里取出token

    response.getHeader("token");

    展开全文
  • Android之window机制token验证

    千次阅读 多人点赞 2020-10-13 20:46:55
    这篇文章讲解关于window token的问题,同时也是Context机制和Window机制这两篇文章的一个补充。如果你对Android的Window机制和Context机制目前位了解过,强烈建议你先阅读前面两篇文章,可以帮助理解整个源码的解析...

    文章已授权『郭霖』公众号发布

    前言

    很高兴遇见你~ 欢迎阅读我的文章

    这篇文章讲解关于window token的问题,同时也是Context机制Window机制这两篇文章的一个补充。如果你对Android的Window机制和Context机制目前位了解过,强烈建议你先阅读前面两篇文章,可以帮助理解整个源码的解析过程以及对token的理解。同时文章涉及到Activty启动流程源码,读者可先阅读Activity启动流程这篇文章。文章涉及到这些方面的内容默认读者已经阅读且了解,不会对这方面的内容过多阐述,如果遇到一些内容不理解,可以找到对应的文章看一下。那么,我们开始吧。

    当我们想要在屏幕上展示一个Dialog的时候,我们可能会在Activity的onCreate方法里这么写:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val dialog = AlertDialog.Builder(this)
        dialog.run{
            title = "我是标题"
            setMessage("我是内容")
        }
        dialog.show()
    }
    

    他的构造参数需要一个context对象,但是这个context不能是ApplicationContext等其他context,只能是ActivityContext(当然没有ApplicationContext这个类,也没有ActivityContext这个类,这里这样写只是为了方便区分context类型,下同)。这样的代码运行时没问题的,如果我们使用Application传入会怎么样呢?

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // 注意这里换成了ApplicationContext
        val dialog = AlertDialog.Builder(applicationContext)
        ...
    }
    

    运行一下:

    报错了,原因是You need to use a Theme.AppCompat theme (or descendant) with this activity.,那我们给他添加一个Theme:

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // 注意这里添加了主题
        val dialog = AlertDialog.Builder(applicationContext,R.style.AppTheme)
        ...
    }
    

    好了再次运行:

    嗯嗯?又崩溃了,原因是:Unable to add window -- token null is not valid; is your activity running?token为null?这个token是什么?为什么同样是context,使用activity没问题,用ApplicationContext就出问题了?他们之间有什么区别?那么这篇文章就围绕这个token来展开讨论一下。

    文章采用思考问题的思路来展开讲述,我会根据我学习这部分内容时候的思考历程进行复盘。希望这种解决问题的思维可以帮助到你。
    对token有一定了解的读者可以看到最后部分的整体流程把握,再选择想阅读的部分仔细阅读。

    什么是token

    首先我们看到报错是在ViewRootImpl.java:907,这个地方肯定有进行token判断,然后抛出异常,这样我们就能找到token了,那我们直接去这个地方看看。:

    ViewRootImpl.class(api29)
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        ...
        int res;
        ...
        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
        ...
        if (res < WindowManagerGlobal.ADD_OKAY) {
            ...
            switch (res) {
                case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                    /*
                    *	1
                    */
                    throw new WindowManager.BadTokenException(
                        "Unable to add window -- token " + attrs.token
                        + " is not valid; is your activity running?");    
                    ...
            }
            ...
        }
        ...
    }
    

    我们看到代码就是在注释1的地方抛出了异常,是根据一个变量res来判断的,这个res来自方法addToDisplay,那么token的判断肯定在这个方法里面了,res只是一个 判断的结果,那么我们需要进到这个addToDisplay里去看一下。mWindowSession的类型是IWindowSession,他是一个接口,那他的实现类是什么?找不到实现类就无法知道他的具体代码。这里涉及到window机制的相关内容,简单讲一下:

    WindowManagerService是系统服务进程,应用进程跟window联系需要通过跨进程通信:AIDL,这里的IWindowSession只是一个Binder接口,他的具体实现类在系统服务进程的Session类。所以这里的逻辑就跳转到了Session类的addToDisplay方法中。关于window机制更加详细的内容,读者可以阅读Android全面解析之Window机制这篇文章进一步了解,限于篇幅这里不过多讲解。

    那我们继续到Session的方法中看一下:

    Session.class(api29)
    class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
       	final WindowManagerService mService; 
        public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
                int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
                Rect outStableInsets, Rect outOutsets,
                DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
                InsetsState outInsetsState) {
            return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                    outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
                    outInsetsState);
        }
    }
    

    可以看到,Session确实是继承自接口IWindowSession,因为WMS和Session都是运行在系统进程,所以不需要跨进程通信,直接调用WMS的方法:

    public int addWindow(Session session, IWindow client, int seq,
            LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState) {
       	...
        WindowState parentWindow = null;
        ...
    	// 获取parentWindow
        parentWindow = windowForClientLocked(null, attrs.token, false);
        ...
        final boolean hasParent = parentWindow != null;
        // 获取token
        WindowToken token = displayContent.getWindowToken(
            hasParent ? parentWindow.mAttrs.token : attrs.token);
        ...
      	// 验证token
        if (token == null) {
        if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
              Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                               + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
           ...//各种验证
        }
        ...
    }
    

    WMS的addWindow方法代码这么多怎么找到关键代码?还记得viewRootImpl在判断res是什么值的情况下抛出异常吗?没错是WindowManagerGlobal.ADD_BAD_APP_TOKEN和WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN,我们只需要找到其中一个就可以找到token的判断位置,从代码中可以看到,当token==null的时候,会进行各种判断,第一个返回的就是WindowManagerGlobal.ADD_BAD_APP_TOKEN,这样我们就顺利找到token的类型:WindowToken。那么根据我们这一路跟过来,终于找到token的类型了。再看一下这个类:

    class WindowToken extends WindowContainer<WindowState> {
        ...
        // The actual token.
        final IBinder token;
    }
    

    官方告诉我们里面的token变量才是真正的token,而这个token是IBinder对象。

    好了到这里关于token是什么已经弄清楚了:

    • token是一个IBinder对象
    • 只有利用token才能成功添加dialog

    那么接下来就有更多的问题需要思考了:

    • Dialog在show过程中是如何拿到token并给到WMS验证的?
    • 这个token在activity和application两者之间有什么不同?
    • WMS怎么知道这个token是合法的,换句话说,WMS怎么验证token的?

    dialog如何获取到context的token的?

    首先,我们解决第一个问题:Dialog在show过程中是如何拿到token并给到WMS验证的?

    我们知道导致两种context(activity和application)弹出dialiog的不同结果,原因在于token的问题。那么在弹出Dialog的过程中,他是如何拿到context的token并给到WMS验证的?源码内容很多,我们需要先看一下token是封装在哪个参数被传输到了WMS,确定了参数我们的搜索范围就减小了,我们回到WMS的代码:

    parentWindow = windowForClientLocked(null, attrs.token, false);
    WindowToken token = displayContent.getWindowToken(
            hasParent ? parentWindow.mAttrs.token : attrs.token);
    

    我们可以看到token和一个attrs.token关系非常密切,而这个attrs从调用栈一路往回走到了viewRootImpl中:

    ViewRootImpl.class(api29)
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
       ...
    }
    

    可以看到这是一个WindowManager.LayoutParams类型的对象。那我们接下来需要从最开始show()开始,追踪这个token是如何被获取到的:

    Dialog.class(api30)
    public void show() {
        ...
        WindowManager.LayoutParams l = mWindow.getAttributes();
        ...
        mWindowManager.addView(mDecor, l);
        ...
    }
    

    这里的mWindowmWindowManager是什么?我们到Dialog的构造函数一看究竟:

    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        // 如果context没有主题,需要把context封装成ContextThemeWrapper
        if (createContextThemeWrapper) {
            if (themeResId == Resources.ID_NULL) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }
        // 初始化windowManager
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        // 初始化PhoneWindow
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        ...
        // 把windowManager和PhoneWindow联系起来
        w.setWindowManager(mWindowManager, null, null);
        ...
    }
    

    初始化的逻辑我们看重点就好:首先判断这是不是个有主题的context,如果不是需要设置主题并封装成一个ContextThemeWrapper对象,这也是为什么我们文章一开始使用application但是没有设置主题会抛异常。然后获取windowManager,注意,这里是重点,也是我当初看源码的时候忽略的地方。这里的context可能是Activity或者Application,他们的getSystemService返回的windowManager是一样的吗,看代码:

    Activity.class(api29)
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }
        if (WINDOW_SERVICE.equals(name)) {
            // 返回的是自身的WindowManager
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }
    
    ContextImpl.class(api29)
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
    

    Activity返回的其实是自身的WindowManager,而Application是调用ContextImpl的方法,返回的是应用服务windowManager。这两个有什么不同,我们暂时不知道,先留意着,再继续把源码看下去寻找答案。我们回到前面的方法,看到mWindowManager.addView(mDecor, l);我们知道一个PhoneWindow对应一个WindowManager,这里使用的WindowManager并不是Dialog自己创建的WindowManager,而是参数context的windowManager,也意味着并没有使用自己创建的PhoneWindow。Dialog创建PhoneWindow的目的是为了使用DecorView模板,我们可以看到addView的参数里并不是window而只是mDecor。

    我们继续看代码,,同时要注意这个l参数,最终token就是封装在里面。addView方法最终会调用到了WindowManagerGlobaladdView方法,具体调用流程可以看我文章开头的文章:

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }
    	...
        ViewRootImpl root;
        ...
        root = new ViewRootImpl(view.getContext(), display);
    	...
        try {
            root.setView(view, wparams, panelParentView);
        } 
        ...
    }
    

    这里我们只看WindowManager.LayoutParams参数,parentWindow是与windowManagerPhoneWindow,所以这里肯定不是null,进入到adjustLayoutParamsForSubWindow方法进行调整参数。最后调用ViewRootImpl的setView方法。到这里WindowManager.LayoutParams这个参数依旧没有被设置token,那么最大的可能性就是在adjustLayoutParamsForSubWindow方法中了,马上进去看看:

    Window.class(api29)
    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        CharSequence curTitle = wp.getTitle();
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            // 子窗口token获取逻辑
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
                    wp.token = decor.getWindowToken();
                }
            }
            ...
        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
                    wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
            // 系统窗口token获取逻辑
            ...
        } else {
            // 应用窗口token获取逻辑
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
            ...
        }
        ...
    }
    

    终于看到了token的赋值了,这里分为三种情况:应用层窗口、子窗口和系统窗口,分别进行token赋值。

    应用窗口直接获取的是与WindowManager对应的PhoneWindow的mAppToken,而子窗口是拿到DecorView的token,系统窗口属于比较特殊的窗口,使用Application也可以弹出,但是需要权限,这里不深入讨论。而这里的关键就是:这个dialog是什么类型的窗口?以及windowManager对应的PhoneWindow中有没有token?

    而这个判断跟我们前面赋值的不同WindowManagerImpl有直接的关系。那么这里,就必须到Activity和Application创建WindowManager的过程一看究竟了。

    Activity与Application的WindowManager

    首先我们看到Activity的window创建流程。这里需要对Activity的启动流程有一定的了解,有兴趣的读者可以阅读Activity启动流程。追踪Activity的启动流程,最终会到ActivityThread的performLaunchActivity

    ActivityThread.class(api29)
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
    	// 最终会调用这个方法来创建window
        // 注意r.token参数
        activity.attach(appContext, this, getInstrumentation(), r.token,
            r.ident, app, r.intent, r.activityInfo, title, r.parent,
            r.embeddedID, r.lastNonConfigurationInstances, config,
            r.referrer, r.voiceInteractor, window, r.configCallback,
            r.assistToken);
        ...
    }
    

    这个方法调用了activity的attach方法来初始化window,同时我们看到参数里有了r.token这个参数,这个token最终会给到哪里,我们赶紧继续看下去:

    Activity.class(api29)
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        ...
    	// 创建window
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ...
    	// 创建windowManager
        // 注意token参数
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        mWindowManager = mWindow.getWindowManager();
        ...
    }
    

    attach方法里创建了PhoneWindow以及对应的WindowManager,再把创建的windowManager给到activity的mWindowManager属性。我们看到创建WindowManager的参数里有token,我们继续看下去:

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    

    这里利用应用服务的windowManager给Activity创建了WindowManager,同时把token保存在了PhoneWindow内。到这里我们知道Activity的PhoneWindow是拥有token的。那么Application呢?


    Application的创建具体流程可以看到这篇文章contentProvider启动流程,因为contentProvider是伴随着应用的启动而启动的。最终Application的启动会来到ActivityThread的handleBindApplication方法:

    /frameworks/base/core/java/android/app/ActivityThread.java
    private void handleBindApplication(AppBindData data) {
        ...
        // 获取到Application实例
        Application app;
        ...
        try {
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            ...
        }
        ...
    }
    

    这里的data.info是LoadedApk对象,我们深入这个方法看一下:

    LoadedApk.java(api29)
    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }
        ...
        Application app = null;
        ...
        try {
            java.lang.ClassLoader cl = getClassLoader();
            ...
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } 
        ...
        mActivityThread.mAllApplications.add(app);
        mApplication = app;
    
        if (instrumentation != null) {
            try {
                instrumentation.callApplicationOnCreate(app);
            } 
            ...
        }
        ...
        return app;
    }
    

    构建Application的逻辑也不复杂。首先判断Application如果存在则直接返回。后面通过Instrumentation来创建Application对象,最后再通过Instrumentation来回调Application的onCreate方法,我们分别看一下这两个方法:

    Instrumentation.java(api29)
    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = getFactory(context.getPackageName())
                .instantiateApplication(cl, className);
        app.attach(context);
        return app;
    }
    public void callApplicationOnCreate(Application app) {
        app.onCreate();
    }
    
    Application.java
    final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }
    

    我们可以看到,直到Application回调onCreate方法,整个过程没有涉及token的赋值。Activity是在attach方法中传入了token参数,而这里Application并没有,所以Application并没有token,也没有初始化PhoneWindow和WindowManager。那么Application的getSystemService返回的是什么呢?Application调用的是ContextImpl的getSystemService方法,而这个方法返回的是应用服务的windowManager,Application本身并没有创建自己的PhoneWindow和WindowManager,所以也没有给PhoneWindow赋值token的过程。

    因此,Activity拥有自己PhoneWindow以及WindowManager,同时它的PhoneWindow拥有token;而Application并没有自己的PhoneWindow,他返回的WindowManager是应用服务windowManager,并没有赋值token的过程

    那么到这里结论已经快要出来了,还差最后一步,我们回到赋值token的那个方法中:

    Window.class(api29)
    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            // 子窗口token获取逻辑
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
                    wp.token = decor.getWindowToken();
                }
            }
            ...
        } else {
            // 应用窗口token获取逻辑
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
            ...
        }
        ...
    }
    

    当我们使用Activity来添加dialog的时候,Activity本身是带有token的,Dialog是属于应用级窗口(至于为什么读者可以前往dialog的show方法中跟踪源码),他的window层级数是2,而Activity的界面是1,所以他会显示在Activity之上。而因为这里Dialog是应用级窗口,所以他最终就拿到了Activity的token。

    但是如果使用的是Application,因为它内部并没有token,那么这里获取到的token就是null,后面到WMS也就会抛出异常了。而这也就是为什么使用Activity可以弹出Dialog而Application不可以的原因。因为受到了token的限制。

    WMS是如何验证token的

    到这里我们已经知道。我们从WMS的token判断找到了token的类型以及token的载体:WindowManager.LayoutParams,然后我们再从dialog的创建流程追到了赋值token的时候会因为windowManager的不同而不同。因此我们再去查看了两者不同的windowManager,最终得到结论Activity的PhoneWindow拥有token,而Application使用的是应用级服务windowManager,并没有token

    那么此时还是会有疑问:

    • token到底是在什么时候被创建的?
    • WMS怎么知道我这个token是合法的?

    虽然到目前我们已经弄清原因,但是知识却少了一块,秉着探索知识的好奇心我们继续研究下去。

    我们从前面Activity的创建window过程知道token来自于r.token,这个r是ActivityRecord,是AMS启动Activity的时候传进来的Activity信息。那么要追踪这个token的创建就必须顺着这个r的传递路线一路回溯。同样这涉及到Activity的完整启动流程,我不会解释详细的调用栈情况,默认你清楚activity的启动流程,如果不清楚,可以先去阅读Activity的启动流程。首先看到这个ActivityRecord是在哪里被创建的:

    /frameworks/base/core/java/android/app/servertransaction/LaunchActivityItem.java/;
    public void execute(ClientTransactionHandler client, IBinder token,
            PendingTransactionActions pendingActions) {
        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
        ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
                mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
                mPendingResults, mPendingNewIntents, mIsForward,
                mProfilerInfo, client);
        // ClientTransactionHandler是ActivityThread实现的接口,具体逻辑回到ActivityThread
        client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
    }
    

    这样我们需要继续往前回溯,看看这个token是在哪里被获取的:

    /frameworks/base/core/java/android/app/servertransaction/TransactionExecutor.java
        
    public void execute(ClientTransaction transaction) {
        ...
        executeCallbacks(transaction);
        ...
    }
    public void executeCallbacks(ClientTransaction transaction) {
        ...
            final IBinder token = transaction.getActivityToken();
            item.execute(mTransactionHandler, token, mPendingActions);
        ...
    }
    

    可以看到我们的token在ClientTransaction对象获取到。ClientTransaction是AMS传来的一个事务,负责控制activity的启动,里面包含两个item,一个负责执行activity的create工作,一个负责activity的resume工作。那么这里我们就需要到ClientTransaction的创建过程一看究竟了。下面我们的逻辑就要进入系统进程了:

    ActivityStackSupervisor.class(api28)
    final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
        boolean andResume, boolean checkConfig) throws RemoteException {
        ...
        final ClientTransaction clientTransaction = ClientTransaction.obtain(app.thread,
                r.appToken);
        ...
    }
    

    这个方法创建了ClientTransaction,但是token并不是在这里被创建的,我们继续往上回溯(注意代码的api版本,不同版本的代码会不同):

    ActivityStarter.java(api28)
    private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            SafeActivityOptions options,
            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
            TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup) {
        ...
      
        //记录得到的activity信息
        ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
                callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
                resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
                mSupervisor, checkedOptions, sourceRecord);
       ...
    }
    

    我们一路回溯,终于看到了ActivityRecord的创建,我们进去构造方法中看看有没有token相关的构造:

    ActivityRecord.class(api28)
    ActivityRecord(... Intent _intent,...) {
        appToken = new Token(this, _intent);
        ...
    }
    
    static class Token extends IApplicationToken.Stub {
       ...
        Token(ActivityRecord activity, Intent intent) {
            weakActivity = new WeakReference<>(activity);
            name = intent.getComponent().flattenToShortString();
        }
        ...
    }
    

    可以看到确实这里进行了token创建。而这个token看接口就知道是个Binder对象,他持有ActivityRecord的弱引用,这样可以访问到activity的所有信息。到这里token的创建我们也找到了。那么WMS是怎么知道一个token是否合法呢?每个token创建后,会在后续发送到WMS ,WMS对token进行缓存,而后续对于应用发送来的token只需要在缓存拿出来匹配一下就知道是否合法了。那么WMS是怎么拿到token的?


    activity的启动流程后续会走到一个方法:startActivityLocked,这个方法在我前面的activity启动流程并没有讲到,因为它并不属于“主线”,但是他有一个非常重要的方法调用,如下:

    ActivityStack.class(api28)
    void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
            boolean newTask, boolean keepCurTransition, ActivityOptions options) {
        ...
        r.createWindowContainer();
        ...
    }
    

    这个方法就把token送到了WMS 那里,我们继续看下去:

    ActivityRecord.class(api28)
    void createWindowContainer() {
        ...
        // 注意参数有token,这个token就是之前初始化的token
        mWindowContainerController = new AppWindowContainerController(taskController, appToken,
                this, Integer.MAX_VALUE /* add on top */, info.screenOrientation, fullscreen,
                (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges,
                task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(),
                appInfo.targetSdkVersion, mRotationAnimationHint,
                ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L);
    	...
    }
    

    注意参数有token,这个token就是之前初始化的token,我们进入到他的构造方法看一下:

    AppWindowContainerController.class(api28)
    public AppWindowContainerController(TaskWindowContainerController taskController,
            IApplicationToken token, AppWindowContainerListener listener, int index,
            int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
            boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
            int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
            WindowManagerService service) {
        ...
        synchronized(mWindowMap) {
            AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
           ...
            atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
                    inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
                    requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
                    alwaysFocusable, this);
            ...
        }
    }
    

    还记得我们在一开始看WMS的时候他验证的是什么对象吗?WindowToken,而AppWindowToken是WindowToken的子类。那么我们继续追下去:

    AppWindowContainerController.class(api28)
    AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token,
            boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
            boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
            int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
            boolean alwaysFocusable, AppWindowContainerController controller) {
        return new AppWindowToken(service, token, voiceInteraction, dc,
                inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
                rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
                controller);
    }
    AppWindowToken(WindowManagerService service, IApplicationToken token, ...) {
        this(service, token, voiceInteraction, dc, fullscreen);
        ...
    }
    
    WindowToken.class
    WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
            DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) {
        token = _token;
        ...
        onDisplayChanged(dc);
    }
    

    createAppWindow方法调用了AppWindow的构造器,然后再调用了父类WindowToken的构造器,我们可以看到这里最终对token进行了缓存,并调用了一个方法,我们看看这个方法做了什么:

    WindowToken.class
    void onDisplayChanged(DisplayContent dc) {
        dc.reParentWindowToken(this);
    	...
    }
    
    DisplayContent.class(api28)
    void reParentWindowToken(WindowToken token) {
        addWindowToken(token.token, token);
    }
    private void addWindowToken(IBinder binder, WindowToken token) {
        ...
        mTokenMap.put(binder, token);
        ...
    }
    

    mTokenMap 是一个 HashMap<IBinder, WindowToken> 对象,这里就可以保存一开始初始化的token以及后来创建的windowToken两者的关系。这里的逻辑其实已经在WMS中了,所以这个也是保存在WMS中。AMS和WMS都是运行在系统服务进程,因而他们之间可以直接调用方法,不存在跨进程通信。WMS就可以根据IBinder对象拿到windowToken进行信息比对了。至于怎么比对,代码位置在一开始的时候已经有涉及到,读者可自行去查看源码,这里就不讲了。

    那么,到这里关于整个token的知识就全部走了一遍了,AMS怎么创建token,WMS怎么拿到token的流程也根据我们回溯的思路走了一遍。

    整体流程把握

    前面根据我们思考问题的思维走完了整个token流程,但是似乎还是有点乱,那么这一部分,就把前面讲的东西整理一下,对token的知识有一个整体上的感知,同时也当时前面内容的总结。先来看整体图:

    1. token在创建ActivityRecord的时候一起被创建,他是一个IBinder对象,实现了接口IApplicationToken。
    2. token创建后会发送到WMS,在WMS中封装成WindowToken,并存在一个HashMap<IBinder,WindowToken>。
    3. token会随着ActivityRecord被发送到本地进程,ActivityThread根据AMS的指令执行Activity启动逻辑。
    4. Activity启动的过程中会创建PhoneWindow和对应的WindowManager,同时把token存在PhoneWindow中。
    5. 通过Activity的WindowManager添加view/弹出dialog时会把PhoneWindow中的token放在窗口LayoutParams中。
    6. 通过viewRootImpl向WMS进行验证,WMS在LayoutParams拿到IBinder之后就可以在Map中获取WindowToken。
    7. 根据获取的结果就可以判断该token的合法情况。

    这就是整个token的运作流程了。而具体的源码和细节在上面已经解释完了,读者可自行选择重点部分再次阅读源码。

    从源码设计看token

    我在Context机制一文中讲到,不同的context拥有不同的职责,系统对不同的context限制了不同的权利,让在对应情景下的组件只能做对应的事情。其中最明显的限制就是UI操作。

    token看着是属于window机制的领域内容,其实是context的知识范畴。我们知道context一共有三种最终实现类:Activity、Application、Service,context是区分一个类是普通Java类还是android组件的关键。context拥有访问系统资源的权限,是各种组件访问系统的接口对象。但是,三种context,只有Activity允许有界面,而其他的两种是不能有界面的,也没必要有界面。为了防止开发者乱用context造成混乱,那么必须对context的权限进行限制,这也就是token存在的意义。拥有token的context可以创建界面、进行UI操作,而没有token的context如service、Application,是不允许添加view到屏幕上的(这里的view除了系统窗口)。

    为什么说这不属于window机制的知识范畴?从window机制中我们知道WMS控制每一个window,是通过viewRootImpl中的IWindowSession来进行通信的,token在这个过程中只充当了一个验证作用,且当PhoneWindow显示了DecorView之后,后续添加的View使用的token都是ViewRootImpl的IWindowSession对象。这表示当一个PhoneWindow可以显示界面后,那么对于后续其添加的view无需再次进行权限判断。因而,token真正限制的,是context是否可以显示界面,而不是针对window

    而我们了解完底层逻辑后,不是要去知道怎么绕过他的限制,动一些“大胆的想法”,而是要知道官方这么设计的目的。我们在开发的时候,也要针对不同职责的context来执行对应的事务,不要使用Application或Service来做UI操作

    总结

    文章采用思考问题的思路来表述,通过源码分析,讲解了关于token的创建、传递、验证等内容。同时,token在源码设计上的思想进行了总结。

    android体系中各种机制之间是互相联系,彼此连接构成一个完整的系统框架。token涉及到window机制和context机制,同时对activity的启动流程也要有一定的了解。阅读源码各种机制的源码,可以从多个维度来帮助我们对一个知识点的理解。同时阅读源码的过程中,不要局限在当前的模块内,思考不同机制之间的联系,系统为什么要这么设计,解决了什么问题,可以帮助我们从架构的角度去理解整个android源码设计。阅读源码切忌无目标乱看一波,要有明确的目标、验证什么问题,针对性寻找那一部分的源码,与问题无关的源码暂时忽略,不然会在源码的海洋里游着游着就溺亡了。

    全文到此,感谢你的阅读

    原创不易,觉得有帮助可以点赞收藏评论转发关注。
    笔者才疏学浅,有任何错误欢迎评论区或私信交流。
    如需转载请私信交流。

    另外欢迎光临笔者的个人博客:传送门

    展开全文
  • 认证码Token加密解密代码 若有用你下载便是
  • Web项目中经常会用token来进行用户的访问验证,那么在获得token之后,如果有很多地方需要根据token获得对应的用户信息,你会怎么获取? 本文给大家提供N种方式,对照一下,看看你的项目中所使用的方式属于哪个Level...

    Web项目中经常会用token来进行用户的访问验证,那么在获得token之后,如果有很多地方需要根据token获得对应的用户信息,你会怎么获取?

    本文给大家提供N种方式,对照一下,看看你的项目中所使用的方式属于哪个Level,是不是要赶快升级一下?

    关于token生成、认证部分的操作本文不会涉及,也就是默认token是经过合法性校验的,本文将重点放在之后进行的业务相关处理,即基于token获取用户信息的方式(部分方式需要基于SpringBoot)。

    Level1:手动获取

    通常token会放在header当中,最低级的获取方式就是直接从header中获取token,然后通过token转换获得userId,示例代码如下:

    @GetMapping("/level1")
    public Integer level1(HttpServletRequest request) {
        String token = request.getHeader("token");
        log.info("level1 获得的token为:{}", token);
        Integer userId = TokenUtil.getUserIdByToken(token);
        log.info("userId={}", userId);
        return userId;
    }
    

    这种方式最简单直观,还可以进一步封装,比如提供一个BaseController,封装公共的部分,本质是一样的,但又引入了继承关系。因此,通常适用于有少数地方使用的场景。如果有大量的地方使用,这样写比较麻烦,不推荐使用,也没什么技术含量。

    Level2:过滤器token转userId

    在上一种方案中,既然每一次调用都需要进行token和userId的转换,那就通过过滤器将这一转换过程统一处理。在过滤器中获得token,然后转换成userId,再把userId写回到header当中,使用时直接从header中拿userId即可。

    先定义过滤器,示例代码如下:

    @Slf4j
    @Component
    public class ArgumentFilter implements Filter {
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            String token = httpRequest.getHeader("token");
            Integer userId = TokenUtil.getUserIdByToken(token);
            log.info("filter获取用户Id={}", userId);
            HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(httpRequest) {
                @Override
                public String getHeader(String name) {
                    if ("userId".equals(name)) {
                        return userId + "";
                    }
                    return super.getHeader(name);
                }
            };
            filterChain.doFilter(requestWrapper, httpResponse);
        }
    }
    

    这里主要通过实现Filter接口的doFilter方法(JDK8可用实现需要的接口方法即可),在request中获得token之后,通过HttpServletRequestWrapper将转换之后的userId放置在header当中。

    SpringBoot项目中,需要对ArgumentFilter进行相应的配置,指定过滤的URL:

    @Configuration
    public class FilterConfig {
    
        @Resource
        private ArgumentFilter argumentFilter;
        
        @Bean
        public FilterRegistrationBean<Filter> registerAuthFilter() {
            FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
            registration.setFilter(argumentFilter);
            registration.addUrlPatterns("/level2");
            registration.setName("authFilter");
            // 值越小,Filter越靠前
            registration.setOrder(1);
            return registration;
        }
    }
    

    此时在Controller中使用如下:

    @GetMapping("/level2")
    public Integer level2(HttpServletRequest request) {
        Integer userId = Integer.parseInt(request.getHeader("userId"));
        log.info("userId={}", userId);
        return userId;
    }
    

    虽然这种方式已经进步了很多,但每次都要获得HttpServletRequest,然后再从其中获得userId,还是有一些不方便。能不能继续改进一下?那继续往下看。

    Level3:参数匹配

    上一种方式已经处理获得了userId,那么能不能做的更彻底一些,只需要在Controller方法上出现userId,就直接给它赋值呢?来看一下实现:

    @Slf4j
    @Component
    public class ArgumentParamFilter implements Filter {
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            String token = httpRequest.getHeader("token");
            Integer userId = TokenUtil.getUserIdByToken(token);
            log.info("filter获取用户Id={}", userId);
            HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(httpRequest) {
                @Override
                public String[] getParameterValues(String name) {
                    if ("userId".equals(name)) {
                        return new String[]{userId.toString()};
                    }
                    return super.getParameterValues(name);
                }
    
                @Override
                public Enumeration<String> getParameterNames() {
                    Set<String> paramNames = new LinkedHashSet<>();
                    paramNames.add("userId");
                    Enumeration<String> names = super.getParameterNames();
                    while (names.hasMoreElements()) {
                        paramNames.add(names.nextElement());
                    }
                    return Collections.enumeration(paramNames);
                }
            };
            filterChain.doFilter(requestWrapper, httpResponse);
        }
    }
    

    这里从header中获取到token,转换为userId,然后匹配方法的参数名称,如果是userId,那么就将转换之后的userId赋值给对应的参数。相关filter配置与上一个方法一样,不再贴代码,来看一下Controller中的使用:

    @GetMapping("/level3")
    public Integer level3(Integer userId) {
        log.info("userId={}", userId);
        return userId;
    }
    

    只需在Controller中的方法参数上定义userId便可直接赋值,看起来是不是方便很多。但很明显上面只支持get请求,如果是Post方法并且参数是通过body体(Json格式)传输,那么参数往往是一个实体对象,比如User。能否直接将userId注入到User实体当中呢?

    @Data
    public class User {
    
        private Integer userId;
    
        private String name;
    }
    

    要实现直接注入到User对象中,还需要进一步改造。在上面的filter中再添加上针对body体传输方式的处理,在HttpServletRequestWrapper中再实现getInputStream方法:

    @Override
    public ServletInputStream getInputStream() {
        byte[] requestBody;
        try {
            requestBody = StreamUtils.copyToByteArray(request.getInputStream());
            Map map = JsonUtils.toObject(Map.class, new String(requestBody));
            map.put("userId", userId);
            requestBody = JsonUtils.toJson(map).getBytes();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    
        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
        return new ServletInputStream() {
            @Override
            public int read() {
                return bais.read();
            }
    
            @Override
            public boolean isFinished() {
                return false;
            }
    
            @Override
            public boolean isReady() {
                return true;
            }
    
            @Override
            public void setReadListener(ReadListener readListener) {
    
            }
        };
    }
    

    先读取流中的(JSON格式)数据,然后将信息解析成Map,在Map中添加上userId,再转换成JSON格式,最后再创建一个流将其写出。对应的Controller实现如下:

    @PostMapping("/level3Post")
    public Integer level3Post(@RequestBody User user) {
        log.info("userId={}", user.getUserId());
        return user.getUserId();
    }
    

    通过postman等工具测试一下,就会发现User对象中已经被注入了对应值。

    至此,是不是就完美了?好像还有一些瑕疵。

    第一个:虽然按照约定定义userId参数即可,但容易误伤,比如某些业务有自身的userId,不小心命名重复了,会有被覆盖的风险。

    第二个:参数的名称只能是userId,且不能够灵活的定义其他名称。

    第三个:如果想返回更多信息,比如用户(User)的信息,处理就变得更加复杂。而且如果body体传递的参数比较复杂,解析成Map再封装转换有一定的风险和性能问题。

    那么,我们再进行改造升级一下,下面示例基于SpringBoot。

    Level4:方法参数解析器

    Spring提供了多种解析器Resolver,比如常用的统一处理异常的HandlerExceptionResolver。同时,还提供了用来处理方法参数的解析器HandlerMethodArgumentResolver。它包含2个方法:supportsParameter和resolveArgument。其中前者用来判断是否满足某个条件,当满足条件(返回true)则可进入resolveArgument方法进行具体处理操作。

    基于HandlerExceptionResolver,我们可以分以下部分来进行实现:

    • 自定义注解@CurrentUser,用于Controller方法上的User参数;
    • 自定义LoginUserHandlerMethodArgumentResolver,实现HandlerMethodArgumentResolver接口,通过supportsParameter检查符合条件的参数,通过resolveArgument方法来将token转换成User对象,并赋值给参数。
    • 注册HandlerMethodArgumentResolver到MVC当中。

    下面来看具体的实现,先定义注解@CurrentUser:

    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CurrentUser {
    }
    

    注解就是用来做标识用的,标识指定的参数需要进行处理。对于注解了@CurrentUser的参数是由自定义的LoginUserHandlerMethodArgumentResolver来进行判断处理的:

    @Component
    public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
        
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(CurrentUser.class) &&
                    parameter.getParameterType().isAssignableFrom(User.class);
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
                                      NativeWebRequest request, WebDataBinderFactory factory) {
            // header中获取用户token
            String token = request.getHeader("token");
            Integer userId = TokenUtil.getUserIdByToken(token);
            // TODO 根据userId获取User信息,这里省略,直接创建一个User对象。
            User user = new User();
            user.setName("Tom");
            user.setUserId(userId);
            return user;
        }
    }
    

    supportsParameter方法中通过两个条件来过滤参数,首先参数需要使用CurrentUser注解,同时参数的类型为User。当满足条件时返回true,进入resolveArgument进行处理。

    在resolveArgument中,从header中获取token,然后根据token获取对应User信息,这里可以注入UserService来获得更多的用户信息,然后将构造好的User对象返回。这样,后续就可以将返回的User绑定到Controller中的参数上。

    但此时自定义的Resolver并没有生效,还需要添加到MVC当中:

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Resource
        private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver;
    
        @Override
        public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
            argumentResolvers.add(loginUserHandlerMethodArgumentResolver);
        }
    
    }
    

    至此,便可以在Controller中使用该注解来获取用户信息了,具体使用如下:

    @GetMapping("/level4")
    public Integer level4(@CurrentUser User user) {
        log.info("userId={},username={}", user.getUserId(), user.getName());
        return user.getUserId();
    }
    

    上面介绍了直接注入一个User对象,如果你只需要userId,那么将User对象替换成Integer或Long类型即可。

    通过这种形式,使用起来更加方便了,当我们需要获取User信息时,只需在请求的参数中使用@CurrentUser User即可。不需要的地方也不会出现误操作的可能,具有了充分的灵活性和可拓展性。

    小结

    本文通过一个场景的业务场景,从最基础的实现一路演变到具有一定设计性的实现,涉及到了拦截器、过滤器、注解等一些列的知识点和实战经验。这正是我们在项目开发时中不断演进的过程。你的项目中使用的哪种方式?是不是需要升级或体验一下了?

    本文相关源码地址可关注公众号:程序新视界,回复“1004”获得。

    最后,个人的视频号也开通了,一分钟给大家讲一个干货知识点,一分钟给大家分享一个职场经验。欢迎关注。

    在这里插入图片描述

    程序新视界

    公众号“ 程序新视界”,一个让你软实力、硬技术同步提升的平台,提供海量资料

    微信公众号:程序新视界

    展开全文
  • Spring Security中使用token

    千次阅读 2020-09-12 09:14:34
    一旦生成新的token,所有旧token失效。 多点登录:同一个账号在同一时间可多次登录,每次登陆都会获得一个token。这些token的有效期是隔离的,不受新生成token的影响。 对于token,若要实现单点登录,则必须将所有...
  • 内容只为接口开发,配合前端和移动app调用使用,不包含html页面,基于springboot+oauth2.0+jwttoken鉴权(内有怎么使用redistoken和数据库token注释)+restful风格+阿里短信+阿里消息推送+车牌识别等。该项目为工作...
  • 同clientId多端登录下jwt+auth2+security使用RedisTokenStore刷新token后长token不可用:Invalid refresh token问题
  • 有个地方需要注意: 我这里刷新产生新的refreshToken时 旧的refreshToken并没有失效,如果不是特别敏感这点的话可以不计较,若是在意的话,那需要自己处理:比如用缓存记录失效的token每次token认证判断是否是失效的...
  • 关于token和refresh token

    千次阅读 2018-08-21 15:15:17
     refresh token 的意义 传统的认证方式一般采用cookie/session来实现,这是我们的出发点。 1.为什么选用token而不选用cookie/session? 本质上token和cookie/session都是字符串,然而token是自带加密算法和用户...
  • GitLab升级到后续的版本之后,由于Session API已经不再支持,通过用户名/密码方式获取Access Token的方式需要换一种方式,这篇文章介绍一下一种通过使用cookie获取的方式。
  • 这篇文章将详细讲解一道CSS注入题目,包括CSS注入、越权、csrf-token窃取及CSP绕过,同时总结XSS绕过知识,希望对您有所帮助。第一次参加CTF,还是学到了很多东西。人生路上,要珍惜好每一天与家人陪伴的日子。感谢...
  • oauth2 token刷新,token续期,access_token和refresh_token实效如何设置 token认证,生成的token 过一段时间就会失效(不要故意把时间设的很长,这样不安全,token变得毫无意义!),用户需要重新登录获取token。用户...
  • JWT之token机制与双token详解

    千次阅读 2021-01-13 21:12:38
    token机制 何为tokentoken即为令牌,是服务器生成的一串字符串,作为客户端向服务器进行请求的“通行证”。在客户端进行初次登陆后由服务器返回,之后的每次请求只需要携带token进行请求即可,而无需携带密码等...
  • Token登录认证详解

    千次阅读 多人点赞 2020-05-25 18:44:35
    Token 认证的来龙去脉 前后端分离使用 Token 登录解决方案 理解Cookie和Session机制 基于 Cookie/Session 的认证方案 Cookie Cookie的工作原理 由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户...
  • 微信公众平台开发-如何保证access_token长期有效

    千次下载 热门讨论 2014-05-06 00:09:34
    博客教程《微信公众平台开发教程第22篇-如何保证access_token长期有效》的配套代码,教程地址如下: http://blog.csdn.net/lyq8479/article/details/25076223
  • 配置Swagger带token的三种方式

    千次阅读 2021-05-28 21:35:45
    现在的项目基本上都是前后端分离,很多API的调用都需要用到token验证,本文就介绍怎么在swagger的header中自动添加token。 在每个接口上手动添加header package com.morris.swagger.web; import ...
  • Spring和Token整合详解

    千次阅读 2019-05-29 11:05:09
    Spring和Token整合详解 一、官方主页 Spring Security 二、概述 Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web ...
  • JWT产生和验证Token

    万次阅读 多人点赞 2019-07-31 19:15:33
    Token验证  最近了解下基于 Token 的身份验证,跟大伙分享下。很多大型网站也都在用,比如 Facebook,Twitter,Google+,Github 等等,比起传统的身份验证方法,Token 扩展性更强,也更安全点,非常适合用在 Web...
  • tp5 生成token 验证token 解密token

    千次阅读 2020-05-22 18:37:49
    生成Token /** * 创建 token * @param array $data 必填 自定义参数数组 * @param integer $exp_time 必填 token过期时间 单位:秒 例子:7200=2小时 * @param string $scopes 选填 token标识,请求接口的token ...
  • vue axios 刷新token 刷新jwt js刷新token http刷新token

    千次阅读 热门讨论 2019-04-23 17:27:58
    前言 在互联网的登陆系统中一般有session cookie 和 jwt token来进行验证用户是否登录的.下面来说一下关于 jwt的坑: 1.首先登录成功的返回,其中jwt的有效期为2小时,refreshJwt的有效期为30天如下: 2.保存jwt 和...
  • 接口安全-Token

    万次阅读 2020-06-20 17:14:28
    Token登录认证 chrisghb 1 2019.06.12 21:11:16 字数 3,039 阅读 11,549 参考文章: Token 认证的来龙去脉 前后端分离使用 Token 登录解决方案 理解Cookie和Session机制 基于 Cookie/Session 的认证方案 Cookie ...
  • 七牛Android上传token的生成和上传

    热门讨论 2015-04-01 14:38:47
    七牛的说明文档中有让我疑惑的地方,自己找的资源把它跑通了,主要是上传token的生成部分。七牛里面没有Android部分的实例代码,写完之后共享给大家。避免再走弯路。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 658,639
精华内容 263,455
关键字:

token