我有一个reactJS网络应用程序,我正在使用react-redux-firebase进行身份验证,并且工作正常。
现在,我想添加一个自己的nodejs服务器(后面有一个小数据库),并且我读到我可以使用react webapp登录名中的firebase令牌对nodeJs服务器上的用户进行身份验证,然后再让他在数据库。
但是我得到了错误
No pem found for envelope: {"alg":"RS256","kid":"f5c9aebe234da6016bd7b949168b8cd5b4ec9eeb","typ":"JWT"}
这是我在客户端获取令牌并将其发送到服务器的方式
async function updateDataInDatabase2(data, dispatch, getState) {
try {
await axios.post(`http://localhost:5000/app/todo/data`, JSON.stringify(data), {
headers: { 'Content-Type': 'application/json', 'firebase-idToken': getState().firebase.auth.stsTokenManager.accessToken },
});
} catch (err) {
console.log(err.message);
}
}
我发送的令牌如下:
'eyJhbGciOiJSUzI1NiIsImtpZCI6ImY1YzlhZWJlMjM0ZGE2MDE2YmQ3Yjk0OTE2OGI4Y2Q1YjRlYzllZWIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vam9ycm9jaC1jb25zdWx0aW5nIiwiYXVkIjoiam9ycm9jaC1jb25zdWx0aW5nIiwiYXV0aF90aW1lIjoxNTkwMDU5Njk5LCJ1c2VyX2lkIjoiVkFXSEFZVXdXS2g5akJkbE82QWFOc0ZUVFJwMSIsInN1YiI6IlZBV0hBWVV3V0toOWpCZGxPNkFhTnNGVFRScDEiLCJpYXQiOjE1OTAwNTk2OTksImV4cCI6MTU5MDA2MzI5OSwiZW1haWwiOiJtYXJrdXNhY3Rpb25qYWNrc29uQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbIm1hcmt1c2FjdGlvbmphY2tzb25AZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.KRWI4drcSSU-3YVALR4kJlLdbB9JfhSvbhsa4nR42r41IQkeRj8uIZRkA6-kmprpohlRt8v3ZlM1NsY37eNeHsI1x-KIYlVmcpE4GDETrTMNQ-NdfoyIAFqij79iPi4b7y5uDy6uFLLiKgoQKUJKIT_OFDcM1vUXeLrsA0Jn4eoA7w8wMHTqCA0vYU2YutLuBydpfdElGM0LR3yqWAf6jJFjRu45vyY1JQsI2utTBCv21B4_IDFiN7ov8NqUFjX5CHkRNiipF9P6H9USYvcPTg6AQYfMhMd8V1rtT9_EXPZMNEMnR72sIWG9Y5-Fq4fT7K1mtj7OZlKURGfKSdyybA'
这似乎是有效的令牌。
我也试图那样做:
getFirebase().auth().currentUser.getIdToken()
但是会提供这样的对象:
const token1 = {
a: 2,
b: null,
c: null,
f: null,
g: false,
h: false,
i:
'eyJhbGciOiJSUzI1NiIsImtpZCI6ImY1YzlhZWJlMjM0ZGE2MDE2YmQ3Yjk0OTE2OGI4Y2Q1YjRlYzllZWIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vam9ycm9jaC1jb25zdWx0aW5nIiwiYXVkIjoiam9ycm9jaC1jb25zdWx0aW5nIiwiYXV0aF90aW1lIjoxNTkwMDU5Njk5LCJ1c2VyX2lkIjoiVkFXSEFZVXdXS2g5akJkbE82QWFOc0ZUVFJwMSIsInN1YiI6IlZBV0hBWVV3V0toOWpCZGxPNkFhTnNGVFRScDEiLCJpYXQiOjE1OTAwNTk2OTksImV4cCI6MTU5MDA2MzI5OSwiZW1haWwiOiJtYXJrdXNhY3Rpb25qYWNrc29uQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbIm1hcmt1c2FjdGlvbmphY2tzb25AZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.KRWI4drcSSU-3YVALR4kJlLdbB9JfhSvbhsa4nR42r41IQkeRj8uIZRkA6-kmprpohlRt8v3ZlM1NsY37eNeHsI1x-KIYlVmcpE4GDETrTMNQ-NdfoyIAFqij79iPi4b7y5uDy6uFLLiKgoQKUJKIT_OFDcM1vUXeLrsA0Jn4eoA7w8wMHTqCA0vYU2YutLuBydpfdElGM0LR3yqWAf6jJFjRu45vyY1JQsI2utTBCv21B4_IDFiN7ov8NqUFjX5CHkRNiipF9P6H9USYvcPTg6AQYfMhMd8V1rtT9_EXPZMNEMnR72sIWG9Y5-Fq4fT7K1mtj7OZlKURGfKSdyybA',
};
使用该对象可能不正确。 JSON.stringyfy给了我一个解析错误。因此,我决定将String发送到“ i”属性的后面,该属性与上面相同。
在服务器端,我正在尝试像这样进行验证:
router.post('/data', async (req, res) => {
const idToken = req.header('firebase-idToken');
const userid = await verifyFirebaseIdToken(idToken);
...
// do database modification
});
const { OAuth2Client } = require('google-auth-library');
const CLIENT_ID = 'Jorroch-Consulting-Web-App';
const client = new OAuth2Client(CLIENT_ID);
const verifyFirebaseIdToken = async (token) => {
try {
const ticket = await client.verifyIdToken({
idToken: token.trim(),
audience: CLIENT_ID,
});
const payload = ticket.getPayload();
const userid = payload['sub'];
console.log('UserID: ', userid);
return userid;
} catch (error) {
console.log('Error validating firebase idToken: ', error.message);
}
};
但是在verifyIdToken中,我得到了在oauth2client.js中抛出的错误:
if (!Object.prototype.hasOwnProperty.call(certs, envelope.kid)) {
// If this is not present, then there's no reason to attempt verification
//throw new Error('No pem found for envelope: ' + JSON.stringify(envelope));
}
这是引发错误的函数的完整代码:
/**
* Verify the id token is signed with the correct certificate
* and is from the correct audience.
* @param jwt The jwt to verify (The ID Token in this case).
* @param certs The array of certs to test the jwt against.
* @param requiredAudience The audience to test the jwt against.
* @param issuers The allowed issuers of the jwt (Optional).
* @param maxExpiry The max expiry the certificate can be (Optional).
* @return Returns a promise resolving to LoginTicket on verification.
*/
async verifySignedJwtWithCertsAsync(jwt, certs, requiredAudience, issuers, maxExpiry) {
const crypto = crypto_1.createCrypto();
if (!maxExpiry) {
maxExpiry = OAuth2Client.MAX_TOKEN_LIFETIME_SECS_;
}
const segments = jwt.split('.');
if (segments.length !== 3) {
throw new Error('Wrong number of segments in token: ' + jwt);
}
const signed = segments[0] + '.' + segments[1];
let signature = segments[2];
let envelope;
let payload;
try {
envelope = JSON.parse(crypto.decodeBase64StringUtf8(segments[0]));
}
catch (err) {
err.message = `Can't parse token envelope: ${segments[0]}': ${err.message}`;
throw err;
}
if (!envelope) {
throw new Error("Can't parse token envelope: " + segments[0]);
}
try {
payload = JSON.parse(crypto.decodeBase64StringUtf8(segments[1]));
}
catch (err) {
err.message = `Can't parse token payload '${segments[0]}`;
throw err;
}
if (!payload) {
throw new Error("Can't parse token payload: " + segments[1]);
}
if (!Object.prototype.hasOwnProperty.call(certs, envelope.kid)) {
// If this is not present, then there's no reason to attempt verification
//throw new Error('No pem found for envelope: ' + JSON.stringify(envelope));
}
const cert = certs[envelope.kid];
if (envelope.alg === 'ES256') {
signature = formatEcdsa.joseToDer(signature, 'ES256').toString('base64');
}
const verified = await crypto.verify(cert, signed, signature);
if (!verified) {
throw new Error('Invalid token signature: ' + jwt);
}
if (!payload.iat) {
throw new Error('No issue time in token: ' + JSON.stringify(payload));
}
if (!payload.exp) {
throw new Error('No expiration time in token: ' + JSON.stringify(payload));
}
const iat = Number(payload.iat);
if (isNaN(iat))
throw new Error('iat field using invalid format');
const exp = Number(payload.exp);
if (isNaN(exp))
throw new Error('exp field using invalid format');
const now = new Date().getTime() / 1000;
if (exp >= now + maxExpiry) {
throw new Error('Expiration time too far in future: ' + JSON.stringify(payload));
}
const earliest = iat - OAuth2Client.CLOCK_SKEW_SECS_;
const latest = exp + OAuth2Client.CLOCK_SKEW_SECS_;
if (now < earliest) {
throw new Error('Token used too early, ' +
now +
' < ' +
earliest +
': ' +
JSON.stringify(payload));
}
if (now > latest) {
throw new Error('Token used too late, ' +
now +
' > ' +
latest +
': ' +
JSON.stringify(payload));
}
if (issuers && issuers.indexOf(payload.iss) < 0) {
throw new Error('Invalid issuer, expected one of [' +
issuers +
'], but got ' +
payload.iss);
}
// Check the audience matches if we have one
if (typeof requiredAudience !== 'undefined' && requiredAudience !== null) {
const aud = payload.aud;
let audVerified = false;
// If the requiredAudience is an array, check if it contains token
// audience
if (requiredAudience.constructor === Array) {
audVerified = requiredAudience.indexOf(aud) > -1;
}
else {
audVerified = aud === requiredAudience;
}
if (!audVerified) {
throw new Error('Wrong recipient, payload audience != requiredAudience');
}
}
return new loginticket_1.LoginTicket(envelope, payload);
}
因此令牌中一定缺少
!Object.prototype.hasOwnProperty.call(certs, envelope.kid))
是真的。
在调试模式下,我看到certs是具有两个属性的对象(960a7e8e8341ed752f12b186fa129731fe0b04c0和c1771814ba6a70693fb9412da3c6e90c2bf5b927),信封中的kid属性是f5c9aebe234da6016bd7b949168b8cd。
所以kid的长度与让我认为不是完全错误的长度相同。似乎verify尝试根据kid属性中指定的证书进行验证,但是certs对象中只有两个不同的证书。
任何人都知道令牌有什么问题吗?
我现在整天都在搜索,想把我项目的火力踢开。
答案 0 :(得分:1)
使用诸如oauth2client.js之类的外部库似乎有很多复杂性,您也可以考虑在后端使用Firebase Admin SDK尝试对令牌进行身份验证。这样,您可以发送从诺言中返回的令牌
firebase.auth().currentUser.getIdToken(true).then(idToken => axios.post...)
在node.js服务器上,您可以按照以下说明设置Admin SDK:https://firebase.google.com/docs/admin/setup
然后在收到请求时调用authenticate方法
admin.auth().verifyIdToken(idToken)
.then(function(decodedToken) {
let uid = decodedToken.uid;
}