菜鸟笔记
提升您的技术认知

token验证的方法-ag真人游戏

统一token处理

自定义注解的方法及使用

排除token校验注解类

为不需要校验 token 的方法定义注解
@documented //标记注解
@target(elementtype.method) //指定作用在方法上 对方法拦截
@retention(retentionpolicy.runtime) //作用域 在运行时有效

noauthorization.java

import java.lang.annotation.*;
/**
 * 包含该注解的方法 不需要校验token  直接放行
 * @author 陌路
 * @date 2022-03-19
 * @apinote token统一处理
 */
@documented//标记注解
@target(elementtype.method) //指定作用在方法上 对方法拦截
@retention(retentionpolicy.runtime) //作用域 在运行时有效
public @interface noauthorization {
  
    /** ---------@定义注解---------
     *  元注解
     *    @target({method,type}) 表示这个注解可以用用在类/接口上,还可以用在方法上
     *    @retention(retentionpolicy.runtime) 表示这是一个运行时注解,即运行起来之后,
     *	  才获取注解中的相关信息,而不像基本注解如@override 那种。
     *    @inherited 表示这个注解可以被子类继承
     *    @documented 表示当执行javadoc的时候,本注解会生成相关文档(标记)
     *
     *  自定义注解的使用:
     *  该注解定义好之后,将该注解加在方法上@noauthorization即可,
     *  若注解中存在属性需要赋值,则直接可以在注解中赋值即可,
     *	eg:public @interface zjobj{
     *		string name();
     *		integer sex();
     *	}
     *  eg: @zjobj(属性="值") -> @zjobj(name="张三",sex=1)
     *
     *  也可这样定义注解,效果都是一样的
     *  @target(value = { elementtype.annotation_type })
     *  @retention(retentionpolicy.runtime)
     *  @inherited
     *  @documented
     *
     * ---------@解析注解@使用注解---------
     *  * 若注解中有属性,则可以直接通过反射来获取属性值
     *  * 相关配置信息本来是以属性的方式存放的,现在改为了以注解的方式
     *  *
     *  * @解析注解: 通过反射,获取这个类上的注解对象
     *  * zjobj zjobj = class.foraname(zjobj.class);
     *  * 拿到注解对象之后,通过方法,获取各个注解元素的值:
     *  * string name = zjobj.name();
     *  * integer sex = zjobj.sex ();
     */
}

本地线程缓存类

封装获取当前登录人数据信息类

tokeninterceptor 将获取到的user数据信息添加到 本地线程userthreadlocal中去
tokeninterceptor类中实现了handlerinterceptor拦截器,对请求进行了拦截
并对token进行了校验,token有效就会把user数据信息存储到userthreadlocal当前类中
通过调用该类的get()方法时,即可获取到user的数据信息

userthreadlocal.java

/**
 * 获取当前登录人信息
 * @author 陌路
 * @date 2022-03-19
 * @apinote 封装当前登录用户信息
 */
public class userthreadlocal {
  
    private static final threadlocal local = new threadlocal();
    /**
     * `tokeninterceptor`类中实现了`handlerinterceptor`拦截器,对请求进行了拦截
     * 并对token进行了校验,token有效就会把`user`数据信息存储到当前类中
     * 通过调用该类的`get()`方法时,即可获取到`user`的数据信息
     */
    //构造方法私有化,不运行外部实例化该对象
    private userthreadlocal() {
  
    }
    public static void set(user user) {
  
        local.set(user);
    }
    public static user get() {
  
        return local.get();
    }
}

token校验类

定义方法的请求拦截器,判断用户请求的方法是否需要校验token信息,prehandle 前置拦截

此拦截器是在执行用户请求的方法前拦截到的,该拦截器结束之前,不会执行用户请求的方法

当该拦截器执行完成后,根据拦截器返回结果(true|false)判断是否继续执行用户的请求

prehandle 返回 false 时,用户请求将不再继续执行,为 true 时校验通过才会继续执行

思路:
首先判断用户请求的方法中是否包含了@noauthorization注解 若包含了该注解 则放行
如果不包含该注解,说明需要对token进行校验,继续执行下面的校验代码
校验:首先判断请求头中是否包含 "authorization"请求头数据 不包含则结束校验 不予访问
若包含,则需要解析token数据拿到user对象,不为空就把当前user对象存储到userthreadlocal线程中去并返回true验证成功

tokeninterceptor.java

import org.apache.commons.lang3.stringutils;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.component;
import org.springframework.web.method.handlermethod;
import org.springframework.web.servlet.handlerinterceptor;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
/**
 * 请求拦截器 统一token校验
 * @author 陌路
 * @date 2022-03-19
 * @apinote 统一token校验
 */
@component
public class tokeninterceptor implements handlerinterceptor {
  
