NULL http-заголовки передаются на сервер из приложения Angular2

У меня есть REST api, разработанный с использованием SpringBoot, и он содержит реализацию JWT для аутентификации и авторизации. Для этого используется FilterRegistrationBean.

У меня есть класс под названием JwtFilter, который расширяет GenericFilterBean. В этом я ищу содержимое заголовков запросов, чтобы авторизовать пользователей.

public class JwtFilter extends GenericFilterBean{

    @Override
    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {

        System.out.println("INSIDE JWT FILTER");

        final HttpServletRequest request = (HttpServletRequest) req;

        final String authHeader = request.getHeader("Authorization");
        String reqHdr = request.getHeader("X-Requested-By");
        String myHdr = request.getHeader("myheader");

        System.out.println("Auth header = "+authHeader);
        System.out.println("requested by header = "+reqHdr);
        System.out.println("my header = "+myHdr);


        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            System.out.println("JwtFilter.doFilter -> auth header is null or token does not start with Bearer");
            throw new ServletException("Missing or invalid Authorization header.");
        }

        final String token = authHeader.substring(7); // The part after "Bearer "

        try {
            final Claims claims = Jwts.parser().setSigningKey("secretkey").parseClaimsJws(token).getBody();
            request.setAttribute("claims", claims);
        }
        catch (final SignatureException e) {
            System.out.println("VIRGLK EXCEPTION : JwtFilter.doFilter -> Signature Exception, invalid token = "+e.getMessage());
            throw new ServletException("Invalid token.");
        }

        chain.doFilter(req, res);
    }

}

Я могу заверить, что настройка заголовков динамически работает нормально, так как я тестировал ее для некоторых других HTTP-запросов на тот же сервер, которые не были отфильтрованы по URL (запрос на вход).

Но, когда дело касается запросов, отфильтрованных выше, я вижу, что каждый заголовок запросов имеет значение NULL.

Ниже приводится способ добавления заголовков в запросы в Angular2. Я добавил класс BaserequestOptions и переопределил метод слияния, чтобы динамически добавлять заголовки.

@Injectable()
export class CustomRequestOptions extends BaseRequestOptions {
    constructor(private _globals: Globals) {
        super();
        this.headers.set('Content-Type', 'application/json');
        this.headers.set('X-Requested-By', 'Angular 2');
    }

    merge(options?: RequestOptionsArgs): RequestOptions {
        var newOptions = super.merge(options);
        let hdr = this._globals.getAuthorization();
        newOptions.headers.set("Authorization", hdr);
        newOptions.headers.set("myheader", "my header value");
        return newOptions;
    }

}

Но эти заголовки имеют нулевое значение, если в API проверяется на фильтрацию запросов. Как я уже упоминал выше, для нефильтрованных запросов нет проблем. Оба статически добавлены ( "X-Requested-By" ) и динамически добавлены ( "myheader" ) заголовки и доступны на сервере.

Я не могу понять, что здесь происходит. По крайней мере, у меня нет подсказки, с какой стороны возникает ошибка.

Ниже приведен заголовок запроса, скопированный из консоли браузера для отказавшего запроса.

OPTIONS /protected/get-roles/USER HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Access-Control-Request-Headers: authorization,content-type,myheader,x-requested-by
Accept: */*

Может кто-нибудь указать мне, что может быть проблемой здесь. Я не знаю.

ИЗМЕНИТЬ

Я предполагаю, что проблема исходит от сервера по следующей причине.

Мне удалось успешно получить все значения заголовков запроса, которые были выполнены после того, как заголовок авторизации установлен и этот запрос не является фильтрованным. Поэтому я предполагаю, что что-то не так с реализацией JWT-фильтра.

Это может быть глупый вопрос, но, это связано с тем, что запросы, которые я отправляю, не относятся к типу ServletRequest, может быть? doFilter в JwtFilter принимает параметр ServletRequest.

Ответ 1

Я думал о написании решения, которое я нашел в качестве ответа здесь, поскольку он может помочь кому-то в будущем. Спасибо за комментарий JB Nizet. Он показал мне, в чем проблема.

Это была проблема с CORS. Запрос перед полетом был отправлен браузером до того, как был отправлен фактический запрос GET (в моем случае). Как отметил @JB Nizet, я изменил свой код, ссылаясь на следующие связанные ответы.

Совместное использование ресурсов с помощью Spring Безопасность

Заголовок в ответе не должен быть подстановочным знаком '*', когда режим учетных данных запроса "включает"

Теперь мой JwtFilter выглядит следующим образом

public class JwtFilter extends GenericFilterBean{

    private final List<String> allowedOrigins = Arrays.asList("http://localhost:3000");

    @Override
    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {

        System.out.println("INSIDE JWT FILTER");

        final HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;


        // Access-Control-Allow-Origin
        String origin = request.getHeader("Origin");
        response.setHeader("Access-Control-Allow-Origin", allowedOrigins.contains(origin) ? origin : "");
        response.setHeader("Vary", "Origin");

        // Access-Control-Max-Age
        response.setHeader("Access-Control-Max-Age", "3600");

        // Access-Control-Allow-Credentials
        response.setHeader("Access-Control-Allow-Credentials", "true");

        // Access-Control-Allow-Methods
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");

        // Access-Control-Allow-Headers
        response.setHeader("Access-Control-Allow-Headers",
                "Origin, Authorization, myheader, X-Requested-By, X-Requested-With, Content-Type, Accept, " + "X-CSRF-TOKEN");

        if (request.getMethod().equals("OPTIONS")) {
            response.flushBuffer();
        }else{

            final String authHeader = request.getHeader("Authorization");
            String reqHdr = request.getHeader("X-Requested-By");
            String myHdr = request.getHeader("myheader");

            System.out.println("=====================================================");
            System.out.println("Auth header = "+authHeader);
            System.out.println("requested by header = "+reqHdr);
            System.out.println("my header = "+myHdr);
            System.out.println("=====================================================");

            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                System.out.println("JwtFilter.doFilter -> auth header is null or token does not start with Bearer");
                throw new ServletException("Missing or invalid Authorization header.");
            }

            final String token = authHeader.substring(7); // The part after "Bearer "

            try {
                final Claims claims = Jwts.parser().setSigningKey("secretkey").parseClaimsJws(token).getBody();
                request.setAttribute("claims", claims);
            }
            catch (final SignatureException e) {
                System.out.println("JwtFilter.doFilter -> Signature Exception, invalid token = "+e.getMessage());
                throw new ServletException("Invalid token.");
            }

            chain.doFilter(req, res);

        }


    }

}

Счастливое кодирование!

Ответ 2

Я видел эту проблему много раз раньше. Ключ здесь Access-Control-Allow-Headers http header. Таким образом, CORS, это может укусить вас, если заголовки, которые ваш клиент и сервер пытаются обменять, не указаны. Вкладка "Сеть" в браузере может даже отображать заголовки в объектах ответа и запроса, однако он не будет доступен для прикладного уровня.

Используете ли вы Spring Boot для сервера приложений? Если да, там какая-то магия в том, как настроен WebSecurity. Конкретное расширение и настройка HttpSecurity через WebSecurityConfigurerAdapter поможет обрабатывать заголовки CORS во всех HTTP-глаголах, таких как OPTION, POST, GET и т.д.

Взгляните на https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers для примеров того, что должно быть значением заголовков, в основном просто списком ключевых слов с разделителями-запятыми.