使用Shiro时的跨域问题探讨
作者:木子六日
时间:2021年1月13日
引
本来真的不想说这个问题,我感觉自己还没百分百弄明白,不过操作层面差不多了,又碰到了,索性说一下。
前后端分离的项目,后端和前端基本上是不会在同一台机子的同一个端口上的,这个时候就跨域了。
简单说就是你的页面要发另一个请求,这个请求的去处不是页面本身的来处,这就算跨域。
解决办法
简单地解决跨域我在之前的博客里都有写过,这里再贴一下吧。
传统的办法就是往响应头里加一些东西允许跨域:
package com.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
public class AllowCORS implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type,Access-Token,Authorization,ybg");
return true;
}
}
这是SpringMVC拦截器的做法,其实也可以写一个filter。
SpringBoot的办法要更简单一些:
package com.ljj.SpringBootLog.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CORSConfigurer implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT").maxAge(3600);
}
}
会话保持
没错这就是我今天想说的。
ajax请求默认是不让带cookie的。如果我们希望带cookie,以axios为例:
import axios from 'axios'
export function request(config) {
const instance = axios.create({
baseURL: 'http://test',
withCredentials: true
})
return instance(config)
}
我们就要加上这个withCredentials
.
代码层面前端配这么一个东西就行了。
我们接着看后端,以SpringBoot为例:
package com.ljj.kongzi.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* CORS配置,允许跨域访问
* @author muziliuri
*
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer{
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8080").allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT","HEAD","OPTIONS").maxAge(3600);
}
}
origin处不能再写*了,必须写成具体的,就是你的前端服务的主机名端口号。
在开发过程中写成localhost就行了,即使后端的服务是在别的机子上也没关系。
但是如果前后端的服务无法在同一台机子上,还会出现一个问题,就是你的浏览器认为这是不安全的。
- Firefox是允许的;
- Chrome是不允许的,输入
chrome://flags
,然后搜索samesite by default cookies
,把那两项都disable了就行了。
最后部署前端服务的时候记得布到和后台一个机子上就行。
跨域时Shiro的重定向问题
上面的做法并不能保证万无一失,一旦会话断开,仍还会产生跨域问题。
为什么呢?我们知道Shiro在认证失败的时候会做重定向,前后端分离的时候一般是自定义一段json返回。
//认证失败
Map<String, Filter> filters=new HashMap<>();
filters.put("authc", new MyUserFilter());
shiroFilterFactoryBean.setFilters(filters);
其中MyUserFilter是我自定义的Filter:
package com.ljj.kongzi.shiro.filters;
import java.io.IOException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.web.filter.authc.UserFilter;
import com.ljj.kongzi.common.Response;
public class MyUserFilter extends UserFilter{
@Override
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setContentType("application/json;charset=utf-8");
Response jsonResponse = new Response(600, "Please login first.", null);
response.getWriter().write(jsonResponse.toJson());
}
}
但是我发现只要代码过这儿,浏览器那边就一定会报跨域的错。
经过我的实验response的请求头里面并没有CorsConfig里面配置的信息,也就是说没有Access-Control-Allow-Origin
这些。
也就是说对于shiro认证失败的请求,SpringBoot的CORS配置无效。
那只能手动配一下了:
package com.ljj.kongzi.shiro.filters;
import java.io.IOException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.web.filter.authc.UserFilter;
import com.ljj.kongzi.common.Response;
public class MyUserFilter extends UserFilter{
@Override
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT, HEAD");
resp.setHeader("Access-Control-Max-Age", "3600");
resp.setHeader("Access-Control-Allow-Headers", "Content-Type,Access-Token,Authorization,ybg");
resp.setHeader("Access-Control-Allow-Credentials", "true");
resp.setContentType("application/json;charset=utf-8");
Response jsonResponse = new Response(600, "Please login first.", null);
response.getWriter().write(jsonResponse.toJson());
}
}
这样一来就没问题了。