Img src 链接中的授权头

我有一个使用 jwt进行认证的 api。我使用这个 api 的 vuejs应用程序。我试图在应用程序中显示一个图像使用

<img src="my/api/link" />

但是 api期望 Authorization报头包含 jwt token

我可以像这样在浏览器请求中添加头文件吗(回答这里的一些问题使我相信这是不可能的) ?

是否有任何方法绕过它(使用 js)或者我应该改变 api本身?

60220 次浏览

You can not perform authentication on images which are directly used as href in img tag. If you really want this type of authentication on your images, then it's better to fetch them using ajax and then embed in your html.

<img src="/api/images/yourimage.jpg?token=here-your-token">

In the backend you validate JWT from queryparam.

By default browsers are sending cookies. You can prevent cookie sending in fetch if you set header's {credentials: 'omit'}. MDN

Full fetch example:

const user = JSON.parse(localStorage.getItem('user'));
let headers = {};


if (user && user.token) {
headers = { 'Authorization': 'Bearer ' + user.token };
}


const requestOptions = {
method: 'GET',
headers: headers,
credentials: 'omit'
};


let req = await fetch(`${serverUrl}/api/v2/foo`, requestOptions);
if (req.ok === true) {
...

Now, when you are login in, in your website, the webapp could save to credentials into both localStorage and cookie. Example:

let reqJson = await req.json();
// response is: {token: 'string'}
//// login successful if there's a jwt token in the response
if (reqJson.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify({token: reqJson.token}));
document.cookie = `token=${reqJson.token};`; //set the cookies for img, etc
}

So your webapp uses localStorage, just like your smartphone application. Browser gets all the static contents (img, video, a href) by sending cookies by default.

On the server side, you can copy the cookie to authorization header, if there is none.

Node.js+express example:

.use(function(req, res, next) { //function setHeader
if(req.cookies && req.headers &&
!Object.prototype.hasOwnProperty.call(req.headers, 'authorization') &&
Object.prototype.hasOwnProperty.call(req.cookies, 'token') &&
req.cookies.token.length > 0
) {
//req.cookies has no hasOwnProperty function,
// likely created with Object.create(null)
req.headers.authorization = 'Bearer ' + req.cookies.token.slice(0, req.cookies.token.length);
}
next();
})

I hope it helps someone.

There is another one method adds headers to HTTP request. Is it "Intercept HTTP requests". https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Intercept_HTTP_requests

A workaround I often use is by leveraging a so-called nonce API endpoint. When calling this endpoint from the authenticated client, a short living string (could be a guid) is generated (for instance 30 seconds) and returned. Server-side you could of course add current user data to the nonce if you wish.

The nonce is then added to the image's query string and be validated server-side. The cost of this workaround is an extra API call.The main purpose of the workaround however is an increased security warrantee. Works like a charm ;) .

This is my solution based on Tapas' answer and this question How to display Base64 images in HTML?:

let jwtHeader = {headers: { Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX..."}
let r = await axios.get(`/path/image`, {...jwtHeader, responseType:"arraybuffer"});
let d = Buffer.from(r.data).toString('base64');
let a = document.createElement('img');


a.src = `data:image/png;base64, ${d}`;
a.width = 300;
a.height = 300;
document.getElementById("divImage").appendChild(a);

In this case the html would have a <div id="divImage">

You can use a Service Worker to intercept the img fetchs and add the Authorization header with the JWT token before hitting the server. Described in:

Try this

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试获取图片</title>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
</head>
<body>
<img id="test-img" src="" />
<script>
var request = new XMLHttpRequest();
request.open('GET','http://127.0.0.1/appApi/profile/cust/idcard/2021/12/30/533eed96-da1b-463b-b45d-7bdeab8256d5.jpg', true);
request.setRequestHeader('token', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDA5MTg1NTgsInVzZXJpZCI6IjMxIn0.TQmQE9E1xQwvVeAWRov858W2fqYpSMxZPCGlgvtcUDc');
request.responseType = 'arraybuffer';
request.onload = function(e) {
var data = new Uint8Array(this.response);
var raw = String.fromCharCode.apply(null, data);
var base64 = btoa(raw);
var src = "data:image;base64," + base64;
        

document.getElementById("test-img").src = src;
};
        

request.send();
</script>
</body>
</html>