用JWT机制实现Token身份验证-程序员宅基地

技术标签: Java后端之路  框架  

用JWT机制实现Token身份验证

1.JWT(JSON WEB TOKEN)

由于HTTP的无状态性,我们无法判断是哪个客户端在请求接口。这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下回这个客户端再发送请求时候,还得再验证一下。

解决的方法就是,当用户请求登录的时候,如果没有问题,我们在服务端生成一条记录,这个记录里可以说明一下登录的用户是谁,然后把这条记录的 ID 号发送给客户端,客户端收到以后把这个 ID 号存储在 Cookie 里,下次这个用户再向服务端发送请求的时候,可以带着这个 Cookie ,这样服务端会验证一个这个 Cookie 里的信息,看看能不能在服务端这里找到对应的记录,如果可以,说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。

上面说的就是 Session,我们需要在服务端存储为登录的用户生成的 Session ,这些 Session 可能会存储在内存,磁盘,或者数据库里。我们可能需要在服务端定期的去清理过期的 Session 。

但是有些情况下session并不那么好用,一个是session会占用后端内存资源,还有如果用户cookie中的sessionId被窃取,很容易就可以获取用户的私有数据.这时我们可以用token来解决这些问题

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:

  • 1.客户端使用用户名跟密码请求登录
  • 2.服务端收到请求,去验证用户名与密码
  • 3.验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
  • 4.客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
  • 5.客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
  • 6.服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

实际上,JWT的Token就是 JSON数据转换后的一串字符串 JWT主要包括三个部分:header(头部)payload(数据)signature(签名)

  • header
    header表示头部数据
{
"alg": "HS256",
"typ": "JWT"
}

alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT

  • payload
    Payload 里面是 Token 的具体内容
{
 "id": 1,
 "username": "wintershii",
 "passwd": "",
 "admin": true
}
  • signature
    JWT 的最后一部分是 Signature ,这部分内容有三个部分,先是用 Base64 编码的 header.payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret (私匙),这个相当于是一个密码,这个密码秘密地存储在服务端。

最终我们将这三部分内容用"."分开,并用Base64编码

2.使用Token进行身份验证

作为一个后端为SSM框架的项目,首先现在pom.xml中引入依赖

<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.5.0</version>
        </dependency>

然后编写Token工具类

public class TokenUtil {

    //token有效时间
    private static final long EXPIRE_TIME = 15 * 60 * 1000;
   
    private static final String TOKEN_SECRET = "thefirsttoken777";//这里填写私匙

