师兄:说说你理解的 Token

前言

在学 Javaweb 的时候,我们就一直被强调 javaweb 中有四大作用域:pageContext域request域session域application域

  • pageContext域里的变量只能在当前页面使用。
  • request域中对象的有效范围是当前的请求范围。
  • session域中对象的有效范围是当前的会话范围。
  • application域中对象的有效范围是整个应用活跃的方位。

了解基本概念后,综上我们可以得出若要进行网络通信,我们一般都要使用到 requestsession。由于 request 的作用域只是在请求范围内有效,每次请求后数据就会自动销毁。但有时候我们有想要存储一些能够存储久的数据,例如登录信息用户信息等,request域是做不到的。

服务端的 session 和 客户端(浏览器)的 cookie 的性质很相似,例如他们都能存储一定期限的数据。如果说Cookie机制是客户身上的“通行证”,那么Session机制就是多个“通行证”组成的“客户明细表”。从理论上讲,这两个内存间是联系不到一块的。但是由于这两者相似的性质, java 的 jspspringthymeleaf模板就将他们关联了起来。让人们产生 sessioncookie 是一致的。jsp 为例,jsp 是一个特殊的 servlet 程序,虽然我们在里面编写的是 html 语句,但他实际上是会通过 servlet 的一些手段从而转变为前端页面。而 springthymeleaf 模板也用了这样的方式。因为 session 能够存储时间久的数据,故我们能够用 session 来存储 登录信息用户信息等。

image

session/cookie 的弊端:

  • cookie 不是很安全,别人可以分析存放在本地的 cookie 并进行 cookie 欺骗。
  • 单个 cookie 在客户端的限制是 3K,就是说一个站点在客户端存放的 cookie 不能超过 3k
  • session 会在一定时间内保存在服务器上。当访问量怎多,会占用服务器的性能。
  • 如果是在服务器集群下,或者跨域的服务导向架构,就要求 session 域中的数据共享,即每台服务器都能够读取 session。这个时候我们就只能将 session 数据持久化,这是一个非常糟糕的办法。

更加重大的问题

如果用户在浏览器的设置里禁用了 cookie;或者你的网站是使用前后分离的技术来实现。根本就无法简单的使用 session 就能解决了。一是由于浏览器中没了 cookie,你的数据无法存储了,二是前后端的联系被撕裂了,单独设置服务端的 session 无法同步到 浏览器的 cookie 中。此时我们就需要用另一个思想:自己在前后端都分别开辟一个空间来存储用户信息。

token 就是这样的一种思想。其流程为:

  1. 客户端使用用户名跟密码请求登录。
  2. 服务端收到请求,去验证用户名与密码。
  3. 验证成功后,服务端再生成一个独一无二的字符串(Token 令牌),并且将这个令牌保存到服务器的缓存中,再把这个 令牌发送给客户端。
  4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 LocalStorage 里。
  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
  6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据。

image

token 带来的好处

此外,token 除了能解决这个现实问题,也带来了许多的好处。

  1. 无状态,可扩展:在客户端存储的 tokens 是无状态的,并且能够被扩展。基于这种无状态和不存储 session 信息,负载均衡器能够将用户信息从一个服务器传到其他服务器上。
  2. 安全:请求中发送 token 而不再发送 cookie,能够防止 CSRF(跨站请求伪造)。即使在客户端使用 cookie 存储 tokencookie 也仅仅是一个存储机制而不是用于认证。并且 token 是有时效的,一段时间后用户需要重新验证。
  3. 可扩展性:能够与其他程序共享数据。
  4. 多平台跨域:每次都是携带绑定自己信息的令牌访问服务器,当在服务集群环境时,不是只能访问某一台指定的服务器,而是通过从缓存服务器中拿出数据,并更具负载均衡器来选择处理的服务器。

Token 的实现

准备阶段

  1. 使用 Mysql 数据库
  2. 开启 Redis 缓存
  3. 使用 Springboot 框架

代码段

1. 登录
<form id="login_form" class="login_form">
    用户名:<input type="text" name="username">
    <br>
    密码:<input type="password" name="password">
    <br>
    <input type="button" value="提交" onclick="login()">
</form>
<script type="application/javascript">
    function login(){
        $.ajax({
            type:"post",    // post 请求
            dataType:"json",    // 返回 json 格式
            url:"http://localhost:8080/login",     // 请求路径
            data:$("#login_form").serialize(),     // 表单序列化
            success: function(result){
                console.log(result)                // 打印结果
                if(result.code == 200){
                    // 存储 token
                    localStorage.token = result.data;     // 将 token 存到 localStorage 里
                    location.href = "/index";           // 跳转到首页
                }
            },fail:function(err){
                console.log(err)
            }
        });
    }
</script>
@Resource
RedisTemplate<String, Object> redisTemplate;       // redis 缓存模板

@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public Result login(String username, String password){
    System.out.println(username);
    System.out.println(password);
    User user = userService.getUser(username, password);
    if(user != null){
        // 登录成功,生成 token 令牌
        String token = UUID.randomUUID() + "";     // 使用 uuid 生成唯一 key,以此作为 token 令牌
        redisTemplate.opsForValue().set(token, user, Duration.ofMinutes(30L));    // 将 token 放到 redis 缓存中,存半个小时
        return new Result(200, "请求成功", token);
    }
    return new Result(400, "请求失败", null);
}
2. 资源访问请求
function requestToken(){
    $.ajax({
        type:"get",                           // 请求方式
        dataType: "json",                     // 返回格式
        url:"http://localhost:8080/getUser",   // 请求地址
        headers : {
            "token" : localStorage.token        // 设置 header 头,将 token 放到请求头中更加安全
        },
        success:function(result){
            console.log(result)
        }
    })
}
@RequestMapping("/getUser")
@ResponseBody
public Result getUserOfLogin(HttpServletRequest request){
    String token = request.getHeader("token");   // 从请求头中获取 token
    Object user = redisTemplate.opsForValue().get(token);   // 在 redis 缓存中寻找 token 对应的信息

    if(user != null){
        // 获取成功,封装返回值
        return new Result(200, "请求成功", user);
    }
    return new Result(400, "找不到用户信息", null);    // 可能没存在这个 token 或者 token 以过期
}
3. 注销
function logout(){
    $.ajax({
        type:"get",
        url:"http://localhost:8080/logout",
        dataType:"json",
        headers:{
            "token":localStorage.token
        },
        success:function(result){
            localStorage.removeItem("token");   // 在浏览器端清除 token
        },fail : function(err){
            console.log(err)
        }
    })
}
@RequestMapping("/logout")
@ResponseBody
public Result logout(HttpServletRequest request){
    String token = request.getHeader("token");     // 从请求头中获取 token
    Boolean delete = redisTemplate.delete(token);   // 根据 token 删除 redis 缓存里对应的信息
    if(delete){
        return new Result(200, "注销成功", null);
    }else{
        return new Result(400, "注销失败", null);

    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慢慢编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值