目录
一. 前端浏览器保存用户信息
在浏览器中保存用户信息分为
1. 会话存储:只在会话期间保存用户信息,一旦用户关闭浏览器,用户信息就会被销毁
this.$http.post("login","account="+this.account+"&password="+this.password).then((resp)=>{
if(resp.data.code==200){
//前端浏览器中存储用户信息
sessionStorage.setItem("account",resp.data.result.account); //会话期间存储,关闭浏览器就会销毁
sessionStorage.setItem("gender",resp.data.result.gender);
sessionStorage.setItem("phone",resp.data.result.phone);
}
}
2.本地存储:可以长期保存用户信息,但会有风险
本地存储调用localStorage()即可,写法和会话存储相同
二. 前端路由导航守卫
前端路由守卫可以根据是否满足条件,从而进行相应的强制跳转,以登录为例子,如果该用户账号密码均正确才能来到后台操作,否则如果到后台页面了,但用户信息为空,那么路由导航守卫就会让你强制返回登录界面,该功能可以在index.js中配置
rout.beforeEach((to,from,next)=>{
if(to.path=='/login'){ //如果访问的是登录页面,直接放行,允许访问
return next();
}else{ //访问的是其他后端页面,要判断
var account = sessionStorage.getItem("account");
if(account==null){ //如果账号为空,回到登录页面
return next("/login");
}else{ //账号不为空,放行,允许访问
next();
}
}
})
三. 路由嵌套
当我们想在一个页面的某些区域进行页面跳转时,就要用到路由嵌套
在index.js中,找到主页面的路由,在里面定义一个children属性,将想在该页面跳转的子路由定义在里面即可
/* 定义组件路由 */
var rout = new router({
routes: [
{
path:'/',
component: Login
},
{
path: '/login',
component: Login
},
//这个是路由嵌套
{
path: '/main',
component: Main,
children:[
{
path:'/majorlist',
component:MajorList
},
{
path:'/studentlist',
component:StudentList
}
]
}
]
});
配置好这个还需要在主页面的.vue文件中修改三个地方
四. web会话跟踪
由于http请求是无状态的,这就导致每次发来的请求后端给响应之后,这次交互就结束了,当下次同一个人再来请求时,服务器也无法认出是哪一个用户,这就会导致用户在进行操作时,我们不知道是哪个用户做的,所以我们需要一种手段,来明确本次操作是哪个用户在操作
1. web会话跟踪原理
当前端第一次发来请求时,后端验证账号密码均正确,然后给前端发送一个令牌(令牌中存有用户信息),前端将其保存下来,之后每次发送请求时,都要携带该令牌,后端在响应前会先根据令牌确认身份,如果令牌验证不符合,直接强制退回登录界面,令牌验证成功,我们可以根据令牌得到用户信息,就知道是哪个用户在进行操作了
2. JWT
jwt(json web token):用来生成token的一种方式,一种可以携带用户信息,并且可以设置秘钥加密的字符串,是安全的
2.1 传统的session认证
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.
缺点:
- 1.用户数量多,服务器压力变大
- 2.扩展性差
- 3.容易被伪造攻击
-
2.2 基于token的鉴权机制
- 过程就是上面说的web会话跟踪的原理:
- 1. 用户使用账号和密码发出post请求
- 2. 服务器使用私钥创建一个jwt
- 3. 服务器返回这个jwt给浏览器
- 4. 浏览器将该jwt串在请求头中像服务器发送请求
- 5. 服务器验证该jwt
- 6. 返回响应的资源给浏览器
2.3 jwt的构成
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
这就是一个完整的jwt,它是由三部分组成的每个部分以.结束
第一部分(header):主要是声明类型和加密方式,这部分没有加密,只是通过base64进行了转码
第二部分(playload): 存储部分用户信息,该部分也没有加密,只是通过base64进行了转码
第三部分(signature):主要是签证信息,这部分通过上面两部分的base64转码拼接起来,再加上header中声明的加密方式,对其进行加密,就得到了第三部分
补充:base64是一种编码方式,将普通的符号码值按照6位一遍码,重新得到自己编码表中的符号
2.4 jwt搭建
1. 在maven项目中的pom.xml文件中注入如下依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.2</version>
</dependency>
2. 创建token生成方法
创建一个类专门用来管理token,不用我们自己写
public static String getToken(User u) {
String token = "";
try {
//过期时间 为1970.1.1 0:0:0 至 过期时间 当前的毫秒值 + 有效时间
Date expireDate = new Date(new Date().getTime() + 10*1000);
//秘钥及加密算法
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
//设置头部信息
Map<String,Object> header = new HashMap<>();
header.put("typ","JWT");
header.put("alg","HS256");
//携带id,账号信息,生成签名
token = JWT.create()
.withHeader(header)
.withClaim("id",u.getId())
.withClaim("account",u.getAccount())
.withExpiresAt(expireDate)
.sign(algorithm);
}catch (Exception e){
e.printStackTrace();
return null;
}
return token;
}
3. 验证token是否有效的方法,不用自己写
/**
* 验证token是否有效
* @param token
* @return
*/
public static boolean verify(String token){
try {
//验签
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e) {//当传过来的token如果有问题,抛出异常
return false;
}
}
4. 获取token中用户信息的那部分
/**
* 获得token 中playload部分数据,按需使用
* @param token
* @return
*/
public static DecodedJWT getTokenInfo(String token){
return JWT.require(Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE")).build().verify(token);
}
五. 前端发送请求携带token
按理说,我们应该在每次发送get请求或post请求时将token信息拼接在请求时的信息中,但这样会很麻烦,我们每发送一个请求都要拼接一次,万一我本来的请求信息就很多,还要再拼接一个岂不是更麻烦,所以这里我们可以进行一个配置
5.1 请求拦截器
为了避免每次都要自己携带token信息,前端可以在发送请求时添加一个请求拦截器,这样我们在发送请求时,就会自动执行请求拦截器中的内容,将我们的token携带在请求头中,由浏览器自己携带过去,后端只需要调用getHeader方法就能获得token信息
//axios 请求拦截
axios.interceptors.request.use(config =>{
//为请求头对象,添加Token验证的token字段
config.headers.token = sessionStorage.getItem('token');
return config;
})
六. 后端过滤器验证token
我们知道前端发送的请求中携带了token,那么我们后端就要对token进行验证,如果不正确,给前端响应回去一个标准json格式,前端再拿到状态码时就可以强制回到登录界面,但是我们不可能给每一个servlet程序都写一遍验证的过程,这样太重复且麻烦了,我们只需要定义一个过滤器专门用来验证token是否正确即可
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ffyc.dormServer.model.Result;
import com.ffyc.dormServer.util.JWTUtil;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter(urlPatterns = "/api/*")
public class TokenFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest; //向下转型
String token = request.getHeader("token"); //接收请求头中的token
boolean verify = JWTUtil.verify(token);
if(verify){ //验证成功,继续向后执行,到达目标servlet程序
filterChain.doFilter(servletRequest, servletResponse);
}else{ //验证失败,向前端响应401
Result result = new Result(401, "token验证失败", null);
servletResponse.getWriter().write(new ObjectMapper().writeValueAsString(result));
}
}
}
这样前端收到响应后就可以做出对应的操作
七. 前端响应拦截器
当前端收到后端响应回来的数据时,我们可以根据状态码来作出对应的判断,但我们知道当返回500表示后端出异常,401表示token验证失败返回到登录界面,这两个状态码所要做的事情都是相同的,我们没必要在每一次接收响应时都写一遍,只需要写每次收到200时,要做的事就可以,所以前端为我们提供了一个响应拦截器,当我们每次收到后端的响应时,就可以先通过拦截器判断状态码是不是500或401如果是就不用响应到前端,直接在拦截器作出响应操作,如果是200才在前端做出具体操作
// 添加响应拦截器
axios.interceptors.response.use((resp) =>{//正常响应拦截
if(resp.data.code==500){
ElementUI.Message({message:resp.data.desc,type:"error"})
}
if(resp.data==401){
ElementUI.Message({message:resp.data.desc,type:"warning"})
router.replace("/login");
}
return resp;
});