hello,我是卷卷毛,我又来啦

咱们书接上回,上一节我们讨论了一种基于token验证方式的登录方案,文章在这里:

  • 【JavaWeb】实现基于Token方式的用户登录

上节的内容,简单些说,就是介绍了一种实现用户登录的方式。

服务端在校验用户的账号密码信息无误后,为用户生成一串token作为口令返回给用户。之后,按照约定,用户在访问后端时将token添加在请求头中,发送给后端。后端根据头部中的token信息校验用户的登录状况。

那么这一节,承接上回,我们要解决这一登录方案的前后端交互问题,焦点在于token的获取,存储,传递和移除,别看这个问题简单,实现起来却是困难重重,博主就在这里疯狂蹚雷(爆哭),于是才有了这篇博客,也是帮看到这篇博客的小伙伴避避雷。

(碎碎念:虽说token登录的方式可以避开跨域请求中Cookie传递的若干问题,但是在实践中却总是无法绕开这个话题,也许跨域问题是前后端分离开发必攻下的一块高地吧)


对于这个问题,有的小伙伴一听:

哈?这么简单,在请求中添加一个请求头不就好了?


不过看起来确实是很简单,但是别着急嗷,不妨先来回顾一下我们后台的流程:

上一篇文章中,编写的后台有三个接口以及一个拦截器:

  • /user/login用户登录接口,访问的时候需要传递usernamepassword,只认识账号123456789,密码123456。登录成功会返回用户的token

  • /user/logout用户登出接口,访问的时候回检测用户是否处于登录状态,如果用户处于登录状态则将用户登出。

  • /user/loginStatus用户登录状态,访问会返回此时用户的登录状态

  • AuthorizationFilter登录拦截器,会检测请求头Authorization字段中的token以判断用户的登录状态。

复习完后,让我们写个简答的前端,试着使用123456789,123456登录获取token:

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>token传递</title></head><body><button id="login">点击登录</button><button id="logout">点击登出</button><button id="login-status">登录状态</button></body>
</html>


接着给每个按钮编写对应的点击事件,访问三个接口。这三个接口就包含着对token的重要基本操作

  • 获取
  • 存储
  • 携带
  • 移除

用户登录及token存储

在发送token之前我们要先拿到token呐,所以先为登录按钮编写点击事件,像/user/login发送一个登录请求:

$('#login').click(()=>{$.ajax({url:'http://localhost/user/login',data:{username:'123456789',password:'123456'},type:'POST',success:(res)=>{alert(res);}})
})

点击发送,可以看到后台的提示信息:

但是前台并没有弹出窗口,感觉这个请求不太对劲,打开控制台一看:

第一个跨域问题:访问控制源

原来是遇到了跨域的问题,提示我们需要在response中设置头Control-Allow-Origin。他表示跨域请求允许的访问源。

看到这个问题,不要惊慌,应为

这个问题经常是出现在小伙伴们前后端分离开发的第一只拦路虎。
这个看似前端的问题,其实是后端的,需要在后端给相应头中加上若干跨域相关字段

由于后面我们还要频繁的设置响应头的信息。我们不妨在后端设置一个跨域拦截器CROSFilter,专门负责对跨域请求进行处理:

@Order(1)//拦截器排序,跨域拦截最先进行拦截
@WebFilter(filterName="CROSFilter",urlPatterns= {"*.action","*"})
public class CROSFilter implements Filter{public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest)request;HttpServletResponse rep= (HttpServletResponse)response;System.out.println("跨域拦截器拦截到请求:" + req.getRequestURI());//设置允许访问域,这里直接取访问域,表示允许来自这个域的请求rep.setHeader("Access-Control-Allow-Origin", req.getHeader("origin"));//执行拦截链中的下一环chain.doFilter(request, response);}
}

(这里在响应中允许了访问控制源为访问源,相当于谁来都允许。有时候也使用*来表示通配,但是与之后会设置的另一些头会产生冲突,所以建议使用当前这个。)

此时再点击按钮进行访问,后端提示信息:

前端提示:

这表示我们克服了跨域请求的第一关,成功得到了token。这时可以选择将返回的token储存在本地,可以以键值对的形式存放在sessionStorage中,需要携带token时也可以从中获取,主要使用到的函数用法如下:

sessionStorage.setItem(key,value);//添加一个元素
sessionStorage.getItem(key);//获取一个元素
sessionStorage.removeItem(key);//移出一对映射

然后我们用sessionStorage改造一下登录方法,将获取到的token存放其中:

