Междоменный интерфейс, вызванный фильтром Широ

Java

фон проблемы

Проект компании отделен от фронтенда и бекенда, в последнее время требуется добавлять кастомные заголовки в шапку запроса.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выпускать!