Problem extracting token from incoming request header in Spring boot. Httpservletrequest.getHeader("Authorization") == null;

Asked

Viewed 271 times

0

I made a CRUD API using Spring boot and used authentication and authorization in this CRUD with JWT (JSON web token).
I have a tokenFilter class in my API which is responsible for validating tokens received from requests.
In this class I extract the token from the request headers (Httpserveletrequest) and check if it is null or poorly formatted (it does not start with "Bearer") and whenever I extract it has null value.
I do not know what is happening, because when sending this request from the front to the back I check if the token is valid and I see that it is...

I made the CORS settings in this API. I can even receive requests of the post type in it (create user request, login user request). But the post type request with credentials (token) is not processed, because the token is null.

Why this token (Authorization header) has null value in my API?!

NOTE: When I try to request with credentials, a CORS error appears in the browser console, saying that the preflighted request does not have ok status, but this does not make sense because in the API I have configured accepted origins, accepted headers and accepted methods, and that’s what the preflighted request analyzes.

Java class with the @Springbootapplication annotation:

package com.crud.rest;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;

/**
 * This is the main class of the project, because it has 
 * springBootApplication annotation. That's why it
 * is the responsible class for uploading the spring server.
 */
@SpringBootApplication
@ComponentScan("com.crud")
public class CrudApplication {

    /**
     * This static method start the spring server.
     * @param args: argument array.
     */
    public static void main(String[] args) {
        SpringApplication.run(CrudApplication.class, args);
    }
    
    /**
     * This method is responsible for filtering tokens for 
     * private routes. And here are noted all the private routes.
     * The '*' at the end of the route indicates that all the routes
     * beginning with "/v1/product/private/" are being passed as a 
     * parameter to the addUrlPatterns method.
     */
    @Bean
    public FilterRegistrationBean filterJwt(){
        FilterRegistrationBean filter = new FilterRegistrationBean();
        filter.setFilter(new TokenFilter());
        filter.addUrlPatterns("/v1/product/private/*");
        return filter;
    }

    @Configuration
    public class MyConfiguration {

        @Bean
        public WebMvcConfigurer corsConfigurer() {
            return (WebMvcConfigurer) new WebMvcConfigurerIplmts() {
                @Override
                public void addCorsMappings(CorsRegistry registry) {
                    registry.addMapping("/**")
                            .allowedOrigins("https://italomp.github.io") //this value can't be "*", because allowCredentials(true).
                            .allowedMethods("PUT", "DELETE","POST","GET")
                            .allowedHeaders("Content-type", "Authorization")
                            .allowCredentials(true)
                            .maxAge(3600);
                }
            };
        }
        
        public class WebMvcConfigurerIplmts implements WebMvcConfigurer{
            
            public WebMvcConfigurerIplmts() {
                
            }
        }
    }
}

Tokenfilter class:

package com.crud.rest;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.filter.GenericFilterBean;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;

/** 
 * This class is responsible for filtering the received tokens
 * in the requests.
 * @author Italo Modesto
 */
public class TokenFilter extends GenericFilterBean{

