Cookie Session Token讲解及用法

我们知道,起初的互联网是无状态的,人们只是使用互联网进行信息的查询,互联网也无需记住访问人的相关信息。但随着处理有状态的应用场景(例如登录吗,购物车等)的出现,人们需要互联网记住用户。

cookie

首先就出现了cookie,用户每次请求都携带cookie数据进行访问,客户端将cookie明文存储在HTTP响应中,每次客户端向服务器发送请求时,都会自动携带存储在本地的 Cookie,以便服务器识别和验证用户身份。

使用方法:

正常第一次登录来说,用户输入账号与密码,我们先验证账户是否存在,再验证密码是否存在,若是都存在并对上了,则设置cookie,因为cookie明文存储,所以使用最好进行加密(下面的例子并没有进行加密)。

设置cookie:

首先new一个cookie

再设置生命周期

再用HttpServletResponse.addCookie(cookie)进行添加Cookie

Cookie Session Token讲解及用法_第1张图片

 获取cookie:

在HttpServletRequest中 request.getCookies()可以获取cookies们遍历它们,判断key中的数据是否存在我们设定的数据,若存在则查询数据库。

Cookie Session Token讲解及用法_第2张图片

 注销cookie:

用户登出的话,获取cookie,再设置0秒生命周期,再写回浏览器,就算注销了cookie

Cookie Session Token讲解及用法_第3张图片

 cookie跨域问题

在前后端分离项目中,cookie会出现跨域问题,导致前端只能接收cookie,并不能保存cookie(也就是在浏览器set-cookie中能看到值,但浏览器里并没有保存)

出现set-cookie有值,但cookie里面没有

需要在前端请求接口添加 withCredentials: true表示接收cookie(在axios.create()里添加)

后端则需要加上跨域配置器(仅供参考,当时我试的时候加了前端的就好了,后端的根据不同情况来定)

Cookie Session Token讲解及用法_第4张图片

Session

 我们刚刚说到,cookie用明文方式存储在浏览器会特别不安全,所以出现了session,对象信息存储在服务器中。

 当用户首次访问网站时,服务器会为该用户创建一个唯一的会话标识符(通常称为会话ID),并将其以 cookie 的形式发送给客户端,通常称为“会话Cookie”。客户端浏览器将这个会话Cookie保存下来,并在随后的每个请求中将其发送回服务器。服务器通过会话ID来识别客户端,从而将客户端的请求关联到正确的会话数据。会话数据可以包含任意类型的信息,如用户的登录状态、用户设置、购物车内容等。

用法:

使用HttpSevletRequest request.getSession获取当前请求的Session对象(这个session是服务器端实际存储回话信息的地方。),之后setAttrubute来进行存放数据,getAttribute来进行获取数据

Cookie Session Token讲解及用法_第5张图片

session跨域问题 

暂时没遇到特别的设置,一般设置了后端的CORS就可以了

可能会混淆的:

从代码上看,response.addcookie与session.setAttribute都是把信息放在客户端的,那为什么说session是存储在服务端的呢

session的存储,说的是会话数据本身(会话中的信息)是存储在服务器端的。

总结cookie与session

cookie:

  • 存储位置:加密后的用户名(newusername)作为一个Cookie存储在客户端(浏览器)。
  • 运作过程:
    • 用户登录成功后,服务器将加密后的用户名(newusername)作为一个Cookie发送给客户端(浏览器)。
    • 客户端(浏览器)会将这个Cookie保存下来,在后续的请求中携带这个Cookie。
    • 服务器通过Cookie中存储的加密后的用户名(newusername)来验证用户身份

session:

存储位置:用户信息(例如用户名、用户ID等)存储在服务器端的Session对象中。

运作过程:

  • 客户端发送第一次请求到服务器。
  • 服务器在接收到请求后,为该客户端创建一个Session对象,并分配一个唯一的Session ID。
  • 服务器将Session ID以Cookie的形式发送给客户端,客户端保存下来。
  • 客户端后续的每次请求都会携带这个保存在Cookie中的Session ID。
  • 服务器根据Session ID找到对应的Session对象,从中获取和更新数据,实现会话状态的保持。

随着前后端分离的技术流行,越来越需要一个东西来满足前后端交流,而不像cookie与session那样由后端主导。之后提出了JSON数据,进而提出后端把需要的上下文状态打包为一个token发给前端,前端在拿到后,在需要时(例如某个功能需要验证权限才能使用)携带token与请求发送给后端,后端对信息进行解包。

Token

Token是一种用于身份认证和授权的令牌,它在Web应用程序和API中被广泛使用。通常,Token是一个字符串,由服务器生成并发送给客户端

为什么常存在redis数据库:token并不需要一直生效,而且设置了有效期能更好保证安全性,而存入redis不会长期占用主数据库的资源,也可以更好地控制其生命周期。(后面为了加强token的可靠性,一般用户信息会加密进token里,出现了JWT,也就脱离了redis的依赖)

在后续的请求中由客户端携带在请求头或请求参数中。Token的主要目的是验证用户身份,并授予用户访问特定资源的权限。

服务器将生成的Token发送给客户端,通常通过HTTP响应的头部或响应体的一部分。(放在客户端的通常都放在相应那边)

在使用Token进行身份验证时,客户端需要将Token放在HTTP头的“Authorization”字段中,或者放在请求的Cookie中,以便服务器识别和验证用户的身份。

使用方法:

后端:

Cookie Session Token讲解及用法_第6张图片

 Redis也要对JSON格式数据进行配置

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);

        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        //Hash也采用这种序列方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }

 对于前端而言,需要前端进行存储并携带,这里使用pinia对token进行获取并存储