    /**
     * 颁发签名
     * @return
     */
    public static String sign(Integer id, String phone) {
        try {
            // 设置过期时间
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            // 私钥和加密算法
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            // 设置头部信息
            Map<String, Object> header = new HashMap<>(2);
            header.put("Type", "Jwt");
            header.put("alg", "HS256");
            // 返回token字符串
            return JWT.create()
                    .withHeader(header)
                    .withClaim("id",id.toString())//在这里可以放自己需要放的数据
                    .withClaim("phone",phone)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 检验token是否是正确格式(是否过期,是否可用)
     * @param token
     * @return
     */
    public static boolean verify(String token){
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception e){
            return false;
        }
    }


    /**
     * 从token中获取相关信息
     * @param token
     * @return
     */
    public static String getInfo(String token ,String type){
        try {
            DecodedJWT jwt = JWT.decode(token);
            if (type.equals("phone")) {
                return jwt.getClaim("phone").asString();
            }
            if (type.equals("id")) {
                return jwt.getClaim("id").asString();
            }

        } catch (JWTDecodeException e){
            e.printStackTrace();
            return null;
        }
        return null;
    }


}

根据上面的TokenUtil,在用户登录后,我们可以签发给该用户Token,并将其添加到响应头(或者在响应体里加也可以),然后下次客户端请求我们的时候就要带着这个Token来请求接口

/**
     * 用户登录
     * @return
     */
    @RequestMapping(value = "/login.do",method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<User> login(String phone, String password, HttpServletResponse response) {
        ServerResponse<User> serverResponse = userService.login(phone,password);
        if (serverResponse.isSuccess()) {
            Integer id = serverResponse.getData().getId();
            //生成Token
            String token = TokenUtil.sign(id,phone);
            if (token != null) {
            	//添加到响应头中
                response.addHeader("token",token);
                return serverResponse;
            }
        }
        return ServerResponse.createByErrorMessage("登录失败");
    }

那么如何判断请求是否带有Token呢?我们当然可以在每个Controller中都进行判断,但是这也太麻烦了,这时我们想到了过滤器,也就是Spring MVC拦截器.我们把除了注册,登录这些不需要用户登录的接口的其他接口用拦截器拦截,并检测其请求头中是否带有Token信息

/**
 * token拦截器
 */
@Component
public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        response.setCharacterEncoding("utf-8");
        String token = request.getHeader("token");
        if (token != null){
            boolean result = TokenUtil.verify(token);
            if(result){
                System.out.println("通过拦截器");
                return true;
            }
        }
        System.out.println("认证失败");
        return false;
    }
}<!--配置拦截器-->
    <mvc:interceptors>
    <!--<bean class="com.ma.interceptor.CustomeInterceptor" />-->
    <!--拦截器1-->
    <mvc:interceptor>
        <!--配置拦截器的作用路径-->
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/user/register.do"/>
        <mvc:exclude-mapping path="/user/login.do"/>
        <!--定义在<mvc:interceptor>下面的表示匹配指定路径的请求才进行拦截-->
        <bean class="com.winter.interceptor.TokenInterceptor"/>
    </mvc:interceptor>
    </mvc:interceptors>

下面是spring mvc配置文件中的拦截器相关注解

<!--配置拦截器-->
    <mvc:interceptors>
    <!--<bean class="com.ma.interceptor.CustomeInterceptor" />-->
    <!--拦截器1-->
    <mvc:interceptor>
        <!--配置拦截器的作用路径-->
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/user/register.do"/>
        <mvc:exclude-mapping path="/user/login.do"/>
        <!--定义在<mvc:interceptor>下面的表示匹配指定路径的请求才进行拦截-->
        <bean class="com.winter.interceptor.TokenInterceptor"/>
    </mvc:interceptor>
    </mvc:interceptors>

这样我们就可以使用Token进行会话了,但当我们需要进行某些敏感操作(删除,修改信息)时,我们需要在Controller中解析Token中的数据,并将其与要操作的用户数据进行对比,如果不是同一用户的数据,那就拒绝操作,返回失败.

最后,因为JWT如果被窃取,服务器也没有办法去识别,所以为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wintershii/article/details/88373260

智能推荐

java基础面试题_gatway过滤器 sql优化_神愛世人-的博客-程序员宅基地

1. 类和对象的区别答:类是一个独立的程序单位,他应该有个类名,它的内部包含了属性和服务两个主要部分对象其实就是构成系统的一个基本单位 ,一个对象由一组属性和一组服务组成的说白了,类就像一台机器,而对象就是类身上的零件2. 接口答:接口只能具有抽象方法,一个类可以实现多个接口,当类实现了接口以后就必须实现接口中的所有的抽象方法3. JAVA中类的六种关系1.继承关系 2.聚合关系 3.依赖关系 4.组合关系 5.实现关系 6.关联关系4. 为什么要用spring答:基于POJO的轻量级_gatway过滤器 sql优化

自定义AlertDialog(二)-程序员宅基地

先来看主页面布局main_activity.xml里面只有一个button(添加点击事件,弹出加载框)再看MainActivitypackage com.example.loadingdialog;import android.app.Activity;import android.os.Bundle;import android.view.View;public cl

JMF -程序员宅基地

第八章 理解JMF RTP APIJMF可以通过定义在javax.media.rtp,javax.media.rtp.event及javax.media.rtp.rtcp包中的API来回放和传输RTP流。JMF可通过标准JMF插件机制扩展到支持额外RTP相关的格式以及动态载荷。注意:JMF兼容实现不是必须支持javax.media.rtp,javax.media.rtp.event,

