首先配置允许跨域
@Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT")
.maxAge(3600);
}
}
因项目需对用户校验,因此配置了过滤器,
然后出现问题:
**1.过滤器拦截后,页面提示没有跨域
2.在过滤器配置跨域后,返回responsebody是空的**
代码如下:
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
@WebFilter(filterName = "sessionFilter", urlPatterns = {"/*"})
public class SessionFilter implements Filter {
@Value("${manage.session.timeout}")
private long timeout;
private final ObjectMapper mapper = new ObjectMapper();
//不需要登录就可以访问的路径(比如:注册登录等)
String[] includeUrls = new String[]{
"/web-manage/su/submitLogin", "/su/submitLogin"};
/**
* 登录校验
*
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
log.info("Headers:{}", response.getHeader("Access-Control-Request-Headers"));
//String origin = request.getHeader("Origin");
//if (origin == null) {
// origin = request.getHeader("Referer");
//}
//response.setHeader("Access-Control-Allow-Origin", origin);
//使用通配符responseBody是空的!放开上段代码后项目正常
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Headers", "Content-Type,Access-Token");
response.setHeader("Access-Control-Expose-Headers", "*");
String uri = request.getRequestURI();
String jsessionId = "";
log.info("uri :{}, request.getContentType(): {}", uri, request.getContentType());
//是否需要过滤
boolean needFilter = isNeedFilter(uri, includeUrls);
if (!needFilter) {
//不需要过滤直接传给下一个过滤器
filterChain.doFilter(servletRequest, servletResponse);
} else {
Cookie[] cookies = request.getCookies();
if (cookies == null || cookies.length == 0) {
cookieNoJessionid(response);
} else {
for (Cookie cookie : cookies) {
if (CommonConstant.JSESSIONID.equals(cookie.getName())) {
jsessionId = cookie.getValue();
break;
}
}
log.info("ContentType:" + request.getContentType());
if (request.getMethod().equals("OPTIONS")) {
response.setStatus(HttpStatus.OK.value());
return;
}
RequestWrapper requestWrapper = null;
if (request instanceof HttpServletRequest) {
if ("POST".equals(request.getMethod().toUpperCase())
&& request.getContentType() != null
&& request.getContentType().contains("application/json")) {
requestWrapper = new RequestWrapper(request);
}
}
judgeLogin(jsessionId, response, request, requestWrapper, filterChain);
}
}
}
/**
* 判断是否已登录
*
* @param jsessionId
* @param response
* @param request
* @param requestWrapper
* @param filterChain
* @throws IOException
* @throws ServletException
*/
private void judgeLogin(String jsessionId, HttpServletResponse response, HttpServletRequest request,
RequestWrapper requestWrapper, FilterChain filterChain) throws IOException, ServletException {
// 判断 jsessionId 是否为空
if (StringUtils.isBlank(jsessionId)) {
cookieNoJessionid(response);
} else {
// 获取ValueOperations bean
ServletContext context = request.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
ValueOperations valueOperations = (ValueOperations) ctx.getBean("valueOperations");
// 获取用户信息
String userBoStr = (String) valueOperations.get(RedisConstant.REDIS_LOGIN + jsessionId);
SysUserBo sysUserBo = mapper.readValue(userBoStr, SysUserBo.class);
if (sysUserBo != null) {
filterChain.doFilter(request == null ? requestWrapper : request, response);
} else {
// 返回登录超时提醒
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write(this.mapper.writeValueAsString(ResultUtil.errorResult(
ExceptionEnum.ERROR_PARAMETERS.getCode(), "jsessionid 登录超时")));
}
}
}
/**
* cookie 无jsessionid
* @param response
* @throws IOException
*/
private void cookieNoJessionid(HttpServletResponse response) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write(this.mapper.writeValueAsString(ResultUtil.errorResult(
ExceptionEnum.ERROR_PARAMETERS.getCode(),
"jsessionid 为空!")));
}
/**
* @param uri
* @Description: 是否需要过滤
*/
public boolean isNeedFilter(String uri, String[] includeUrls) {
for (String includeUrl : includeUrls) {
if (includeUrl.equals(uri)) {
return false;
}
}
return true;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
百度一波,给出的解释是:
出自:https://www.cnblogs.com/nopnog/articles/9133115.html
Access-Control-Allow-Origin 的正确玩法
前端发起的跨域请求需要适当地响应 Access-Control-Allow-Origin 头。但是这个头目前并不支持部分通配符,无法匹配某个域名下的子域。直接使用星号又可能带来各种问题。更坑爹的是它还不支持设置多个值。那么如果一个后端程序用于多个域名的访问该怎么办呢?
很多时候一套后端的程序都对应了多个调用方,它们使用的的域名不同,scheme 可能也不同。所以 Access-Control-Allow-Origin 的值没法写死一个域名。如果直接暴力地将其设置为「」,可能导致一些未被授权的域名也能请求到资源。而且当 Access-Control-Allow-Credentials 的值为 true 时会导致Access-Control-Allow-Origin 无法被设置为「」。
正确的玩法应该是在后端程序中获取从 HTTP 请求头传过来的 Origin 字段,然后在程序中验证它的值是否合法,并且做出适当的响应。也就是说,可以不依赖前端的跨域限制,后端如果认为一个请求的 Origin 来自不正确的地方就直接毫不留情地 403 掉。其它情况如果没有 Origin 或者 Origin 正确则将 Origin 的值原原本本地放入 Access-Control-Allow-Origin 中响应回去。
其实虽然这个问题可以完全通过后端解决,但我还是很费解规范中无法使用局部通配符,和无法配置多个值的设定。也许这么设计的目的就是希望后端直接 403,而不是让浏览器来拦截吧(反正让浏览器来拦截还需要额外的传输成本,有点浪费)