我目前正在使用 Nextjs 实现身份验证流程和一个使用 Expressjs 的 api。
我正在寻找存储 JWT token
作为身份验证 token in memory
我可以使用存储在 HTTPOnly cookie
中的刷新 token 定期刷新.
对于我的实现,我引用了 nice OSS 项目 here .
我的问题是,当我将身份验证 token 存储在 inMemoryToken
中时在登录期间,该值仅存储在可用的客户端但仍然可用的服务器端,反之亦然。
另一个例子是当我断开连接时:
inMemoryToken
等于服务器端logout()
被称为正面和inMemoryToken = null
getServerSideProps()
在服务器上调用但 inMemoryToken
服务器上的值仍然等于以前的值,因此我的用户仍然显示为已连接。 这里是
Nextjs
代码//auth.js
import { Component } from 'react';
import Router from 'next/router';
import { serialize } from 'cookie';
import { logout as fetchLogout, refreshToken } from '../services/api';
let inMemoryToken;
export const login = ({ accessToken, accessTokenExpiry }, redirect) => {
inMemoryToken = {
token: accessToken,
expiry: accessTokenExpiry,
};
if (redirect) {
Router.push('/');
}
};
export const logout = async () => {
inMemoryToken = null;
await fetchLogout();
window.localStorage.setItem('logout', Date.now());
Router.push('/');
};
const subMinutes = (dt, minutes) => {
return new Date(dt.getTime() - minutes * 60000);
};
export const withAuth = (WrappedComponent) => {
return class extends Component {
static displayName = `withAuth(${Component.name})`;
state = {
accessToken: this.props.accessToken,
};
async componentDidMount() {
this.interval = setInterval(async () => {
inMemoryToken = null;
const token = await auth();
inMemoryToken = token;
this.setState({ accessToken: token });
}, 60000);
window.addEventListener('storage', this.syncLogout);
}
componentWillUnmount() {
clearInterval(this.interval);
window.removeEventListener('storage', this.syncLogout);
window.localStorage.removeItem('logout');
}
syncLogout(event) {
if (event.key === 'logout') {
Router.push('/');
}
}
render() {
return (
<WrappedComponent
{...this.props}
accessToken={this.state.accessToken}
/>
);
}
};
};
export const auth = async (ctx) => {
console.log('auth ', inMemoryToken);
if (!inMemoryToken) {
inMemoryToken = null;
const headers =
ctx && ctx.req
? {
Cookie: ctx.req.headers.cookie ?? null,
}
: {};
await refreshToken(headers)
.then((res) => {
if (res.status === 200) {
const {
access_token,
access_token_expiry,
refresh_token,
refresh_token_expiry,
} = res.data;
if (ctx && ctx.req) {
ctx.res.setHeader(
'Set-Cookie',
serialize('refresh_token', refresh_token, {
path: '/',
expires: new Date(refresh_token_expiry),
httpOnly: true,
secure: false,
}),
);
}
login({
accessToken: access_token,
accessTokenExpiry: access_token_expiry,
});
} else {
let error = new Error(res.statusText);
error.response = res;
throw error;
}
})
.catch((e) => {
console.log(e);
if (ctx && ctx.req) {
ctx.res.writeHead(302, { Location: '/auth' });
ctx.res.end();
} else {
Router.push('/auth');
}
});
}
const accessToken = inMemoryToken;
if (!accessToken) {
if (!ctx) {
Router.push('/auth');
}
}
return accessToken;
};
//page index.js
import Head from 'next/head';
import { Layout } from '../components/Layout';
import { Navigation } from '../components/Navigation';
import { withAuth, auth } from '../libs/auth';
const Home = ({ accessToken }) => (
<Layout>
<Head>
<title>Home</title>
</Head>
<Navigation />
<div>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</div>
</Layout>
);
export const getServerSideProps = async (ctx) => {
const accessToken = await auth(ctx);
return {
props: { accessToken: accessToken ?? null },
};
};
export default withAuth(Home);
部分 express js代码:app.post('/api/login', (req, res) => {
const { username, password } = req.body;
....
const refreshToken = uuidv4();
const refreshTokenExpiry = new Date(new Date().getTime() + 10 * 60 * 1000);
res.cookie('refresh_token', refreshToken, {
maxAge: 10 * 60 * 1000,
httpOnly: true,
secure: false,
});
res.json({
access_token: accessToken,
access_token_expiry: accessTokenExpiry,
refresh_token: refreshToken,
user,
});
});
app.post('/api/refresh-token', (req, res) => {
const refreshToken = req.cookies['refresh_token'];
.....
const newRefreshToken = uuidv4();
const newRefreshTokenExpiry = new Date(
new Date().getTime() + 10 * 60 * 1000,
);
res.cookie('refresh_token', newRefreshToken, {
maxAge: 10 * 60 * 1000,
httpOnly: true,
secure: false,
});
res.json({
access_token: accessToken,
access_token_expiry: accessTokenExpiry,
refresh_token: newRefreshToken,
refresh_token_expiry: newRefreshTokenExpiry,
});
});
app.post('/api/logout', (_, res) => {
res.clearCookie('refresh_token');
res.sendStatus(200);
});
我的理解是,即使 let inMemoryToken
声明一次,它的两个单独实例将在运行时可用,一个客户端和一个服务器端,并且修改不会影响另一个。我对吗?
在这种情况下,由于 auth 方法既可以在服务器上调用,也可以在客户端上调用,如何解决这个问题?
最佳答案
我创建了一个示例,展示了如何使用 session 在内存中为单个用户跨请求存储信息。如果您只是对代码感兴趣,可以查看底部的代码框。
有两件事要记住:
但是,您可以使用存储在客户端 cookie 中的通用 ID,并将数据存储在内存中,并通过请求上的 session 访问它。
当您收到请求时,您可以检查 cookie 是否存在于请求的 header 中,如果存在则尝试从内存中加载 session ,如果不存在或无法加载,则创建一个新 session 。
| Incoming Request
| |--> Check the cookie header for your session key
| |--> If cookie exists load cookie
| |--> Else create session + use 'set-cookie' header to tell the client it's session key
| |--> Do stuff with the data stored in the session
为了能够做到这一点,我们需要有一些方法来存储 session 和与之关联的数据。您说您只想将数据存储在内存中。const memoryStore = new Map();
好的,现在我们有了内存存储,但是我们如何让它在请求中保持不变呢?让我们将它存储为一个全局对象。const MEMORY_STORE = Symbol.for('__MEMORY_STORE');
const getMemoryStore = () => {
if (!global[MEMORY_STORE]) {
global[MEMORY_STORE] = new Map();
}
return global[MEMORY_STORE];
};
完美,现在我们可以调用getMemoryStore
访问持久化的数据。现在我们要创建一个处理程序,尝试从请求中加载 session ,否则创建一个新 session 。const SESSION_KEY = '__my_session_id';
const loadSession = (req, res) => {
const memory = getMemoryStore();
const cookies = parseCookies(req.headers.cookie);
const cookieSession = cookies[SESSION_KEY];
// check to make sure that cookieSession is defined and that it exists in the memory store
if (cookieSession && memory.has(cookieSession)) {
const session = memory.get(cookieSession);
req.session = session;
// do something with the session
} else {
// okay the session doesn't exists so we need to create one, create the unique session id
const sessionId = uuid();
const session = { id: sessionId };
memory.set(sessionId, session);
// set the set-cookie header on the response with the session ID
res.setHeader('set-cookie', `${SESSION_KEY}=${sessionId}`);
req.session = session;
}
};
现在我们可以在服务器端的任何地方调用它,它将加载或创建 session 。例如,您可以从 getServerSideProps
调用它export const getServerSideProps = ({ req, res }) => {
loadSession(req, res);
// our session exists on req.session !!
return { props: { ... } };
};
我制作了一个带有工作示例的代码框:https://codesandbox.io/s/distracted-water-biicc?file=/utils/app.js
关于javascript - Nextjs - 存储在内存中的身份验证 token + 仅 HTTP cookie 中的刷新 token ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64379817/