Angular 5:从拦截器中的 http 响应标头获取授权

Posted

技术标签:

【中文标题】Angular 5:从拦截器中的 http 响应标头获取授权【英文标题】:Angular 5 : get authorization from http response headers in the interceptor 【发布时间】:2019-05-20 18:13:50 【问题描述】:

一般来说,我是 Angular 的新手,我成功地使用带有 Spring Security 的 jwt 实现了身份验证。 但是现在我在到期后实现了刷新令牌,并且在检索我从后端的响应标头中发送的新刷新令牌时遇到了问题。

这里是负责令牌管理的 spring 安全过滤器:

package ma.dataprotect.extend.cqradar.web.security;

import static ma.dataprotect.extend.cqradar.ws.security.utils.SecurityConstants.HEADER_STRING;
import static ma.dataprotect.extend.cqradar.ws.security.utils.SecurityConstants.ROLES;
import static ma.dataprotect.extend.cqradar.ws.security.utils.SecurityConstants.SECRET;
import static ma.dataprotect.extend.cqradar.ws.security.utils.SecurityConstants.TOKEN_PREFIX;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import ma.dataprotect.extend.cqradar.commun.model.User;
import ma.dataprotect.extend.cqradar.ws.security.utils.SecurityConstants;
import ma.dataprotect.extend.cqradar.ws.security.utils.TokenUtility;

