简体   繁体   中英

Angular2 Spring Boot JWT missing Response Header

I use Angular2, Angular-cli, Spring Boot 1.4.0 and jwt. When I sign in my Angular2 client I can not get jwt token.

My security config is:

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable() // disable csrf for our requests.
                .authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/api/user/signup").permitAll()
                .antMatchers(HttpMethod.POST, "/api/user/login").permitAll()
                .anyRequest().authenticated()
                .and()
                // We filter the api/login requests
                .addFilterBefore(new JWTLoginFilter("/api/user/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
                // And filter other requests to check the presence of JWT in header
                .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
               .permitAll().and().csrf().disable();
    }
}

My TokenAuthenticationService is :

public class TokenAuthenticationService {

    private final long EXPIRATIONTIME = 1000 * 60 * 60 * 24 * 10; // 10 days
    private final String secret = "ThisIsASecret";
    private final String tokenPrefix = "Bearer";
    private final String headerString = "Authorization";
    public void addAuthentication(HttpServletResponse response, String username)
    {
        // We generate a token now.
        String JWT = Jwts.builder()
                    .setSubject(username)
                    .setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
                    .signWith(SignatureAlgorithm.HS512, secret)
                    .compact();
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.setHeader(headerString, tokenPrefix + " "+ JWT);
        response.getHeaderNames().stream()
    .forEach(System.out::println);
    }
   }

When I send sign in request with postman, I recieve response like this: 在此处输入图片说明

But I send sign in request my Angular2 application I can not recieve response header named "Authorization" custom header. My response object is like this: 在此处输入图片说明

But I look browser console I see my costum header "Authorization". 在此处输入图片说明

My Angular2 code is:

@Injectable()
export class LoginService {
  private authEvents: Subject<AuthEvent>;
  private cred: AccountCredentials;

  constructor(private http: JsonHttpService ){
    this.authEvents = new Subject<AuthEvent>();
    this.cred = new AccountCredentials();
  }


  login(email: string, password: string) {
    this.cred.password = password;
    this.cred.username = email;
    return this.http.post('http://localhost:9090/api/user/login', this.cred)
    .do((resp: Response) => {
      localStorage.setItem('jwt', resp.headers.get('Authorization'));
      this.authEvents.next(new DidLogin());
    });
  }

  logout(): void {
    localStorage.removeItem('jwt');
    this.authEvents.next(new DidLogout());
  }

  isSignedIn(): boolean {
    return localStorage.getItem('jwt') !== null;
  }
}

export class DidLogin {
}
export class DidLogout {
}

export type AuthEvent = DidLogin | DidLogout;

And My JsonHttpService is:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import {
  Http,
  RequestOptionsArgs,
  RequestOptions,
  Response,
  Headers
} from '@angular/http';

const mergeAuthToken = (options: RequestOptionsArgs = {}) => {
  let newOptions = new RequestOptions({}).merge(options);
  let newHeaders = new Headers(newOptions.headers);
  const jwt = localStorage.getItem('jwt');

  if (jwt && jwt !== 'null') {
    newHeaders.set('Authorization', jwt);
  }
 newHeaders.set('content-type', 'application/x-www-form-urlencoded; charset=utf-8');

  // newHeaders.set('Access-Control-Allow-Origin', '*');
  newOptions.headers = newHeaders;
  return newOptions;
};

@Injectable()
export class JsonHttpService {

  constructor(private http: Http) { }


  get(url: string, options?: RequestOptionsArgs): Observable<Response> {
    return this.http.get(url, mergeAuthToken(options));
  }

  post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
    return this.http.post(url, body, mergeAuthToken(options));
  }

  put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
    return this.http.put(url, body, mergeAuthToken(options));
  }

  delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
    return this.http.delete(url, mergeAuthToken(options));
  }

  patch(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
    return this.http.patch(url, body, mergeAuthToken(options));
  }

  head(url: string, options?: RequestOptionsArgs): Observable<Response> {
    return this.http.head(url, mergeAuthToken(options));
  }


}

So why I can not recieve my jwt token and add my browser localStorage?

The browser does not expose custom headers to the app by default.

You will need the following header in your Backend Cors config

'Access-Control-Expose-Headers' 'Authorization';

Note that even if the headers are present in the dev console your app can't read them if they are not exposed by you server application.

Are you running your Angular2 app and springboot on a different port? If so, have you enable CORS in your springboot application?

Add withCredentials: true to your Angualr2 post header

this.http.post('http://localhost:9090/api/user/login', 
  { withCredentials: true },
  this.cred)

For more springboot JWT work with Angular, checkout Springboot JWT Starter

The Best way to solve your problem would be to add cors configurations in your application as described here https://spring.io/blog/2015/06/08/cors-support-in-spring-framework . You can also do it as follows:

@Configuration
public class RestConfigs {
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addExposedHeader(MyUtils.AUTHENTICATION); //Header String
        config.addAllowedHeader("*");
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("DELETE");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

I had similar issue with Angular 6 and couldn't find solution for few days. After googling, frustrating, thinking and who knows what else I found the answer :)

At the end I had to switch to pure javascript and this solution works for me:

http.open('POST', url, true);

http.setRequestHeader('Content-type', 'application/json');
http.setRequestHeader('Accept', 'application/json, text/plain, */*');

http.onreadystatechange = function() {
  if (http.readyState == 4 && http.status == 200) {
    console.log('xhr:', http.getAllResponseHeaders());
  }
}
http.send(params);

http.getAllResponseHeaders() returns all headers similar like in web browser. Hope this will help you too.

I found well written post about CORS, which can cause troubles, on https://www.html5rocks.com/en/tutorials/cors/

Also as @evans-m said headers have to be exposed .

I am not still sure is it problem with Angular, browser or maybe even spring?!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM