Angular2 Spring Boot JWT missing Response Header

2019-06-06 03:59发布

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: enter image description here

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: enter image description here

But I look browser console I see my costum header "Authorization". enter image description here

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?

4条回答
聊天终结者
2楼-- · 2019-06-06 04:18

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);
    }
查看更多
对你真心纯属浪费
3楼-- · 2019-06-06 04:28

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

查看更多
Juvenile、少年°
4楼-- · 2019-06-06 04:41

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?!

查看更多
ゆ 、 Hurt°
5楼-- · 2019-06-06 04:42

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.

查看更多
登录 后发表回答