在自己写单独项目时,遇到这样的问题,登录网站后,我不小心触碰到浏览器的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