    @autowired
    private userservice userservice;
    @override
    public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler)
        throws exception {
  
        /** 思路
         * 首先判断请求的方法中是否包含了@noauthorization注解 若包含了此注解 则不作处理
         * 包含该@noauthorization注解 表示不需要做token的验证 直接return true 放行即可
         * 如果不包含该注解,说明需要对token进行校验,则继续执行下面的代码
         * 校验:首先判断请求头中是否包含 "authorization" 请求头数据 不包含则结束校验 不予访问
         * 若包含 则需要解析token数据 拿到 user 对象,判断user对象是否为null
         * 为null 说明不存在,直接返回false,不予访问,重新登录
         * 不为空 则需要把当前user对象存储到 userthreadlocal 线程中去 并返回true 验证成功
         */
        // 如果 handler 是 handlermethod 类型,则把 handler(object类型)转为handlermethod类型
        if (handler instanceof handlermethod) {
  
            handlermethod handlermethod = (handlermethod) handler;
            // 获取将要访问的方法名,根据方法名获取方法上的 noauthorization 注解,判断该注解是否存在
            noauthorization noauthorization = handlermethod
                			.getmethod()
                			.getannotation(noauthorization.class);
            //若方法中存在该 noauthorization 注解,则表示无需校验 返回true
            if (noauthorization != null) {
  
                return true;
            }
        }
        //获取到请求头中的头信息(token)
        string token = request.getheader("authorization");
        //判断token是否存在
        if (stringutils.isnotempty(token)) {
  
            //存在则根据token解析user数据
            user user = this.userservice.queryuserbytoken(token);
            //判断解析的user数据是否为null
            if (null != user) {
  
                //不为空 则将user信息存储到线程中
                userthreadlocal.set(user);
                return true;
            }
        }
        //若请求头中不存在authorization 直接返回false 提示状态码401无权限
        response.setstatus(401);
        return false;
    }
}

service解析token实现类

解析token数据,把token数据解析为user对象

解析完成后重置缓存时长,将缓存时间重新增加到一个小时

/**
  * 解析token,获取user数据信息
  * 对token进行检测,如果token存在,则解析出user数据信息
  * 如果token不存在,则return null
  * 除注册和发送验证码外不需要检测token外,其他功能均需要检测token
  */
@override
public user queryuserbytoken(string token) {
  
    try {
  
        // 生成rediskey获取当前登陆人信息
        string redistokenkey = "token_"   token;
        // 获取缓存数据
        string cachedata = this.redistemplate.opsforvalue().get(redistokenkey);
        if (stringutils.isnotempty(cachedata)) {
  
            // 刷新时间
            this.redistemplate.expire(redistokenkey, 1, timeunit.hours);
            //反序列化,将json数据转化为user对象返回
            return mapper.readvalue(cachedata, user.class);
        }    
    } catch (exception e) {
  
        e.printstacktrace();
    }
    return null;
}

如果是分布式系统,需要远程调用sso服务验证token的,可以加上下面的代码

编码格式和超时时间配置类(远程调用支持类)

配置 resttemplate 模板,设置服务远程调用过程中的编码格式

添加消息数据的字符集格式、连接超时时间和数据获取超时时间

resttemplateconfig.java

/**
 * resttemplate模板配置类
 * @author 陌路
 * @date 2022-03-19
 * @apinote 设置模板的字符集、链接超时时间和获取超时时间
 */
@configuration
public class resttemplateconfig {
  
    @bean
    public resttemplate resttemplate(clienthttprequestfactory factory) {
  
        resttemplate resttemplate = new resttemplate(factory);
        // 支持中文编码,getmessageconverters消息转换器,将字符集设置为 utf-8
        resttemplate.getmessageconverters()
            .set(1, new stringhttpmessageconverter(charset.forname("utf-8")));
        return resttemplate;
    }
    @bean
    public clienthttprequestfactory simpleclienthttprequestfactory() {
  
        simpleclienthttprequestfactory factory = new simpleclienthttprequestfactory();
        // 设置数据获取的超时时间,单位为ms
        factory.setreadtimeout(5000);
        // 设置连接超时时间,单位为ms
        factory.setconnecttimeout(5000);
        return factory;
    }
}

远程调用service类

远程服务调用,验证和获取token数据(调用sso单点登录中接口)

url:url为sso单点登录服务器的ip地址和端口加协议的全名地址

eg:http://47.101.xxx.xxx:80 本地:eg:http://127.0.0.1:80

该方法涉及了服务的远程调用过程,需要使用 resttemplate 中的 .getforobject();

把上面的service解析token的方法改为下面这段代码

/**
  * 解析token
  * 调用sso接口进行解析token
  * @param token
  * @return
  */
public user queryuserbytoken(string token) {
  
    try {
  
        // 返回一串json字符串,url为sso单点登录服务器的ip地址和端口eg:http://127.0.0.1:80
        string url = "http://192.168.10.188:18080";
        string jsondata = this.resttemplate.getforobject(url   "/user/{token}", string.class, token);
        // 如果查询到就返回user对象 查询不到就返回null
        if (stringutils.isnotempty(jsondata)) {
  
            // mapper.readvalue方法将json字符串转化为对象 并返回
            return mapper.readvalue(jsondata, user.class);
        }
    } catch (exception e) {
  
        e.printstacktrace();
    }
    return null;
}

controller控制器接口

远程服务调用,被调用的接口(sso单点登录中的控制器接口)

远程服务调用此接口需要使用全名地址:
协议://ip地址:端口号 http://ip:port (https://ip:port)

下面的controller作为sso中被远程调用的测试接口(请根据实际接口调用)
/**
  * 验证token
  * @param token
  * @return
  */
@getmapping("{token}")
public user queryuserbytoken(@pathvariable("token") string token) {
  
    //根据token把查询到的结果返回
    return this.userservice.queryuserbytoken(token);
}
网站地图