在自己写单独项目时,遇到这样的问题,登录网站后,我不小心触碰到浏览器的x按钮,好了,重新打开还得输入账号密码验证码,好繁琐
对于不依赖任何插件库的情况下这是第一种解决方案:
前后端分离,VUE需要做的事情
在main.js中增加以下配置:
import axios from 'axios';
axios.defaults.withCredentials=true;
或者请求时:设置 withCredentials: true
axios({
url: url,
data: data,
headers: {
'Content-Type': 'multipart/form-data'
},
method: 'POST',
withCredentials: true
}).then(response => {
resolve(response)
}java中需要做的
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//解决后续中文乱码
response.setContentType("text/html;charset=utf-8");
response.setCharacterEncoding("utf-8");
//添加跨域CORS
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Headers", getHeadersInfo(request));
response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
// 是否允许浏览器携带用户身份信息(cookie) 必须加
response.setHeader("Access-Control-Allow-Credentials","true");
return true;
}
//获取原有的头部原路返回,不然还会存在token等字段跨域失败
private String getHeadersInfo(HttpServletRequest request) {
String headers="";
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
headers+=headerNames.nextElement()+",";
}
if(!headers.contains("content-type")){
headers+="content-type,";
}
if(!headers.contains("token")){
headers+="token,";
}
return headers;
}需要注意的是,上述代码只会保持在同一个session,关闭浏览器重新打开保持登陆需要做下面的操作(此步骤适合前后端一体的程序)
在java的登陆成功方法中加入一个cookie丢到客户端,让客户端记住JSESSIONID,和java的session保持一致的存活时间
//new一个cookie,cookie的名字是JSESSIONID跟带id的cookie一样
Cookie cookie = new Cookie("JSESSIONID", HttpServletHelper.getSession().getId());
//设置cookie应用范围。getContextPath是获取当前项目的名字。
cookie.setPath(HttpServletHelper.getRequest().getContextPath());
//设置有效时间
cookie.setMaxAge(HttpServletHelper.getSession().getMaxInactiveInterval());
//用这个cookie把带id的cookie覆盖掉
response.addCookie(cookie);最最需要注意的是:由于安全考虑,新版的浏览器加入了一个字段(Cookie的SameSite),仅仅使用上述还是无法解决,仍然存在跨域
该字段在spring2.1x中才引入。所以你要升级版本,而且就算你升级了版本,该参数SameSite的值设置为None,也必须要求你为https才能生效。
SameSite值有三个,可以自己了解一下
对于不想升级的用户,并且确保站点是https可以尝试如下代码,注意里面的位置不要改变
response.setHeader("Set-Cookie", "JSESSIONID=xxx;SameSite=None;Secure");springboot2.1x代码
@Bean
public CookieSerializer httpSessionIdResolver() {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setUseHttpOnlyCookie(false);
cookieSerializer.setSameSite("None");
cookieSerializer.setCookiePath("/");
cookieSerializer.setUseSecureCookie(true);
return cookieSerializer;
}pom依赖
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>2.1.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-core</artifactId> <version>2.1.4.RELEASE</version> </dependency>
对于安全考虑,我觉得一就算了吧,太麻烦了。说不准哪天https也不支持了,反正都是谷歌来定义了(毕竟用户群体太多了)
说一下第二种,不依靠session,使用redis替换session,现在各个项目我觉得使用redis应该是家常便饭了吧
先说java部分,登陆成功后生成一个token放入redis
Map<String,Object> userMap=new HashMap<>(2);
userMap.put("user",user);
String token= StringUtils.getUuid();
userMap.put("token",token);
//一定要注意有效期,一定要放,仍然是和session会话时长一致
redisHelper.hset("loginUser",token,user,HttpServletHelper.getSession().getMaxInactiveInterval());参考第一步,跨域的代码仍然需要;下面是登录拦截代码
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//解决OPTIONS请求没有token导致跨域失败问题
if(StringUtils.isNullOrEmpty(request.getContentType())){
return true;
}
//失效等信息会在HttpServletHelper和全局错误拦截器中处理
User user = HttpServletHelper.getUser(redisHelper);
//这么做是刷新有效期
HttpServletHelper.setUser(redisHelper,HttpServletHelper.getToken(),user);
return true;
}重点来了,这个是对登录用户封装的方法
public class HttpServletHelper {
public static HttpServletRequest getRequest() {
return getRequestAttributes().getRequest();
}
public static HttpServletResponse getResponse() {
return getRequestAttributes().getResponse();
}
public static HttpSession getSession() {
return getRequest().getSession();
}
public static ServletRequestAttributes getRequestAttributes() {
return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
}
public static ServletContext getServletContext() {
return ContextLoader.getCurrentWebApplicationContext().getServletContext();
}
public static String getToken(){
String token=getRequest().getHeader("token");
if(StringUtils.isNullOrEmpty(token)){
throw new BusinessException("400","token丢失!");
}
return token;
}
public static User getUser(RedisHelper redisHelper){
User user=null;
try{
user=(User)redisHelper.hget("loginUser",getToken());
}catch (BusinessException e){
}
if(user==null){
throw new BusinessException("203","用户未登录");
}
return user;
}
public static User setUser(RedisHelper redisHelper,String token,User user){
redisHelper.hset("loginUser",token,user, getSession().getMaxInactiveInterval());
return user;
}
}有人就会问我了BusinessException这个找不到啊,我只能告诉你这是自定义异常类,到时候可以全局拦截这个异常,返回对应的提示
BusinessException这个类留给自己思考吧
说一下全局拦截异常处理
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
//这里就是自定义错误拦截的方法
@ExceptionHandler(BusinessException.class)
public ApiResponse BusinessExceptionHandler(BusinessException e){
//拦截到自定义错误后,获取错误代码,还有错误信息,友好的返回前端
return ApiResponse.status(Integer.parseInt(e.getCode()),e.getMessage());
}
}VUE部分只需要将token字段放入header带到后台即可
是不是So Easy

