说明

基于angular拦截器实现,接口报401错误时(一般情况下是token过期了)自动刷新token,避免用户在长时间未关闭页面时导致的授权过期问题

代码

  1. import {
  2. HttpErrorResponse,
  3. HttpEvent,
  4. HttpHandler,
  5. HttpInterceptor,
  6. HttpRequest
  7. } from "@angular/common/http";
  8. import {
  9. Injectable
  10. } from "@angular/core";
  11. import {
  12. throwError,
  13. Observable,
  14. BehaviorSubject,
  15. of
  16. } from 'rxjs';
  17. import {
  18. catchError,
  19. filter,
  20. take,
  21. switchMap,
  22. mergeMap,
  23. finalize
  24. } from 'rxjs/operators';
  25. import {
  26. serviceType
  27. } from "src/app/core/services/data-inquire/cus-type";
  28. import {
  29. DataInquireFuncService
  30. } from "src/app/core/services/data-inquire/data-inquire-func.service";
  31. import {
  32. ComFuncService
  33. } from "../com.func.service";
  34. @Injectable({
  35. providedIn: 'root'
  36. })
  37. export class ServiceAuthInterceptor implements HttpInterceptor {
  38. // 应用代码,按需修改
  39. authToken = '';
  40. // 排除拦截的关键字段,按需修改(高优先级)
  41. excludeList = [
  42. 'login', 'RefreshToken'
  43. ]
  44. // 拦截地址的关键字段,按需修改
  45. interceptorList = [
  46. 'geodaapi',
  47. '192.168.3.46:6360',
  48. '192.168.3.119:6360'
  49. ];
  50. private refreshTokenInProgress = false;
  51. private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
  52. constructor(
  53. private comFuncService: ComFuncService,
  54. private dataInquireFuncService: DataInquireFuncService
  55. ) { }
  56. intercept(
  57. req: HttpRequest<any>,
  58. next: HttpHandler
  59. ): Observable<HttpEvent<any>> {
  60. // 判断是否需要拦截
  61. if (this.excludeList.length) {
  62. for (let i = 0; i < this.excludeList.length; i++) {
  63. if (req.url.indexOf(this.excludeList[i]) > -1) {
  64. return next.handle(req);
  65. }
  66. }
  67. }
  68. // 添加授权Token
  69. req = this.addAuthToken(req);
  70. return next.handle(req).pipe(
  71. catchError((err: HttpErrorResponse) => {
  72. if (err && err.status === 401) {
  73. if (this.refreshTokenInProgress) {
  74. return this.refreshTokenSubject.pipe(
  75. filter(result => result !== null),
  76. take(1),
  77. switchMap(() => next.handle(this.addAuthToken(req)))
  78. );
  79. } else {
  80. this.refreshTokenInProgress = true;
  81. // 设置 refreshTokenSubject 为 null,这样随后的 API 将等到新的 token 被取回时才调用。
  82. this.refreshTokenSubject.next(null);
  83. return this.refreshToken().pipe(
  84. switchMap((success: boolean) => {
  85. this.refreshTokenSubject.next(success);
  86. return next.handle(this.addAuthToken(req));
  87. }),
  88. // 当我们调用刷新 token 方法完成时,重置 refreshTokenInProgress 为 false,
  89. // 这是为了下次 token 需要再次被刷新
  90. finalize(() => this.refreshTokenInProgress = false)
  91. );
  92. }
  93. }
  94. throw err;
  95. })
  96. );
  97. }
  98. /**
  99. * @description: 刷新token
  100. * @param {*}
  101. * @return {*}
  102. */
  103. refreshToken() {
  104. // 获取用于获取新token的refreshToken值
  105. const refreshToken = this.comFuncService.getRefreshToken();
  106. // 通过http方法请求接口获取新的有效的token
  107. return this.dataInquireFuncService.request(serviceType.INIT, 'login', 'refreshToken', {
  108. params: {
  109. refreshToken: refreshToken
  110. }
  111. }).pipe(mergeMap((res: any) => {
  112. if (res && res.code === 1) {
  113. const tokenInfo = res.data;
  114. sessionStorage.setItem('tokenInfo', JSON.stringify(tokenInfo));
  115. return of(true);
  116. }
  117. return of(false);
  118. }))
  119. }
  120. /**
  121. * @description: 添加授权Token
  122. * @param {HttpRequest} req
  123. * @return {*}
  124. */
  125. addAuthToken(req: HttpRequest<any>) {
  126. // 获取token
  127. this.authToken = this.comFuncService.getToken();
  128. // 拦截追加
  129. let authToken = null;
  130. const requestUrl = req.url.toString();
  131. // 循环判断,按需增加token
  132. for (let i = 0; i < this.interceptorList.length; i++) {
  133. const key = this.interceptorList[i];
  134. if (requestUrl.indexOf(key) !== -1) {
  135. authToken = 'Bearer ' + this.authToken;
  136. break;
  137. }
  138. }
  139. if (authToken) {
  140. req = req.clone({
  141. setHeaders: {
  142. 'Authorization': authToken
  143. }
  144. });
  145. }
  146. return req;
  147. }
  148. }

代码解析

参考文档

  1. [译]在 Angular 中使用拦截器的方式 Top 10 | 千里
  2. RxJS之BehaviorSubject - 知乎