一、前端拦截的缺陷
见链接:https://blog.csdn.net/Tom870223050/article/details/115977961?spm=1001.2014.3001.5502
只有前端拦截,可能会被恶意地进行模拟请求,也就是绕过前端,直接访问后台,所以我们在后台还需要配置拦截器
二、springBoot拦截器
springMvc和springBoot实现拦截器方法差不多,但是 springMvc的拦截器是在xml里面注册的,springBoot是省略了xml的配置,直接编写一个实现类进行注册。
- 引入
MiniInterceptor
类
重要类:
1.preHandle
:拦截请求,在controller调用之前
2.postHandle
:请求controller之后,渲染视图之前
3.afterCompletion
: 请求controller之后,视图渲染之后
package com.imooc.controller.interceptor;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.imooc.utils.IMoocJSONResult;
import com.imooc.utils.JsonUtils;
import com.imooc.utils.RedisOperator;
public class MiniInterceptor implements HandlerInterceptor {
@Autowired
public RedisOperator redis;
public static final String USER_REDIS_SESSION = "user-redis-session";
/**
* 拦截请求,在controller调用之前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object arg2) throws Exception {
/**
* 返回 false:请求被拦截,返回
* 返回 true :请求OK,可以继续执行,放行
*/
return true;
}
public void returnErrorResponse(HttpServletResponse response, IMoocJSONResult result)
throws IOException, UnsupportedEncodingException {
}
/**
* 请求controller之后,渲染视图之前
*/
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
throws Exception {
}
/**
* 请求controller之后,视图渲染之后
*/
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
}
}
2 . 注册拦截器类
这时我们需要把
MiniInterceptor
类注册在WebMvcConfig
类中
- webMvcConfig.java
(1)首先我们需要把它注册在spring中,为一个
Bean
@Bean
public MiniInterceptor miniInterceptor() {
return new MiniInterceptor();
}
(2)然后我们把它注册在拦截器中。
addPathPatterns()
表示添加需要进行拦截的路径,也就是我们的后台接口,**
表示对其中的所有方法都要进行拦截
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(miniInterceptor()).addPathPatterns("/user/**")
.addPathPatterns("/video/upload", "/video/uploadCover",
"/video/userLike", "/video/userUnLike",
"/video/saveComment")
.addPathPatterns("/bgm/**")
.excludePathPatterns("/user/queryPublisher");
super.addInterceptors(registry);
}
- 编写拦截器
用户在登陆成功即可保存用户id和
Token
,Token
可以放在redis里面
//loginController.java
public UsersVO setUserRedisSessionToken(Users userModel){
String uniqueToken = UUID.randomUUID().toString();
redis.set(USER_REDIS_SESSION+":"+userModel.getId(),uniqueToken,1000*60*30);
UsersVO usersVO = new UsersVO();
BeanUtils.copyProperties(userModel,usersVO);
usersVO.setUserToken(uniqueToken);
return usersVO;
}
后台在进行鉴权的时候要用到存放用户信息的
Token
和用户id,所以前端向后台发送信息的时候要加上这些信息
//mine.js
url:
method: "POST",
header: {
'content-type': 'application/json', // 默认值
'headerUserId': user.id,
'headerUserToken': user.userToken
}
//MiniInterceptor
package com.imooc.controller.interceptor;
import com.imooc.utils.IMoocJSONResult;
import com.imooc.utils.JsonUtils;
import com.imooc.utils.RedisOperator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
public class MiniInterceptor implements HandlerInterceptor {
@Autowired
public RedisOperator redis;
public static final String USER_REDIS_SESSION = "user-redis-session";
/**
* 拦截请求,在controller调用之前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object arg2) throws Exception {
String userId = request.getHeader("headerUserId");
String userToken = request.getHeader("headerUserToken");
if (StringUtils.isNotBlank(userId) && StringUtils.isNotBlank(userToken)) {
String uniqueToken = redis.get(USER_REDIS_SESSION + ":" + userId);
if (StringUtils.isEmpty(uniqueToken) && StringUtils.isBlank(uniqueToken)) {
System.out.println("用户信息过期,请重新登录...");
returnErrorResponse(response, new IMoocJSONResult().errorTokenMsg("请登录..."));
return false;
} else {
if (!uniqueToken.equals(userToken)) {
System.out.println("账号被挤出...");
returnErrorResponse(response, new IMoocJSONResult().errorTokenMsg("账号被挤出..."));
return false;
}
}
} else {
System.out.println("请登录...");
returnErrorResponse(response, new IMoocJSONResult().errorTokenMsg("请登录..."));
return false;
}
/**
* 返回 false:请求被拦截,返回
* 返回 true :请求OK,可以继续执行,放行
*/
return true;
}
public void returnErrorResponse(HttpServletResponse response, IMoocJSONResult result)
throws IOException, UnsupportedEncodingException {
OutputStream out=null;
try{
response.setCharacterEncoding("utf-8");
response.setContentType("text/json");
out = response.getOutputStream();
out.write(JsonUtils.objectToJson(result).getBytes("utf-8"));
out.flush();
} finally{
if(out!=null){
out.close();
}
}
}
/**
* 请求controller之后,渲染视图之前
*/
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
throws Exception {
}
/**
* 请求controller之后,视图渲染之后
*/
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
}
}
preHandle
方法:首先获取到前端的Header发送字段,然后进行判空,成功后若token到达时间了会失效,此时后台返回重新登陆信息,还有一种情况是用户在一个手机上进行登陆,又在第二台手机上进行登陆,这时会向后台发送两次信息,其中,两次的userToken信息是不一样的,第一次发送的userToken保存到变量uniqueToken里,当!uniqueToken.equals(userToken)
为真表示用户在两台手机上进行登陆,在这个业务逻辑里我们视为不安全登陆,之前的登陆状态将会被挤出
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object arg2) throws Exception {
String userId = request.getHeader("headerUserId");
String userToken = request.getHeader("headerUserToken"); // 获取到前端的Header发送字段
if (StringUtils.isNotBlank(userId) && StringUtils.isNotBlank(userToken)) {
String uniqueToken = redis.get(USER_REDIS_SESSION + ":" + userId);
if (StringUtils.isEmpty(uniqueToken) && StringUtils.isBlank(uniqueToken)) {
System.out.println("用户信息过期,请重新登录...");
returnErrorResponse(response, new IMoocJSONResult().errorTokenMsg("请登 录..."));
return false;
} else {
if (!uniqueToken.equals(userToken)) {
System.out.println("账号被挤出...");
returnErrorResponse(response, new IMoocJSONResult().errorTokenMsg("账号被挤出...")); //后一次的用户信息和前一次的不一致
return false;
}
}
} else {
System.out.println("请登录...");
returnErrorResponse(response, new IMoocJSONResult().errorTokenMsg("请登录..."));
return false;
}
/**
* 返回 false:请求被拦截,返回
* 返回 true :请求OK,可以继续执行,放行
*/
return true;
}
4.处理拦截信息
//MiniInterceptor.java
returnErrorResponse
方法:设置拦截器返回前端的errorTokenMsg
信息格式
@Override
public void returnErrorResponse(HttpServletResponse response, IMoocJSONResult result)
throws IOException, UnsupportedEncodingException {
OutputStream out=null;
try{
response.setCharacterEncoding("utf-8");
response.setContentType("text/json");
out = response.getOutputStream();
out.write(JsonUtils.objectToJson(result).getBytes("utf-8"));
out.flush();
} finally{
if(out!=null){
out.close();
}
}
}
//XX.js
我们设置被拦截后,后端发送的状态码为
502
,前端接受错误信息并跳转到登陆页面
if (res.data.status == 502) {
wx.showToast({
title: res.data.msg,
duration: 3000,
icon: "none",
success: function () {
wx.redirectTo({
url: '../userLogin/login',
})
}
})
}
5.完整的前端请求如下
wx.request({
url: serverUrl + '/XXX?userId=' + userId,
method: "POST",
header: {
'content-type': 'application/json', // 默认值
'headerUserId': user.id,
'headerUserToken': user.userToken
},
success: function (res) {
wx.hideLoading();
if (res.data.status == 200) {
//登陆成功的操作
}
} else if (res.data.status == 502) {
wx.showToast({
title: res.data.msg,
duration: 3000,
icon: "none",
success: function () {
wx.redirectTo({
url: '../userLogin/login',
//Token失效后跳转到登陆界面
})
}
})
}
}
})