Spring MVC Angular的Keycloak未发送图像的承载者身份验证

jfgube3f  于 2023-03-08  发布在  Spring
关注(0)|答案(3)|浏览(132)

我正在运行一个带有Sping Boot MVCAPI和Keycloak的Angular应用程序。我按照https://www.npmjs.com/package/keycloak-angular下的说明配置了Angular,它通常工作正常。
在我的Angular组件中,我使用img标记来包含图像,这些图像由我的 Boot 应用程序提供

<img *ngIf="id" src="api/v1/profile/image/{{id}}">

MVC端点如下所示:

@GetMapping(value = "profile/image/{id}")
    public @ResponseBody byte[] getProfileImage(@AuthenticationPrincipal Jwt jwt, @PathVariable String id)
            throws IOException {
            ...
            }

问题是我在第一次加载映像时收到401响应代码。

以下是我的主要发现:

  • 映像的加载不(从不)包含“授权:bearer ...”请求报头(与通过HttpClient发送的请求相反)
  • 第一个失败的请求将具有set-cookie响应标头
  • 连续请求将包含cookie作为请求标头的一部分,并且将有效

有谁能指出我能做什么吗?keycloak能被配置成这样吗?图像的加载将包含承载者认证报头。

cigdeys3

cigdeys31#

<img *ngIf="id" src="api/v1/profile/image/{{id}}">

这种图像是由浏览器加载的,而不是由HttpClient加载的。而且HttpClient已经配置为在您的情况下注入Authorization标头,而不是浏览器,所以这是问题的根本原因。
您可以使用HttpClient从URL api/v1/profile/image/{{id}}加载图像内容,然后使用base64编码将该图像内容内联到HTML中:

<img src="data:<correct content type>;base64, <base 64 data of the image content>" />

e.g.:
<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA
    AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
        9TXL0Y4OHwAAAABJRU5ErkJggg==" />

这是一个很好的解决方案,小图像(这种方法通常用于社会图标)。
或者您可以在创建图像之前将授权保存到cookie中,格式与您的服务提供商所期望的格式相同)。然后浏览器将在每个请求中发送该授权cookie。当然,跨域cookie可能会有问题。

brjng4g3

brjng4g32#

Angular功能强大,足以满足您的服务器需求,您可以强制img标记通过HttpClient请求图像。
因此,从编写一个管道开始,它接受图像URL并执行HTTP请求 *,同时放置Authorization头 *。
管道可以如下所示:

@Pipe({
  name: 'authImage'
})
export class AuthImagePipe implements PipeTransform {

  constructor(
    private http: HttpClient,
    private auth: AuthService, // your service that provides the authorization token
  ) {}

  async transform(src: string): Promise<string> {
    const token = this.auth.getToken();
    const headers = new HttpHeaders({'Authorization': `Bearer ${token}`});
    const imageBlob = await this.http.get(src, {headers, responseType: 'blob'}).toPromise();
  }

}

您还需要将blob响应转换为base64字符串,以便将其传递给src属性。然后将base64字符串放入Promise中,以便可以依次传递给async管道。这里我们提供了它们以及错误处理(服务器错误,如40X,50X),使用 fallback image

async transform(src: string): Promise<string> {
  const token = this.auth.getToken();
  const headers = new HttpHeaders({'Authorization': `Bearer ${token}`});
  try {
    const imageBlob = await this.http.get(src, {headers, responseType: 'blob'}).toPromise();
    const reader = new FileReader();
    return new Promise((resolve, reject) => {
      reader.onloadend = () => resolve(reader.result as string);
      reader.readAsDataURL(imageBlob);
    });
  } catch {
    return 'assets/fallback.png';
  }
}

这样,你就可以用这样的词:

<img [src]="'api/v1/profile/image/' | authImage | async"/>

您还可以为新版本的img标记编写一个附加组件,如下所示:

@Component({
  selector: 'image',
  template: '<img [src]="src | authImage | async" [alt]="alt"/>',
})
export class ImageComponent {

  @Input() src: string;
  @Input() alt = '';
}

然后像这样使用它:

<image src="api/v1/profile/image/"></image>
tpgth1q7

tpgth1q73#

HTML中的[src]属性用于指定图像、视频、音频文件或包含在网页中的任何其他外部资源的源URL,它是HTML标准的一部分,而不是Angular 。
调整它的一种方法是使用数据绑定。

@Component({
  selector: 'app-my-component',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.css']
})
export class MyComponent {
  imageUrl: string;

  constructor(private http: HttpClient, private sanitizer: DomSanitizer) {
    this.http.get('https://example.com/image.png', { responseType: 'blob' })
      .subscribe(blob => {
        const url = URL.createObjectURL(blob);
        this.imageUrl = this.sanitizer.bypassSecurityTrustUrl(url);
      });
  }
}

// In your template
<img [src]="imageUrl" />

相关问题