完善登录的细节
引入
上节课学习了Ajax,Axios,跨域,Json等东西,学会了,前端页面的登录,但是页面还不完善,且用户登录界面只需通过URL便可以登录,无需登录,这显然不合理,我们这节课来解决一下
1. 显示账号
登录成功后,在前端保存用户信息,并且在main界面中显示用户账号
sessionStorage.setItem("username",resp.data.data.account);会话级别,浏览器关闭即清除
localStorage.setItem("key","value");//长久保存,除非手动清除
这里通过利用浏览器本地的一个会话级别的存储空间,它有一个特点,存储数据,浏览器关闭时自动删除,但是这里仍有缺陷,前端数据并不可靠,用户可能在浏览器的存储空间中篡改数据。
2. 判断登录
来到main组件时,需要在前端判断用户是否登录,并且在后台管理中的许多组件中都需要判断用户是否已经登录,自己判断非常麻烦,在路由中提出了一个叫路由导航守卫的功能,每次发生组件路由时,都会触发一个方法,通过将以下代码配置到index.js中解决
rout.beforeEach((to,from,next)=>{
if(to.path=='/login'){//如果用户访问的登录页,直接放行
return next();
}else{
//这里获取本地会话空间中的usrname判断值是否为空,空则登录
var token = sessionStorage.getItem("username");
if(token==null){
return next("/login");
}else{
next();
}
}
})
3. web会话跟踪技术
在web前端与后端的交互过程中,后端如何知道是哪个用户在此进行操作?为什么后端不知道是哪个用户请求的?
答:因为http请求是无状态的(请求–响应的模式),请求中没有能识别对方身份的标识,可以认为每次请求是独立的。
那么我们在实际开发中就需要解决这个问题,每次向后端发送请求时,需要让后端知道是哪个用户发送的,这个实现的功能称为web会话跟踪技术。
在现在的会话跟踪中, 基于token的会话跟踪技术 在之前还有一种基于HttpSession的会话技术
目标
1.是在登录成功后,在后端生成一个token(令牌)的字符串(可以携带用户信息,加密),
JWT–>json web token是生成token的一种方式
jwt生成token,这种方法优点:
- 简洁
- 可以携带用户信息
- 可以加密
- 不需要在服务器端存储,节省空间
token中信息包含几个部分
- 第一段字符串由base64(可逆)加密算法得到
- 第二段由payload信息通过base64加密算法得到,一般放不敏感信息
- 将第一段和第二段密文拼接起来,对拼接起来的密文和自定义的盐处理,对加密后的密文在做base64加密
如何生成
- 导入jwt组件的jar
- 导入JwtUtil类 包含生成token,验证token,解析token中用户信息部分
2.将此字符串响应到浏览器(前端),并存储起来
sessionStorage.setItem("token",resp.data.data.token);
这里Token就解决了前端篡改数据的问题,如果你的存储空间数据违法,后端会抛出异常,不会认可,篡改也就毫无意义。
3.然后将此token每次与后端交互时,都放在请求头中,
这时一个测试方法,执行一次便向后端发送一次token
test(){//token已经添加在请求头中
var token = sessionStorage.getItem("token");
this.$http.get("login?token="+token).then((resp)=>{
})
}
我们需要每发送一次请求就发送一次token的方法,而这个方法还需要每次调用十分麻烦
上有政策,下有对策,这里有一个Axios拦截器,每向后端发送一次请求,都会在请求头携带一个token中
//Axios请求拦截
axios.interceptors.request.use(config =>{
//为请求头添加一个自定义请求头,只需定义一次即可
config.headers.token = window.sessionStorage.getItem('token');
return config;
})
4.后端接收到token后,会对token进行验证,是否符合规则,符合继续执行,不符合直接响应验证失败
这里后端接收到Get请求头中的Token并对其进行验证,这时LoginServlet中的代码
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Content-Type", "text/html;charset=utf-8");//设置响应内容的编码
PrintWriter pt = resp.getWriter();
//获取前端传送过来的token
String token = req.getHeader("token");
System.out.println(token);
//验证token
boolean res = JWTUtil.verify(token);
CommonResult commonResult = new CommonResult(res?200:202,res,"验证token");
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(commonResult);
pt.print(json);
}
我们每次在接收到token时都要进行验证,这里我们将验证写进过滤器
package com.ffyc.webback.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ffyc.webback.util.CommonResult;
import com.ffyc.webback.util.JWTUtil;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class TokenFilter implements Filter {
@Override//HttpServletRequest extends ServletRequest
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)servletRequest;
String token = req.getHeader("token");
boolean res = JWTUtil.verify(token);
if(res){//token验证正确,继续执行下一个过滤器或Servlet
filterChain.doFilter(servletRequest,servletResponse);
}else{//token 验证失败,无需向下走了
HttpServletResponse resp = (HttpServletResponse) servletResponse;
resp.setHeader("Content-Type", "text/html;charset=utf-8");//设置响应内容的编码
PrintWriter pt = resp.getWriter();
CommonResult commonResult = new CommonResult(202,res,"验证token失败");
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(commonResult);
pt.print(json);
}
}
}
这里我们前端测试的get请求发送到TestServlet中,同时将LoginSerlvet中的doget方法删除并复制到TestServlet中
package com.ffyc.webback.servlet;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ffyc.webback.util.CommonResult;
import com.ffyc.webback.util.JWTUtil;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Content-Type", "text/html;charset=utf-8");//设置响应内容的编码
PrintWriter pt = resp.getWriter();
//获取前端传送过来的token
String token = req.getHeader("token");
DecodedJWT decodedJWT = JWTUtil.getTokenInfo(token);
Integer id = decodedJWT.getClaim("id").asInt();
String username = decodedJWT.getClaim("username").asString();
System.out.println("测试请求"+id+":"+username);
//验证token
boolean res = JWTUtil.verify(token);
CommonResult commonResult = new CommonResult(200,res,"测试成功");
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(commonResult);
pt.print(json);
}
}
一个关于配置的小问题,也是基于目前情况,这里测试向TestServlet发送请求,所以我们不需要向LoginServelt,那我们如何配置web.xml呢,是直接在url-pattern中填入/test吗,如果处理LoginServlet以外的Servelt都要添加TokenFilter过滤器,那这个时候配置就变得十分苦难,我们在TestServlet中改XML为
<!--注册项目中的servlet-->
<servlet>
<servlet-name>testServlet</servlet-name>
<servlet-class>com.ffyc.webback.servlet.TestServlet</servlet-class>
</servlet>
<!--为servlet配置访问地址-->
<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/admin/test</url-pattern>
</servlet-mapping>
而LoginServletxml设置不变同时重新设置TokenFilter中的配置为
<filter>
<filter-name>tokenFilter</filter-name>
<filter-class>com.ffyc.webback.filter.TokenFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>tokenFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
以上设置/admin/*
为为admin访问路径下的所有子路径添加tokenFilter过滤器,在所有要加该过滤器的位置配置url-pattern为/admin/xxx
这时前端接受请求后,code=202即为token验证失败,退出用户系统,代码为
test(){//token已经添加在请求头中
this.$http.get("admin/test").then((resp)=>{
if(resp.data.code==202){
this.$message({message:resp.data.message,type:'warning'});
this.$router.push("/login");
}
})
}
那么退出的方法也需要改变了,因为退出本地会话空间中的username,当再次通过URL访问用户系统时,本地会话空间的username不为空,直接跳入用户界面,我们在日常操作中退出之后,再次进入理当重新输入密码,所以,退出方法也需要修改,这时修改后的方法,退出之后,清除本地的用户信息
out(){
this.$confirm('确定要退出吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
sessionStorage.clear();//清空本地会话空间
this.$router.push("/login");
});
},
4. 梳理
本节课优化了登录操作,在前面我们写的登录界面就只能判断用户的数据,而这节课我们
- 先将用户名显示到了用户系统上
- 判断用户是否登录,当每次访问新网页,如果本地会话空间中的username为空,我们认为用户其实已近推出过浏览器,则让其进入登录界面,使用的路由导航守卫,这样每次切换访问页面时都会进行判断,无需重复代码
- 因为Http无状态,每次请求都只会返回访问的数据,而不像TCP那样基于连接,因此知道本次用户是谁,在这里我们引入了token(令牌),这种token可以存储用户信息,加密,不需要存储在服务器,十分方便。在第一次用户请求发送到后端时,在后端根据加密方式生成了一个和用户信息有关的token字符串,然后将token通过过滤器每次存储在响应头中并发送给用户,浏览器接收到token之后便存储在了本地会话空间,以后每次访问后端时都要发送这个token,如果token正确,执行相应操作,token错误,则用户进行了信息的篡改,立即退出用户系统,进入登陆界面。上几次我们还写过一个退出方法,当时写方法时退出没有清理本地会话空间中的username,所以推出之后,用户可以通过URL再次访问,这显然不合理,我们在每次登录之后清楚本地会话空间中的数据即可
- 这样一个基本的登录就完成了