    /**
     * This method applies a filter on tokens to verify if it's valid.
     * 
     * Firstly, this method converts ServeletRequest to HttpServlerReques 
     * to be able to access its authorization attribute;
     * 
     * then this method extracts the authorization attribute from 
     * the request and check if the attribute is null or badly formatted (in
     * these cases, exceptions will be throws);
     * 
     * Finally, The token is extracted from the request and unpacked for it
     * can be passed as parameter to the filter chain.
     * 
     * The filter chain (in this case) call the resource requested.
     * 
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String auth = req.getHeader("Authorization");
        if(auth == null || !auth.startsWith("Bearer ")) {
            throw new ServletException("Token inexistente ou mal formatado"); //essa exceção é lançada e se eu printar a variável auth, o valor printado é null.
        }
        String token = auth.substring(7);
        try {
            Jwts.parser().setSigningKey("caneca").parseClaimsJws(token).getBody();
        }catch(SignatureException e) {
            throw new ServletException("Token inválido ou expirado");
        }
        chain.doFilter(request, response);
    }

}

Front Javascript Code:

let buttonUserRegistry = document.getElementById("buttonUserRegistry");
let buttonUserLogin = document.getElementById("buttonUserLogin");
let buttonCreateProduct = document.getElementById("buttonCreateProduct");
let buttonReadProduct = document.getElementById("buttonReadProduct");
let buttonUpdateProduct = document.getElementById("buttonUpdateProduct");
let buttonDeleteProduct = document.getElementById("buttonDeleteProduct");
let result = document.getElementById("result");

/**
 * Esse trecho de código adiciona um evento ao elemento
 * buttonUserRegistry.
 * 
 * evento: click
 * 
 * callback: função que coleta dados do novo usuario no form com 
 * id "userRegistry" e envia esses dados para p servidor através 
 * de uma requisição POT.
 * Esses daados compõe um novo usuário que será cadastrado no 
 * servidor
 */
buttonUserRegistry.addEventListener("click", function(){
    let form = document.getElementById("userRegistry");
    let inputName, inputLogin, inputPassword;
    let newUser, registerUrl, optRequest;
    for(e of form){
        if(e.name == "inputName"){
            inputName = e.value;
        }
        else if(e.name == "inputLogin"){
            inputLogin = e.value;
        }
        else if(e.name == "inputPassword"){
            inputPassword = e.value;
        }
    }
    newUser = {"name": inputName, 
               "login": inputLogin, 
               "password": inputPassword};
    registerUrl = "http://localhost:8080/api/v1/user/";
    optRequest = {method: "post",
                  headers: {"Content-type": "application/json"},
                  body: JSON.stringify(newUser)};
    fetch(registerUrl,optRequest)
    .then(resp => result.innerText = resp.status);
});

/**
 * Esse trecho de código adiciona um evento ao elemento 
 * "buttonUserLogin".
 * 
 * evento: click
 * 
 * callback: função que coleta dados do usuário que vai
 * efetuar login (esses dados estão no form que usa o
 * id "userLogin") e envia uma requisição POST para o
 * servidor.
 * E ainda, armaena o token recebido do servido no
 * localStorage.
 */
buttonUserLogin.addEventListener("click", function(){
    let form = document.getElementById("userLogin");
    let inputLogin, inputPassword;
    let user, loginUrl, optRequest;
    for(e of form){
        if(e.name == "inputLogin"){
            inputLogin = e.value;
        }
        else if(e.name == "inputPassword"){
            inputPassword = e.value;
        }
    }
    user = {"login": inputLogin, 
            "password": inputPassword};
    loginUrl = "http://localhost:8080/api/v1/auth/login";
    optRequest = {method: "post",
                  headers: {"Content-type": "application/json"},
                  body: JSON.stringify(user)};
    fetch(loginUrl, optRequest)
    .then(resp => resp.json())
    .then(tokenObject => window.localStorage.setItem("token", tokenObject.token))
    .then(result.innerText = "Login realizado com sucesso!");
});

buttonCreateProduct.addEventListener("click", async function(){
    let form = document.getElementById("createProduct");
    let inputName, inputDescription, inputPrice;
    let newProduct, createProductUrl, optRequest, token;
    for(e of form){
        if(e.name == "inputName"){
            inputName = e.value;
        }
        else if(e.name == "inputDescription"){
            inputDescription = e.value;
        }
        else if(e.name == "inputPrice"){
            inputPrice = e.value;
        }
    }
    newProduct = {"name": inputName, 
                  "description": inputDescription, 
                  "price": inputPrice};
    createProductUrl = "http://localhost:8080/api/v1/product/private/";
    token = await window.localStorage.getItem("token");
    optRequest = {method: "post",
                  headers: {"Content-type":"application/json",
                            "Authorization":"Bearer " + token},
                  credentials: "include",
                  body: JSON.stringify(newProduct)};
    if(token == undefined){
       console.log("token == undefined");
    }
    else if (token == Promise){
        console.log("token == undefined");
    }
    else{
        console.log(token);
    }
    fetch(createProductUrl, optRequest)
    .then(resp => {result.innerText = resp.status;});
});

Error displayed in browser console: Imagem do erro exibido no console do browser

Link To the front page: https://italomp.github.io/interface-CRUD/
Link To the front repository: https://github.com/italomp/interface-CRUD.git
Link to the back repository: https://github.com/italomp/API-RESTful-using-spring-boot.git
Obs: back is not hosted in the cloud. I run it locally.

No answers

Browser other questions tagged

You are not signed in. Login or sign up in order to post.