主流IOC框架测验(.NET)-程序员宅基地

上一篇中,我简单介绍了下Autofac的使用,有人希望能有个性能上的测试,考虑到有那么多的IOC框架,而主流的有:Castle Windsor、微软企业库中的Unity、Spring.NET、StructureMap、Ninject等等。本篇文章主要针对这些IOC框架编写测试程序。Autofac下载地址:http://code.google.com/p/autofac/Ca...

PTA乙级1011-1019_java pta 乙级1020-程序员宅基地

1020月饼那个题有一个测试点通不过,等通过了再补上1011 A+B 和 C (15 分)给定区间 [−2​31​​,2​31​​] 内的 3 个整数 A、B 和 C,请判断 A+B 是否大于 C。输入格式:输入第 1 行给出正整数 T (≤10),是测试用例的个数。随后给出 T 组测试用例,每组占一行,顺序给出 A、B 和 C。整数间以空格分隔。输出格式:对每组测试用例,..._java pta 乙级1020

latex双栏模板中使用 通栏的公式或者图_latex通栏公式-程序员宅基地

latex双栏模板中使用 通栏的公式或者图通栏的图在原来插图的环境中的 figure后加*;具体如下\begin{figure*}[ht] \label{fig1} \centering \includegraphics[width = 3in]{Fig1.pdf} \caption{The recognition accuracy of our model under different spread parameter when testing on (a) the Oxford-III_latex通栏公式

随便推点

Palm J2ME串行通讯程序编写调试 -程序员宅基地

Palm J2ME串行通讯程序编写调试 作者:李鲁群 来源:赛迪网 Palm是3Com公司的产品,其操作系统PalmOS是一种32位的嵌入式操作系统。Palm硬件提供了标准串行通讯接口(RS232接口)和红外线传输接口。利用它可以方便地与其它外部设备通讯、传输数据。目前3Com公司与Sun公司积极合作,已经提供了非常完善的嵌入式Java(J2

JavaSript模块规范 - AMD规范与CMD规范介绍_javasripte 是 cmd amd-程序员宅基地

JavaSript模块化 在了解AMD,CMD规范前,还是需要先来简单地了解下什么是模块化,模块化开发? 模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问题进行系统性的分解以之处理。模块化是一种处理复杂系统分解为代码结构更合理,可维护性更高的可管理的模块的方式。可以想象一个巨大的系统代码,被整合优化分割成逻辑性很强的模块时,对于软件是一种..._javasripte 是 cmd amd

Android Activity以Dialog的方式打开_android dialogactivity-程序员宅基地

全屏样式的Activity1、AndroidManifest.xml 文件中,申明 activity 的主题使用自定义样式:@style/share_activity_styles&lt;activity android:name="com.ecej.ehome.ui.AppointmentTimeActivity" android:screenOrientation="por..._android dialogactivity

vue--安装less依赖报错_ヅLucky_triplex的博客-程序员宅基地

首先,在样式中,我们定义 lang=“less”<style lang="less" scoped>.login_container {background-color: #2b4b6b;}</style>需要安装依赖 即less-loader 和 less但是在用ui图形化界面安装依赖时,会提示报错falied to compile 报错这个错误是因为安装的less-loader 和 less的版本太高我们需要将下载的less-loader 和 less_安装less依赖

C++实现简易图书管理系统-程序员宅基地

C++实现简易图书管理系统运行效果图程序介绍程序框架(H图)程序代码功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入运行效果图启动界面登录界面超级管理员界面你好! 这是你第一次使用 Markdown编辑器 所展示

矩阵论 - 8 - 求解Ax=b:可解性和解的结构-程序员宅基地

求解Ax=b:可解性和解的结构可解的条件 Solvability conditions on bQ:给定 \(A=\begin{bmatrix}1 & 2 & 2 & 2\\2 & 4 & 6 & 8\\3 & 6 & 8 & 10\\\end{bmatrix}\),求\(Ax=b\)的解?方程 \(Ax..._找一个2x4和2x1矩阵的例子,是ax=b的解集是s