фон проблемы
Проект компании отделен от фронтенда и бекенда, в последнее время требуется добавлять кастомные заголовки в шапку запроса.token
, при отладке интерфейса фронтенд всегда не запрашивает, но сам пользуюсьPOSTMAN
Инструмент можно подождать, это проблема, т.е.сложный запросмеждоменная проблема.
анализ проблемы
Некоторые абзацы взяты изПодробное объяснение CORS для совместного использования ресурсов между доменами
сложный запрос
браузер будетCORS
Запросы делятся на две категории: простые запросы (simple request
) и непростые запросы (not-so-simple request
).
Пока следующие два условия выполняются одновременно, это простой запрос.
- Метод запроса является одним из трех методов:
HEAD GET POST
-
HTTP
Информация заголовка не превышает следующих полей:
Это для совместимости с формами (Accept Accept-Language Content-Language Last-Event-ID Content-Type: 只限于三个值 application/x-www-form-urlencoded multipart/form-data text/plain
form
), поскольку исторически формы всегда могли выполнять запросы из разных источников.AJAX
Междоменный дизайн заключается в том, что пока форма может быть отправлена,AJAX
можно отправить напрямую.
Если два вышеуказанных условия не выполняются одновременно, это непростая заявка.
предварительный запрос
не простой запросCORS
Запросы будут добавлены один раз перед официальным общениемHTTP
запросы запросов, известные как «предварительные» запросы (preflight
).
Браузер сначала спрашивает сервер, находится ли доменное имя текущей веб-страницы в списке разрешенных сервером и какие из них можно использовать.HTTP
Поля глагола и заголовка. Только при утвердительном ответе браузер выдаст официальноеXMLHttpRequest
запроса, иначе будет сообщено об ошибке.
фильтр
благодаря проектуshiro
использовалUserFilter
, вот его код:
public class UserFilter extends AccessControlFilter {
public UserFilter() {
}
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (this.isLoginRequest(request, response)) {
return true;
} else {
Subject subject = this.getSubject(request, response);
return subject.getPrincipal() != null;
}
}
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
this.saveRequestAndRedirectToLogin(request, response);
return false;
}
}
Можно видеть, что приведенное выше суждение фильтра используется для определения того, является ли это запросом на вход, иначе он будет искать учетные данные для входа. пока вOPTIONS
В запросе не указаноtoken
информация, следующее запрашивается в связи с обстоятельствамиheader
:
=== MimeHeaders ===
host = 192.168.7.139:4000
connection = keep-alive
accept = */*
access-control-request-method = POST
access-control-request-headers = content-type,x-admin-token
origin = http://192.168.7.117:8080
sec-fetch-mode = cors
referer = http://192.168.7.117:8080/
user-agent = Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
accept-encoding = gzip, deflate
accept-language = zh-CN,zh;q=0.9
можно увидетьtoken
был взятaccess-control-request-headers
, такshiro
Учетные данные для входа не найдены, и запрос естественно отклонен.
задача решена
Решение состоит в том, чтобы переписатьUserFilter
(в зависимости от того, какой фильтр использует проект)isAccessAllowed
Способ, следующим образом:
public class StatelessAuthcFilter extends UserFilter {
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpRequest = WebUtils.toHttp(request);
HttpServletResponse httpResponse = WebUtils.toHttp(response);
if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpResponse.setHeader("Access-control-Allow-Origin", httpRequest.getHeader("Origin"));
httpResponse.setHeader("Access-Control-Allow-Methods", httpRequest.getMethod());
httpResponse.setHeader("Access-Control-Allow-Headers", httpRequest.getHeader("Access-Control-Request-Headers"));
httpResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
}
Затем перейдите к настройке широ и измените пользовательский фильтр на пользовательский фильтр:
/**
* Shiro过滤器配置
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 身份认证失败,则跳转到登录页面的配置
shiroFilterFactoryBean.setLoginUrl(loginUrl);
// 权限认证失败,则跳转到指定页面
shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
Map<String, Filter> filters = new LinkedHashMap<>();
filters.put("user", new StatelessAuthcFilter());
shiroFilterFactoryBean.setFilters(filters);
// Shiro连接约束配置,即过滤链的定义
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 对静态资源设置匿名访问
filterChainDefinitionMap.put("/favicon.ico**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/docs/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/ajax/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
// 不需要拦截的访问
filterChainDefinitionMap.put("/auth/login", "anon");
// 所有请求需要认证
filterChainDefinitionMap.put("/**", "user");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
Я думал, что это будет нормально, но на самом деле проблема с запросом интерфейса после перенаправления страницы остается, потому что перенаправление будет очищать заголовок запроса по умолчанию, поэтому необходимоonAccessDenied
Метод переписан, и полный код выглядит следующим образом:
public class StatelessAuthcFilter extends UserFilter {
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpRequest = WebUtils.toHttp(request);
HttpServletResponse httpResponse = WebUtils.toHttp(response);
if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpResponse.setHeader("Access-control-Allow-Origin", httpRequest.getHeader("Origin"));
httpResponse.setHeader("Access-Control-Allow-Methods", httpRequest.getMethod());
httpResponse.setHeader("Access-Control-Allow-Headers", httpRequest.getHeader("Access-Control-Request-Headers"));
httpResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletResponse httpResp = WebUtils.toHttp(response);
HttpServletRequest httpReq = WebUtils.toHttp(request);
/*系统重定向会默认把请求头清空,这里通过拦截器重新设置请求头,解决跨域问题*/
httpResp.addHeader("Access-Control-Allow-Origin", httpReq.getHeader("Origin"));
httpResp.addHeader("Access-Control-Allow-Headers", "*");
httpResp.addHeader("Access-Control-Allow-Methods", "*");
httpResp.addHeader("Access-Control-Allow-Credentials", "true");
this.saveRequestAndRedirectToLogin(request, response);
return false;
}
}
Это решает междоменную проблему, вызванную сиро. Если контент полезен для вас, вы можете поделиться им с друзьями, чтобы учиться вместе.
Эта статья опубликована в блогеOpenWriteвыпускать!