import {defineStore} from "pinia";
import {computed, ref} from "vue";
import {ElMessage} from "element-plus";

export  const useTokenStore = defineStore('mytoken',()=>{
    //把返回的token拿出来并转化为对象
    const tokenJson = ref("")
    // computed 相当于 getter(属性处理方法)
    const token = computed(()=>{
        try {
            //要么在返回值获取到,要么从浏览器本地缓存中获取,要么为空
            return JSON.parse(tokenJson.value || window.localStorage.getItem("TokenInfo") || "{}")
        }catch (err)
        {
           ElMessage.error("json字符串格式不正确,转化失败")
           //若格式错误,返回空
            window.localStorage.setItem("TokenInfo","")
            throw err
        }

    })

    //function 相当于actions
    function saveToken(data){
        tokenJson.value = data
        window.localStorage.setItem("TokenInfo",data);
    }
    //向外暴露
   return {token, saveToken}
})
//在注册的vue页面中,就可以
import {useTokenStore} from "@/store/Mytoken";
const store = useTokenStore()
//进行保存
const login = ()=>{
 api.Nlogin(loginForm).then(res=>{
   //判断登录成功后.....
    
   store.saveToken(res.data.data)

})
 
}

使用,首先了解前端请求拦截器,就是前端在发送请求之前,拦截进行处理的

这样我们每次使用时就可以先获取token再进行请求,避免每次写请求都需要写获取token

//这个是个人写的获取token的方法,它返回值其中一个就是token
import {useTokenStore} from "@/store/Mytoken";
//请求拦截器   request:指个人写的axios.create({})出来的请求 
//config:代表请求的信息
request.interceptors.request.use((config)=>{
    //首先判断请求里是否有headers,如果没有则创建(虽然好像显得有点多余)
    if (!config.headers)
    {
        config.headers= {}
    }
    const store = useTokenStore() //获取token的方法
    //把相应的token添加到请求头中
    config.headers.Authorization = store.token.token  //store.token会获取到token集合
    //.token则获取到叫做token的token,一般我们根据不同的情况设置不同token代表权限
    return config //一定要return
})

退出登录时需要清空token,就是把保存的值设空

useTokenStore().saveToken('')

后端也要删除redis里的数据

String authorization = request.getHeader("Authorization");

redisTemplate.delete(authorization);

我们意识到这样做会有token在redis过期,但服务器仍然保存这个过期的token,所以需要在返回信息那里加上过期时间,以及用于获取新的token,有效期的数据

方法一:在浏览器把有效时间加上线程本地时间,之后每次规定时间内用本地时间检查是否过期,若过期则刷新token

Cookie Session Token讲解及用法_第7张图片

 方式二:设置个token报错码 ,前端遇到这个报错码就知道token出问题了,就去重新申请token,因为前端是从响应那里获取报错码的,所以我们需要设置响应拦截器

 而上面的方法都是基于,有一个refreshtoken,带着token用户的唯一标识符,并且存储时间比token长

为什么大多数使用方法二,而不是设置长时间的token,或者定期检查。

因为长时间的token也无法避免用户在浏览时,token过期直接被踢去登录页,用户体验会很不好

而定期检查会浪费资源。

在请求提示token过期后,前端自动识别并重新获取token,再自动续上用户的请求。这样虽然多存储了refreshtoken,但对用户的体验是很好的,用户也无法察觉到。

所以使用方法二

先设置一个响应式拦截器

request.interceptors.response.use(
    (response)=> response,

    async (error) => {
    if (error.response.status === 401)
    {   //刷新token
       const  data  =  await refresh()
       if (data.data.status ===200)
       {
           //保存新token
           useTokenStore().saveToken(data.data.data)
           //重新请求token,并且把结果返回
           return request(error.config)
       }
       else {
           //如果失败,则跳转到登录页
           ElMessage.error("访问间隔时间过久,需要重新登录")
           router.push("/")
           return
       }

    }
    return Promise.reject(error)
})

但这样之后,我们意识到,若是一个页面多个请求都需要刷新token,我们进行异步请求就很有可能重复刷新refreshToken

那如何只进行一次refresh呢

这里在请求那里做文章

就是设置一个值,如果请求正在进行中,则设为true,让其他晚来的被拦下

//刷新token,promise表示异步操作结果对象,
// 当我们发送异步请求时,返回的就是Promise对象,这里用来拓宽请求的操作
// (本来直接return回去,现在new一个接住它处理完我们事情后扔出去)
let promiseRT = Promise
let isRefreshing = false

export const refresh = ()=>{
    if (isRefreshing)
    {
        return promiseRT
    }
    isRefreshing  = true
    promiseRT = request({
        method:'POST',
        url:'/user/refresh_token',
        params: {
            refreshtoken: useTokenStore().token?.refreshtoken
        },
    }).finally(()=>{
        isRefreshing = false
    })
    return promiseRT
}

 在后端,需要在token失效时返回特定错误码,这就需要设置请求状态码

response.setStatus(401);

token可能会混淆的:

要是我前面的cookie也是加密存储的,那是否就算是token

并不是,因为token除了用户名,也有其他信息及签名,签名可以监测是否被修改。

Cookie是每次请求都会携带,而token是需要用到的时候,由服务器主动添加到请求头中

大小来说,cookie一般限制4kb(不同浏览器不同),而token不受浏览器限制

存储的数据而言,cookie只能存储字符串,而token可以存储任意形式的数据

本文基于作者自身的学习总结。如有错误,恳请指出。 如果对您有帮助的话,请给我点个赞吧。作者在后面也会分享文章,要是感兴趣也可以给我点个关注。

你可能感兴趣的:(java,spring,spring,boot,javascript,vue.js)