如何确保 JWT 验证发生在 React/Redux 路由器重定向之前?
How to ensure JWT validation occurs before React/Redux router redirect?
我正在使用 React/Redux、用于数据库的 Knex + Objection.Js + PostgreSQL 和用于 API 框架的 feathersjs 开发全栈 PERN 应用程序。因此,我也在前端使用 @feathersjs/client
及其身份验证包。我还使用 connected-react-router
作为我的路由。不幸的是,每当我尝试导航到受保护的路由时,负责设置用户状态的 "login" 请求(来自他们对服务器的 jwt 身份验证)在重定向将用户带到登录页面之前不会完成.
我正在通过调度一个动作来验证 React 应用程序 index.js
文件中的 jwt。
if (localStorage['feathers-jwt']) {
try {
store.dispatch(authActions.login({strategy: 'jwt', accessToken: localStorage.getItem('feathers-jwt')}));
}
catch (err){
console.log('authenticate catch', err);
}
}
该操作由 redux-saga
执行以下操作
export function* authSubmit(action) {
console.log('received authSubmit');
try {
const data = yield call(loginApi, action);
yield put({type: authTypes.LOGIN_SUCCESS, data});
} catch (error) {
console.log(error);
yield put({type: authTypes.LOGIN_FAILURE, error})
}
}
function loginApi(authParams) {
return services.default.authenticate(authParams.payload)
}
这是我的 isAuthenticated
函数和配置对象:
const isAuthenticated = connectedReduxRedirect({
redirectPath: '/login',
authenticatedSelector: state => state.auth.user !== null,
redirectAction: routerActions.replace,
wrapperDisplayName: 'UserIsAuthenticated'
});
这是应用于容器组件的 HOC
const Login = LoginContainer;
const Counter = isAuthenticated(CounterContainer);
const LoginSuccess = isAuthenticated(LoginSuccessContainer);
最后,这是渲染图
export default function (store, history) {
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route exact={true} path="/" component={App}/>
<Route path="/login" component={Login}/>
<Route path="/counter" component={Counter}/>
<Route path="/login-success" component={LoginSuccess}/>
<Route component={NotFound} />
</Switch>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
);
}
我期望发生的事情,当登录并访问时,例如/counter
是下面的
LOGIN_REQUEST 已触发操作
LOGIN_SUCCESS 动作已触发,用户已通过 JWT 认证
路由器发现 user.auth 对象不为空,因此用户是
已验证
路由器允许无重定向导航
我看到的是以下内容(手动导航到 /counter
时)
@@INIT
auth/LOGIN_REQUEST[这个不错,loggingIn: true
]
@@router/LOCATION_CHANGE
{
type: '@@router/LOCATION_CHANGE',
payload: {
location: {
pathname: '/counter',
search: '',
hash: ''
},
action: 'POP',
isFirstRendering: true
}
}
- @@router_LOCATION_CHANGE[就是这个问题]
type: '@@router/LOCATION_CHANGE',
payload: {
location: {
pathname: '/login',
hash: '',
search: '?redirect=%2Fcounter',
key: 'kdnf4l'
},
action: 'REPLACE',
isFirstRendering: false
}
}
用户导航到 /login
,这会按照当前设计将用户注销。
LOGOUT_REQUEST -> LOGIN_SUCCESS -> LOCATION_CHANGE (到 /login-success
)
再次强调,如有任何帮助,我将不胜感激,我可以根据需要提供任何其他信息。
谢谢!
-布伦登
解决方案
我今天通过查看身份验证包 feathers-reduxify-authentication
的功能解决了这个问题。在大多数情况下,重定向配置正确。
后端
authentication.js
注意多个策略,以及如何返回 context.result。这是 feathers-reduxify-authentication
正常工作所必需的。
module.exports = function (app) {
const config = app.get('authentication');
// Set up authentication with the secret
app.configure(authentication(config));
app.configure(jwt());
app.configure(local(config.local));
app.service('authentication').hooks({
before: {
create: [
authentication.hooks.authenticate(config.strategies),
],
remove: [
authentication.hooks.authenticate('jwt')
]
},
after: {
create: [
context => {
context.result.data = context.params.user;
context.result.token = context.data.accessToken;
delete context.result.data.password;
return context;
}
]
}
});
};
前端
src/feathers/index.js
这是根据eddystop的示例工程,升级到feathers 3.0+
import feathers from '@feathersjs/client';
import io from 'socket.io-client';
import reduxifyAuthentication from 'feathers-reduxify-authentication';
import reduxifyServices, { getServicesStatus } from 'feathers-redux';
import { mapServicePathsToNames, prioritizedListServices } from './feathersServices';
const hooks = require('@feathersjs/client');
const socket = io('http://localhost:3030');
const app = feathers()
.configure(feathers.socketio(socket))
.configure(hooks)
.configure(feathers.authentication({
storage: window.localStorage
}));
export default app;
// Reduxify feathers-client.authentication
export const feathersAuthentication = reduxifyAuthentication(app,
{ authSelector: (state) => state.auth.user}
);
// Reduxify feathers services
export const feathersServices = reduxifyServices(app, mapServicePathsToNames);
export const getFeathersStatus =
(servicesRootState, names = prioritizedListServices) =>
getServicesStatus(servicesRootState, names);
中间件和存储。 src/state/configureStore
redux-saga 暂时移除,等我测试完再带回来
import { createBrowserHistory } from 'history';
import { createStore, applyMiddleware, compose } from "redux";
import { routerMiddleware } from 'connected-react-router';
import createRootReducer from './ducks';
import promise from 'redux-promise-middleware';
import reduxMulti from 'redux-multi';
import rootSaga from '../sagas';
import createSagaMiddleware from 'redux-saga';
export default function configureStore(initialState) {
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|| compose;
const middlewares = [
//sagaMiddleware,
promise,
reduxMulti,
routerMiddleware(history)];
const store = createStore(
createRootReducer(history),
initialState,
composeEnhancer(
applyMiddleware(
...middlewares
)
)
);
return store;
}
根减速器,src/state/ducks/index.js
import { combineReducers } from "redux";
import { connectRouter } from 'connected-react-router';
import { reducer as reduxFormReducer } from 'redux-form';
import {feathersAuthentication, feathersServices} from '../../feathers';
import counter from './counter';
const rootReducer = (history) => combineReducers({
counter,
router: connectRouter(history),
users: feathersServices.users.reducer,
auth: feathersAuthentication.reducer,
form: reduxFormReducer, // reducers required by redux-form
});
export default rootReducer;
我正在使用 React/Redux、用于数据库的 Knex + Objection.Js + PostgreSQL 和用于 API 框架的 feathersjs 开发全栈 PERN 应用程序。因此,我也在前端使用 @feathersjs/client
及其身份验证包。我还使用 connected-react-router
作为我的路由。不幸的是,每当我尝试导航到受保护的路由时,负责设置用户状态的 "login" 请求(来自他们对服务器的 jwt 身份验证)在重定向将用户带到登录页面之前不会完成.
我正在通过调度一个动作来验证 React 应用程序 index.js
文件中的 jwt。
if (localStorage['feathers-jwt']) {
try {
store.dispatch(authActions.login({strategy: 'jwt', accessToken: localStorage.getItem('feathers-jwt')}));
}
catch (err){
console.log('authenticate catch', err);
}
}
该操作由 redux-saga
执行以下操作
export function* authSubmit(action) {
console.log('received authSubmit');
try {
const data = yield call(loginApi, action);
yield put({type: authTypes.LOGIN_SUCCESS, data});
} catch (error) {
console.log(error);
yield put({type: authTypes.LOGIN_FAILURE, error})
}
}
function loginApi(authParams) {
return services.default.authenticate(authParams.payload)
}
这是我的 isAuthenticated
函数和配置对象:
const isAuthenticated = connectedReduxRedirect({
redirectPath: '/login',
authenticatedSelector: state => state.auth.user !== null,
redirectAction: routerActions.replace,
wrapperDisplayName: 'UserIsAuthenticated'
});
这是应用于容器组件的 HOC
const Login = LoginContainer;
const Counter = isAuthenticated(CounterContainer);
const LoginSuccess = isAuthenticated(LoginSuccessContainer);
最后,这是渲染图
export default function (store, history) {
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route exact={true} path="/" component={App}/>
<Route path="/login" component={Login}/>
<Route path="/counter" component={Counter}/>
<Route path="/login-success" component={LoginSuccess}/>
<Route component={NotFound} />
</Switch>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
);
}
我期望发生的事情,当登录并访问时,例如/counter
是下面的
LOGIN_REQUEST 已触发操作
LOGIN_SUCCESS 动作已触发,用户已通过 JWT 认证
路由器发现 user.auth 对象不为空,因此用户是 已验证
路由器允许无重定向导航
我看到的是以下内容(手动导航到 /counter
时)
@@INIT
auth/LOGIN_REQUEST[这个不错,
loggingIn: true
]@@router/LOCATION_CHANGE
{
type: '@@router/LOCATION_CHANGE',
payload: {
location: {
pathname: '/counter',
search: '',
hash: ''
},
action: 'POP',
isFirstRendering: true
}
}
- @@router_LOCATION_CHANGE[就是这个问题]
type: '@@router/LOCATION_CHANGE',
payload: {
location: {
pathname: '/login',
hash: '',
search: '?redirect=%2Fcounter',
key: 'kdnf4l'
},
action: 'REPLACE',
isFirstRendering: false
}
}
用户导航到
/login
,这会按照当前设计将用户注销。LOGOUT_REQUEST -> LOGIN_SUCCESS -> LOCATION_CHANGE (到
/login-success
)
再次强调,如有任何帮助,我将不胜感激,我可以根据需要提供任何其他信息。
谢谢!
-布伦登
解决方案
我今天通过查看身份验证包 feathers-reduxify-authentication
的功能解决了这个问题。在大多数情况下,重定向配置正确。
后端
authentication.js
注意多个策略,以及如何返回 context.result。这是 feathers-reduxify-authentication
正常工作所必需的。
module.exports = function (app) {
const config = app.get('authentication');
// Set up authentication with the secret
app.configure(authentication(config));
app.configure(jwt());
app.configure(local(config.local));
app.service('authentication').hooks({
before: {
create: [
authentication.hooks.authenticate(config.strategies),
],
remove: [
authentication.hooks.authenticate('jwt')
]
},
after: {
create: [
context => {
context.result.data = context.params.user;
context.result.token = context.data.accessToken;
delete context.result.data.password;
return context;
}
]
}
});
};
前端
src/feathers/index.js
这是根据eddystop的示例工程,升级到feathers 3.0+
import feathers from '@feathersjs/client';
import io from 'socket.io-client';
import reduxifyAuthentication from 'feathers-reduxify-authentication';
import reduxifyServices, { getServicesStatus } from 'feathers-redux';
import { mapServicePathsToNames, prioritizedListServices } from './feathersServices';
const hooks = require('@feathersjs/client');
const socket = io('http://localhost:3030');
const app = feathers()
.configure(feathers.socketio(socket))
.configure(hooks)
.configure(feathers.authentication({
storage: window.localStorage
}));
export default app;
// Reduxify feathers-client.authentication
export const feathersAuthentication = reduxifyAuthentication(app,
{ authSelector: (state) => state.auth.user}
);
// Reduxify feathers services
export const feathersServices = reduxifyServices(app, mapServicePathsToNames);
export const getFeathersStatus =
(servicesRootState, names = prioritizedListServices) =>
getServicesStatus(servicesRootState, names);
中间件和存储。 src/state/configureStore
redux-saga 暂时移除,等我测试完再带回来
import { createBrowserHistory } from 'history';
import { createStore, applyMiddleware, compose } from "redux";
import { routerMiddleware } from 'connected-react-router';
import createRootReducer from './ducks';
import promise from 'redux-promise-middleware';
import reduxMulti from 'redux-multi';
import rootSaga from '../sagas';
import createSagaMiddleware from 'redux-saga';
export default function configureStore(initialState) {
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|| compose;
const middlewares = [
//sagaMiddleware,
promise,
reduxMulti,
routerMiddleware(history)];
const store = createStore(
createRootReducer(history),
initialState,
composeEnhancer(
applyMiddleware(
...middlewares
)
)
);
return store;
}
根减速器,src/state/ducks/index.js
import { combineReducers } from "redux";
import { connectRouter } from 'connected-react-router';
import { reducer as reduxFormReducer } from 'redux-form';
import {feathersAuthentication, feathersServices} from '../../feathers';
import counter from './counter';
const rootReducer = (history) => combineReducers({
counter,
router: connectRouter(history),
users: feathersServices.users.reducer,
auth: feathersAuthentication.reducer,
form: reduxFormReducer, // reducers required by redux-form
});
export default rootReducer;