$('#login').click(()=>{$.ajax({url:'http://localhost/user/login',data:{username:'123456789',password:'123456'},type:'POST',success:(res)=>{// alert(res);sessionStorage.setItem('token',res);}})
})

(这里是默认成功的,直接将token存储了,实际使用的时候记得检测一下返回的状态呀,登录成功了再存储token)

之后,再次发送请求,在开发者工具中查看获取的token:

这样就完成了token的获取和存储了

用户登录信息及token携带

用户登录成功后,前端获取了登录token并将其存储起来,那么接下来的访问就要在请求头中携带上这个token。

当然,用户登录其实也是可以携带token的,这可以帮助后台辨别用户是否存在重复登录的问题。不过咱们为了演示方便,就先考虑这种情况了。

jquery的ajax请求中,添加请求头的方式为:

$.ajax({...headers:{...},...
})

按照这个格式,在发送任何形式的请求之前,我们需要将sessionStorage中的token取出来,放到请求头Authorization字段中,那么这段代码应该是:

$.ajax({...headers:{Athorization:sessionStorage.getItem('token')},...
})

现在我们将它添加到获取登录信息的请求说明中:

$('#login-status').click(()=>{$.ajax({url:'http://localhost/user/loginStatus',type:'GET',headers:{Authorization:sessionStorage.getItem('token')},success:(res)=>{alert(res);}})
})

好的,当我们满怀欣喜点击登录状态按钮,期待看到已登录的提示时。。

不出所料,意外又发生了:

后端提示,没有拦截到我们传递的token:

前端提示,诶嘛一大堆看起来就头疼的提示:

奇怪的现象又出现了!!

跨域请求添加自定义头

当我们打算查看刚才发出的请求是否存在问题时


纳尼?竟然发送了两个请求?

那么那个多发出去的preflight类型的请求是啥呢?

其实他是跨域请求的一种预检机制,即在发送真正请求的时候先发送一个预检请求探查一下服务端的行为。

触发这个机制的事件是当请求不是简单请求的时候。

非简单请求的判别如下:

  • 请求类型不是POST,GET,HEAD其中的一种
  • 请求头包含了Accept,Accept-Language,Content-Language,Last-Event-ID之外的字段
  • Content-Type头取了application/x-www-form-urlencoded,multipart/form-data,text/plain之外的值

所以,刚才出现预检请求是因为包含了自定义字段。

那么,返回的错误信息代表了什么意思呢?

它的意思是我们自定义的请求头Authorization不被允许。解决这个问题,我们需要在响应头中添加Access-Control-Allow-Headers并在其中指定允许使用额外的头。这个工作还是需要后端来实现,我们在刚才的跨域拦截其中加上一条rep.setHeader("Access-Control-Allow-Headers","Authorization");

@Order(1)
@WebFilter(filterName="CROSFilter",urlPatterns= {"*.action","*"})
public class CROSFilter implements Filter{public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest)request;HttpServletResponse rep= (HttpServletResponse)response;System.out.println("跨域拦截器拦截到请求:" + req.getRequestURI());rep.setHeader("Access-Control-Allow-Origin", req.getHeader("origin"));//访问控制允许头:Authorizationrep.setHeader("Access-Control-Allow-Headers","Authorization");chain.doFilter(request, response);}
}

如果想加入更多的头部,可以使用逗号分隔开。这样就可以携带自定义头啦~

信心满满,添加完成后,重启后端,再用前端进行访问,后端提示:

前端提示:

这。。。

查看访问请求,发现其中并没有携带记录着sessionid的Cookie:

这个问题形成的原因就是前端在进行跨域访问后端时没有携带cookie,也就没有携带首次访问获取的sessionid,使得服务端误以为前端是首次访问,为其创建了新的session。

也就说,token早就丢失了!!

跨域请求携带cookie信息

为了让我们的请求能够携带cookie信息,我们必须在前后端同时允许跨域请求对cookie携带的允许。

在前端,需要加上:xhrFields:{withCredentials:true}即:

$('#login').click(()=>{$.ajax({url:'http://localhost/user/login',data:{username:'123456789',password:'123456'},xhrFields:{withCredentials:true},type:'POST',success:(res)=>{// alert(res);sessionStorage.setItem('token',res);}})
})$('#login-status').click(()=>{$.ajax({url:'http://localhost/user/loginStatus',type:'GET',headers:{Authorization:sessionStorage.getItem('token')},xhrFields:{withCredentials:true},success:(res)=>{alert(res);}})
})

在后端,需要加上:rep.setHeader("Access-Control-Allow-Credentials", "true");,同时记得处理一下预检请求OPTIONS,即:

@Order(1)
@WebFilter(filterName="CROSFilter",urlPatterns= {"*.action","*"})
public class CROSFilter implements Filter{public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest)request;HttpServletResponse rep= (HttpServletResponse)response;System.out.println("跨域拦截器拦截到请求:" + req.getRequestURI());rep.setHeader("Access-Control-Allow-Origin", req.getHeader("origin"));//允许访问源rep.setHeader("Access-Control-Allow-Credentials", "true");//允许携带cookierep.setHeader("Access-Control-Allow-Headers","Authorization");//允许头if("OPTIONS".equals(req.getMethod())) {//处理预检请求    System.out.println("preflight 请求");return;}System.out.println("session id : " + req.getSession().getId());//打印sessionidchain.doFilter(request, response);}
}

完事后,我们再点击按钮进行访问:


另外,如果前端加入了xhrCredentials,后端也都配置好了,但仍然不管用。

首先检查一下各处的拼写,尤其是前端的。

然后如果使用的是Chrome浏览器的话,一定要修改一下配置,它会默认禁止携带cookie!!!!!

  • google打开访问 chrome://flags/#same-site-by-default-cookies 设置disabled,然后重启

不过还是有必要提及的是,token存放在session中并不是一个很好的选择。相比来说,我们更倾向于将其存放在数据库或者中间件像Redis中。

但是本文为了方便演示,只好将其存放在session中,但是很显然,这带来了非常多的问题。

所以,真的,博主蹚的雷小伙伴们一定不要在去猜一遍呀。

用户登出及token清除

最后,我们排除了万难,已经可以完整的传递token了。

临终一脚就是完成用户的登出,相信有了之前的铺垫,用户登出功能将会变得比较容易实现。

直接编写前端代码:

$('#logout').click(()=>{$.ajax({url:'http://localhost/user/logout',xhrFields:{withCredentials:true},type:'GET',headers:{Authorization:sessionStorage.getItem('token')},success:(res)=>{alert(res);}})
})

点击执行登出:


最终代码

前端:

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>token传递</title></head><body><button id="login">点击登录</button><button id="logout">点击登出</button><button id="login-status">登录状态</button></body>
</html>
<script src="jquery-3.5.1.js"></script>
$('#login').click(()=>{$.ajax({url:'http://localhost/user/login',data:{username:'123456789',password:'123456'},xhrFields:{withCredentials:true},type:'POST',success:(res)=>{// alert(res);sessionStorage.setItem('token',res);}})
})$('#logout').click(()=>{$.ajax({url:'http://localhost/user/logout',xhrFields:{withCredentials:true},type:'GET',headers:{Authorization:sessionStorage.getItem('token')},success:(res)=>{alert(res);}})
})$('#login-status').click(()=>{$.ajax({url:'http://localhost/user/loginStatus',type:'GET',headers:{Authorization:sessionStorage.getItem('token')},xhrFields:{withCredentials:true},success:(res)=>{alert(res);}})
})

后端跨域请求拦截:

@Order(1)
@WebFilter(filterName="CROSFilter",urlPatterns= {"*.action","*"})
public class CROSFilter implements Filter{public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest)request;HttpServletResponse rep= (HttpServletResponse)response;System.out.println("跨域拦截器拦截到请求:" + req.getRequestURI());rep.setHeader("Access-Control-Allow-Origin", req.getHeader("origin"));rep.setHeader("Access-Control-Allow-Credentials", "true");rep.setHeader("Access-Control-Allow-Headers","Authorization");if("OPTIONS".equals(req.getMethod())) {System.out.println("preflight 请求");return;}System.out.println("session id : " + req.getSession().getId());chain.doFilter(request, response);}
}

后言

这篇文章知识量不少,主要是博主最近若干天的踩雷史,用以记录,希望能够帮助到更多的小伙伴。

限于篇幅,很多问题在文中并没有特别展开,因此后续还会继续整理和跨域相关的知识点。

下面的文章给博主帮了不少忙,也推荐给你们:

  • 解决谷歌浏览器Cookie跨域问题this Set-Cookie didn
  • 前台请求不带cookie的问题解决方案大汇总
  • ajax请求携带cookie和自定义请求头header(跨域和同域)
  • Cookie和Session、SessionID的那些事儿
  • Ajax请求携带Cookie
  • Access-Control-Allow- 设置 跨域资源共享 CORS 详解
  • HTTP之CROS、预检

系列文章

这个系列是博主有关Javaweb的实践记录,会分析记录一些博主在实践Javaweb过程中遇到的成果、问题、困难、解决方案等。

  • 【JavaWeb】如何实现支持回复功能的留言板
  • 【JavaWeb】实现基于Token方式的用户登录

欢迎关注博主,一起学习交流~

【JavaWeb】(血泪踩雷史...)Token登录前后端交互及跨域问题相关推荐

  1. 【前端44_前后端交互_跨域】前端解决:JSONP、后端解决:CORS 、后端代理

    文章目录 跨域 什么是跨域 前端解决: JSONP 实现原理 步骤 前端:创建标签,拼接传递参数 后端:接收值,返回值 封装 Ajax 代码 在封装的 Ajax 中添加 JSONP 需求 思路 练习: ...

  2. 解决java前后端分离端口跨域问题

    解决java前后端分离端口跨域问题 参考文章: (1)解决java前后端分离端口跨域问题 (2)https://www.cnblogs.com/mollie-x/p/10449686.html 备忘一 ...

  3. Vue flask前后端分离解决跨域

    Vue flask前后端分离解决跨域 安装axios 在项目目录下输入:npm install axios--save-dev 配置axios 在main.js中引入axios import axio ...

  4. Vue+Flask前后端分离 Vue3跨域配置

    Vue+Flask前后端分离 Vue3跨域配置 前端端口号为8080 后端端口号为5000 问题描述 问题解决 接口路径映射 前端端口号为8080 后端端口号为5000 后端端口API 代码片. @a ...

  5. cors 前后端分离跨域问题_SpringBoot 实现前后端分离的跨域访问(CORS)

    序言:跨域资源共享向来都是热门的需求,使用CORS可以帮助我们快速实现跨域访问,只需在服务端进行授权即可,无需在前端添加额外设置,比传统的JSONP跨域更安全和便捷. 一.基本介绍 简单来说,CORS ...

  6. 06-若依前后端分离项目跨域问题解决方式

    什么是跨域 跨域就是前后端分离项目前端无法把session等信息传递给后端服务器.跨域源自浏览器同源策略.同源策略是一种约定,同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互. ...

  7. Spring Boot2.x-13前后端分离的跨域问题解决方法之Nginx

    文章目录 概述 浏览器同源策略 后台搭建 pom.xml interceptor 配置 Controller 启动测试 浏览器和session 后端工程发布到服务器上 问题复现 通过Nginx反向代理 ...

  8. 前后端分离与跨域的解决方案(CORS的原理)

    前后端分离 前后端分离的好处 最大的好处就是前端JS可以做很大部分的数据处理工作,对服务器的压力减小到最小. 后台错误不会直接反映到前台,错误接秒较为友好. 由于后台是很难去探知前台页面的分布情况,而 ...

  9. 前后端分离项目跨域问题及解决方案

    目录 1.什么是跨域 2.前后端分离项目中的跨域问题 3.方法一:SpringBoot后端进行处理 4.方法二:在Vue前端进行处理 5.总结 1.什么是跨域 请求同域资源: 在域名 (或 ip 地址 ...

  10. 前后端分离的跨域解决方案

    声明: 在以往的开发中,前后端分离也不是像现在这么热门,所谓的前端工程师也只不过是写好静态页面由Java工程师或者php工程师嵌入到页面中进行开发,这或许加重了这些工程师的工作量,而且在样式调试上由纯 ...

最新文章

  1. 智能车竞赛技术报告 | 智能车视觉 - 西安邮电大学 - AI小布丁
  2. 【MATLAB】数据类型 ( 矩阵 | 随机数函数 | 生成矩阵 )
  3. [待]-optee的native_intr_handler中断处理流程
  4. linux系统之我的选择
  5. 学习笔记:验证对称二叉树
  6. java jdbc标签jsp_jsp+servlet+javabean+jdbc实现增删改查和分页功能 案例源码
  7. Andriod之使用极光推送自定义消息打造个性的消息推送效果
  8. 使用NPO依赖的一些类库文件介绍
  9. lightOJ 1132 Summing up Powers(矩阵 二分)
  10. 试戴系统完全开放—zoomla!逐浪cms在后4.6时代的又一个亮点
  11. android 弹窗圆角,Android开发笔记: Android最简单的圆角提示框
  12. 对 数组[i].index=i的理解
  13. C++初学必练基础题【第四期】
  14. 【矢量分析】工科矢量分析公式大全
  15. 泛泰A860(高通8064 cpu 1080p) 刷4.4专用中文recovery TWRP2.7.1.2版(三版通刷)
  16. 深度强化学习算法的未来——样本效率研究
  17. 三相对称电力系统中的正序、负序、零序分量
  18. spring spel 获取环境变量
  19. 游戏辅助制作核心--植物大战僵尸逆向之阳光生产加速实现满天星(四)1
  20. 第二台计算机,世界上第二台电脑的名称是什么

热门文章

  1. python数据框列命名_python-按列名称处理pandas数据框值
  2. 笔记 神经网络、BP算法推导
  3. 图像分类网络总结回顾(上)
  4. C语言 输出正三角形图形
  5. 100kW以上 中高频感应加热电源 双DSP数字式IGBT控制板
  6. 全站即时通讯技术资料分类
  7. 理解稀疏编码sparse coding
  8. 奔图打印机显示未连接_奔图打印机常见故障及解决方法
  9. python毕业设计作品基于django框架 教室图书馆座位预约系统毕设成品(7)中期检查报告
  10. [导入]WAP开发教程