【JavaWeb】(血泪踩雷史...)Token登录前后端交互及跨域问题
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了【JavaWeb】(血泪踩雷史...)Token登录前后端交互及跨域问题,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含11929字,纯文字阅读大概需要18分钟。
内容图文
![【JavaWeb】(血泪踩雷史...)Token登录前后端交互及跨域问题](/upload/InfoBanner/zyjiaocheng/593/777a19b3f2344f59a4df5f0e15295f39.jpg)
hello,我是卷卷毛,我又来啦
咱们书接上回,上一节我们讨论了一种基于token验证方式的登录方案,文章在这里:
上节的内容,简单些说,就是介绍了一种实现用户登录的方式。
服务端在校验用户的账号密码信息无误后,为用户生成一串token作为口令返回给用户。之后,按照约定,用户在访问后端时将token添加在请求头中,发送给后端。后端根据头部中的token信息校验用户的登录状况。
那么这一节,承接上回,我们要解决这一登录方案的前后端交互问题,焦点在于token的获取,存储,传递和移除,别看这个问题简单,实现起来却是困难重重,博主就在这里疯狂蹚雷(爆哭),于是才有了这篇博客,也是帮看到这篇博客的小伙伴避避雷。
(碎碎念:虽说token登录的方式可以避开跨域请求中Cookie传递的若干问题,但是在实践中却总是无法绕开这个话题,也许跨域问题是前后端分离开发必攻下的一块高地吧)
对于这个问题,有的小伙伴一听:
哈?这么简单,在请求中添加一个请求头不就好了?
不过看起来确实是很简单,但是别着急嗷,不妨先来回顾一下我们后台的流程:
上一篇文章中,编写的后台有三个接口以及一个拦截器:
-
/user/login
用户登录接口,访问的时候需要传递username
和password
,只认识账号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"));
//访问控制允许头:Authorization
rep.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");//允许携带cookie
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());//打印sessionid
chain.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】(血泪踩雷史...)Token登录前后端交互及跨域问题全部内容,希望文章能够帮你解决【JavaWeb】(血泪踩雷史...)Token登录前后端交互及跨域问题所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。