public class JWTAuthorizationFilter extends BasicAuthenticationFilter 

    private TokenUtility tokenUtility;

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager, TokenUtility tokenUtility) 
        super(authenticationManager);
        this.tokenUtility = tokenUtility;
    

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException 
        String header = request.getHeader(HEADER_STRING);

        if (header == null || !header.startsWith(TOKEN_PREFIX)) 
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setHeader("content-type", MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
            response.getWriter().write(SecurityConstants.convertObjectToJson(
                    "No '" + HEADER_STRING + "' header or does not start with '" + TOKEN_PREFIX + "'"));
            // chain.doFilter(request, response);
            return;
        

        UsernamePasswordAuthenticationToken authenticationToken = null;
        try 
            authenticationToken = getAuthentication(header);
         catch (ExpiredJwtException e) 
            User result = tokenUtility.refreshToken(header); // refresh Token 
            if (result == null) // this user needs to be logged out
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                response.setHeader("content-type", MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
                response.getWriter().write(SecurityConstants.convertObjectToJson("JWT expired needs login"));
                return;
             else // send the refreshed token in the headers
                String refreshedToken = TOKEN_PREFIX.concat(result.getToken());
                response.setStatus(HttpServletResponse.SC_OK);
                response.setHeader("content-type", MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
                response.addHeader(HEADER_STRING, refreshedToken); // Here !!!!
                authenticationToken = getAuthentication(refreshedToken);
            
        
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        chain.doFilter(request, response);
    

    @SuppressWarnings("unchecked")
    private UsernamePasswordAuthenticationToken getAuthentication(String token) 
        if (token == null)
            return null;

        Claims claims = Jwts.parser().setSigningKey(SECRET.getBytes()).parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                .getBody();
        String user = claims.getSubject();
        if (user == null)
            return null;

        List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority(
                ((ArrayList<LinkedHashMap<String, String>>) claims.get(ROLES)).get(0).get("authority")));

        return new UsernamePasswordAuthenticationToken(user, null, authorities);
    


这是前端的拦截器,它应该从响应中检索标头:

import HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpErrorResponse, HttpResponse from "@angular/common/http";
import Observable from "rxjs/Observable";
import Injectable from "@angular/core";
import AuthHelperService from "./auth-helper.service";
import 'rxjs/add/operator/do';

@Injectable()
export class AuthInterceptor implements HttpInterceptor 
  token;

  constructor(private authHelper: AuthHelperService) 
  

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
    this.token = this.authHelper.getToken();

    if(this.token)      
      return next.handle(this.addTokenToRequest(req, this.token)).do(
        (event: HttpEvent<any>) => // catching the http response 
          if (event instanceof HttpResponse) 
            let resp : HttpResponse<any> = event;
            console.log("Success : intercept called req headers:",resp.headers.get('Authorization')); // always gives null in case of refresh (no token !! the Big Problem !!)
            console.log(this.authHelper.getToken());
            if(resp.headers.get('Authorization')!=null)// not null in case of refresh 
              this.authHelper.setToken(resp.headers.get('Authorization')); // reset the new token here to be used in next http requests
                        
            return next.handle(this.addTokenToRequest(req, this.authHelper.getToken()));
          
      ,
        (err: any) => 
          if (err instanceof HttpErrorResponse) 
            switch ((<HttpErrorResponse>err).status) 
              case 400:
                this.authHelper.logout();
                return next.handle(req) ;
              default :
                return next.handle(this.addTokenToRequest(req, this.token));  
            
          
        
      );
    
    return next.handle(this.addJsonToRequest(req));
  

  private addTokenToRequest(request: HttpRequest<any>, token: string) : HttpRequest<any> 
    return request.clone( headers: request.headers.append("Authorization", token).append("Content-Type","application/json"));
  

  private addJsonToRequest(request: HttpRequest<any>) : HttpRequest<any> 
    return request.clone( headers: request.headers.append("Content-Type", "application/json"));
  


编辑添加 auth-helper.service.ts 代码:

import  Injectable, OnDestroy  from "@angular/core";
import  Router  from "@angular/router";
import  User  from "../../models/User";

@Injectable()
export class AuthHelperService implements OnDestroy  

  private tokenKey: string;
  private userKey: string;
  private roleKey: string;
  private userIdKey: string;
  private userStateKey : string;

  constructor(private router: Router) 
    this.tokenKey = 'id_token';
    this.userKey = 'user_full_name';
    this.roleKey = 'user_role';
    this.userIdKey = 'user_id';
    this.userStateKey = 'user';
    this.forceNavToProf()
    console.log("AuthHelperService constructor called");
  

  forceNavToProf()
    this.router.events.subscribe((event) => 
      if(this.isLoggedIn())
       if(this.isNew()) 
        if(event['url'] != undefined && event['url'] != '/logout' && event['url'] != '/profile')
          this.router.navigate(['/profile']);
                
       
      
    );
  

  ngOnDestroy()
    console.log('AuthHelperService ngOnDestroy called')
    sessionStorage.removeItem(this.tokenKey);
    sessionStorage.removeItem(this.userKey);
    sessionStorage.removeItem(this.roleKey);
    sessionStorage.removeItem(this.userIdKey);
    sessionStorage.removeItem(this.userStateKey);
    this.router.navigate(['/login']);
  

  setSession(loggedInUser: User) 
    sessionStorage.setItem(this.tokenKey, 'Bearer ' + loggedInUser.token);
    sessionStorage.setItem(this.userKey, loggedInUser.firstName+' '+loggedInUser.lastName);
    sessionStorage.setItem(this.roleKey, loggedInUser.role.roleName);
    sessionStorage.setItem(this.userIdKey, loggedInUser.id); 
    sessionStorage.setItem(this.userStateKey, String(loggedInUser.isNew));
    this.homePage(loggedInUser.isNew);
    console.log('session values are set correctly ');
  

  setSessionAfterRefresh(loggedInUser: User) 
    sessionStorage.setItem(this.tokenKey, 'Bearer ' + loggedInUser.token);
    sessionStorage.setItem(this.userKey, loggedInUser.firstName+' '+loggedInUser.lastName);
    sessionStorage.setItem(this.roleKey, loggedInUser.role.roleName);
    sessionStorage.setItem(this.userIdKey, loggedInUser.id); 
    sessionStorage.setItem(this.userStateKey, String(loggedInUser.isNew)); 
    console.log('session values are set correctly ',this.router.url);   
  

   // Solution de depanage pour debloquer 
  refresh()   
    console.log('refresh ',this.router.url);
    let currentUrl = this.router.url;
    this.router.navigate(['']);
    setTimeout(() =>  this.router.navigate([currentUrl]); , 100 );

  

  homePage(userState:boolean)
    if(userState)
      this.router.navigate(['/profile']);
    else
      this.router.navigate(['/']);
        
  

  logout() 
    console.log('AuthHelperService logout called');
    sessionStorage.removeItem(this.tokenKey);
    sessionStorage.removeItem(this.userKey);
    sessionStorage.removeItem(this.roleKey);
    sessionStorage.removeItem(this.userIdKey);
    sessionStorage.removeItem(this.userStateKey);
    this.router.navigate(['/login']);
  

  public isLoggedIn(): boolean 
    return this.getToken() != null;
    

  public getLoggedInUserName(): string
    return sessionStorage.getItem(this.userKey);
  

  public getLoggedInUserID(): number
    return Number(sessionStorage.getItem(this.userIdKey));
  

  public isNew(): boolean
    return sessionStorage.getItem(this.userStateKey) === "true";
  

  public setToken(token : string) 
    sessionStorage.setItem(this.tokenKey, token);
  

  public getToken(): string 
    return sessionStorage.getItem(this.tokenKey);
  

  public getIsAdmin(): boolean 
    return 'ROLE_ADMINISTRATOR' === sessionStorage.getItem(this.roleKey);
  

在拦截器的代码中,它给出了 null 但是当我检查开发工具中的网络选项卡时,我可以看到它是由后端过滤器成功发送的。我错过了什么?

【问题讨论】:

【参考方案1】:

要解决此问题,您必须像这样(对于 JAVA)从后端过滤器显式公开响应的标头:

response.addHeader("Access-Control-Expose-Headers",HEADER_STRING);

我希望对某人有所帮助。

【讨论】:

以上是关于Angular 5:从拦截器中的 http 响应标头获取授权的主要内容,如果未能解决你的问题,请参考以下文章

Angular 7 - 如何在某些响应状态代码上重试 http 请求?

使用 Angular 4.3 的 HttpInterceptor 拦截 HTTP 响应标头

Angular 4.3 - HTTP 拦截器 - 刷新 JWT 令牌

从 API 响应读取响应标头 - Angular 5 + TypeScript

Angular 4 和 OAuth - 拦截 401 响应,刷新访问令牌并重试请求

从 Angular 中的 HttpInterceptor 访问 HTTP 错误响应正文