Javascript 就无状态前端客户端而言,这个JWT逻辑有多安全?
我不认为我的确切设置对这个问题有什么影响,但我只是在我的React和React本机应用程序中注意到了这一点,突然意识到它们实际上并没有检查存储的JWT的任何类型的有效性 代码如下:Javascript 就无状态前端客户端而言,这个JWT逻辑有多安全?,javascript,reactjs,react-native,local-storage,jwt,Javascript,Reactjs,React Native,Local Storage,Jwt,我不认为我的确切设置对这个问题有什么影响,但我只是在我的React和React本机应用程序中注意到了这一点,突然意识到它们实际上并没有检查存储的JWT的任何类型的有效性 代码如下: const tokenOnLoad = localStorage.getItem('token') if (tokenOnLoad) store.dispatch({ type: AUTH_USER }) 这可能不是一个真正的问题,因为令牌被附加到头上,服务器将忽略没有有效令牌的任何请求,但是有没有一种方法可以将
const tokenOnLoad = localStorage.getItem('token')
if (tokenOnLoad) store.dispatch({ type: AUTH_USER })
这可能不是一个真正的问题,因为令牌被附加到头上,服务器将忽略没有有效令牌的任何请求,但是有没有一种方法可以将其升级到更好的状态(即:由于令牌格式错误或有人入侵了自己的'token'
)而更安全&加载UI的可能性更小
以下是附加到每个请求的令牌:
networkInterface.use([{
applyMiddleware(req, next) {
if (!req.options.headers) req.options.headers = {}
const token = localStorage.getItem('token')
req.options.headers.authorization = token || null
next()
}
}])
我是否应该添加一些逻辑来至少检查令牌的长度,或者对其进行解码并检查其中是否有用户id?或者,当服务器这样做时,这是在浪费CPU和时间吗
我只是想看看是否有任何低成本的方法来进一步验证令牌和强化应用程序
我还使用了一个requireAuth()
higher-order组件,如果用户没有登录,它会将用户踢出。我觉得如果应用程序确实localStorage.setItem('token'),可能会出现一些糟糕的用户体验,
“lkjashkjdf”)
您的解决方案不是最优的,因为您声明您没有真正检查用户令牌的有效性
让我详细介绍一下您如何处理它:
1。开始时检查令牌
redux persist
完成Provider
组件中的加载和注入dispatch
中检查令牌的有效性
如果令牌已过期,则发送一个操作以使令牌无效。否则,继续,就像什么都没发生一样
看看下面的中间件token.js
我编写了一整套代码样本供您使用,并在需要时进行调整 我在下面提出的解决方案与路由器无关。 如果您使用
react路由器
,也可以与任何其他路由器一起使用
应用程序入口点:App.js
export function submitLogin(values) {
return (dispatch, getState) => {
dispatch({ type: 'readLogin' });
return fetch({}) // !!! Call your API with the login & password !!!
.then((result) => {
dispatch(setToken(result));
setUserToken(result.token);
})
.catch(error => dispatch(addLoginError(error)));
};
}
export function setToken(result) {
return {
type: 'setToken',
...result,
};
}
export function addLoginError(error) {
return {
type: 'addLoginError',
error,
};
}
export function setLoginValues(values) {
return {
type: 'setLoginValues',
values,
};
}
export function setLoginErrors(errors) {
return {
type: 'setLoginErrors',
errors,
};
}
export function invalidateToken() {
return {
type: 'invalidateToken',
};
}
import { combineReducers } from 'redux';
import assign from 'lodash/assign';
import jwtDecode from 'jwt-decode';
export default combineReducers({
isInvalidated,
isFetching,
token,
tokenExpires,
userId,
values,
errors,
});
function isInvalidated(state = true, action) {
switch (action.type) {
case 'readLogin':
case 'invalidateToken':
return true;
case 'setToken':
return false;
default:
return state;
}
}
function isFetching(state = false, action) {
switch (action.type) {
case 'readLogin':
return true;
case 'setToken':
return false;
default:
return state;
}
}
export function values(state = {}, action) {
switch (action.type) {
case 'resetLoginValues':
case 'invalidateToken':
return {};
case 'setLoginValues':
return assign({}, state, action.values);
default:
return state;
}
}
export function token(state = null, action) {
switch (action.type) {
case 'invalidateToken':
return null;
case 'setToken':
return action.token;
default:
return state;
}
}
export function userId(state = null, action) {
switch (action.type) {
case 'invalidateToken':
return null;
case 'setToken': {
const { user_id } = jwtDecode(action.token);
return user_id;
}
default:
return state;
}
}
export function tokenExpires(state = null, action) {
switch (action.type) {
case 'invalidateToken':
return null;
case 'setToken':
return action.expire;
default:
return state;
}
}
export function errors(state = [], action) {
switch (action.type) {
case 'addLoginError':
return [
...state,
action.error,
];
case 'setToken':
return state.length > 0 ? [] : state;
default:
return state;
}
}
查看登录
组件是否位于路由器顶部
import React from 'react';
import { Provider } from 'react-redux';
import { browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import createRoutes from './routes'; // Contains the routes
import { initStore, persistReduxStore } from './store';
import { appExample } from './container/reducers';
import Login from './views/login';
const store = initStore(appExample);
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { rehydrated: false };
}
componentWillMount() {
persistReduxStore(store)(() => this.setState({ rehydrated: true }));
}
render() {
const history = syncHistoryWithStore(browserHistory, store);
return (
<Provider store={store}>
<Login>
{createRoutes(history)}
</Login>
</Provider>
);
}
}
中间件:token.js
export function submitLogin(values) {
return (dispatch, getState) => {
dispatch({ type: 'readLogin' });
return fetch({}) // !!! Call your API with the login & password !!!
.then((result) => {
dispatch(setToken(result));
setUserToken(result.token);
})
.catch(error => dispatch(addLoginError(error)));
};
}
export function setToken(result) {
return {
type: 'setToken',
...result,
};
}
export function addLoginError(error) {
return {
type: 'addLoginError',
error,
};
}
export function setLoginValues(values) {
return {
type: 'setLoginValues',
values,
};
}
export function setLoginErrors(errors) {
return {
type: 'setLoginErrors',
errors,
};
}
export function invalidateToken() {
return {
type: 'invalidateToken',
};
}
import { combineReducers } from 'redux';
import assign from 'lodash/assign';
import jwtDecode from 'jwt-decode';
export default combineReducers({
isInvalidated,
isFetching,
token,
tokenExpires,
userId,
values,
errors,
});
function isInvalidated(state = true, action) {
switch (action.type) {
case 'readLogin':
case 'invalidateToken':
return true;
case 'setToken':
return false;
default:
return state;
}
}
function isFetching(state = false, action) {
switch (action.type) {
case 'readLogin':
return true;
case 'setToken':
return false;
default:
return state;
}
}
export function values(state = {}, action) {
switch (action.type) {
case 'resetLoginValues':
case 'invalidateToken':
return {};
case 'setLoginValues':
return assign({}, state, action.values);
default:
return state;
}
}
export function token(state = null, action) {
switch (action.type) {
case 'invalidateToken':
return null;
case 'setToken':
return action.token;
default:
return state;
}
}
export function userId(state = null, action) {
switch (action.type) {
case 'invalidateToken':
return null;
case 'setToken': {
const { user_id } = jwtDecode(action.token);
return user_id;
}
default:
return state;
}
}
export function tokenExpires(state = null, action) {
switch (action.type) {
case 'invalidateToken':
return null;
case 'setToken':
return action.expire;
default:
return state;
}
}
export function errors(state = [], action) {
switch (action.type) {
case 'addLoginError':
return [
...state,
action.error,
];
case 'setToken':
return state.length > 0 ? [] : state;
default:
return state;
}
}
这是为了检查令牌是否仍然有效而添加的中间件
如果令牌不再有效,将触发分派以使其无效
import jwtDecode from 'jwt-decode';
import isAfter from 'date-fns/is_after';
import * as actions from '../container/actions';
export default function checkToken({ dispatch, getState }) {
return next => (action) => {
const login = getState().login;
if (!login.isInvalidated) {
const exp = new Date(jwtDecode(login.token).exp * 1000);
if (isAfter(new Date(), exp)) {
setTimeout(() => dispatch(actions.invalidateToken()), 0);
}
}
return next(action);
};
}
登录组件
这里最重要的是测试是否(!login.isInvalidated)
如果登录数据未失效,则表示用户已连接,且令牌仍然有效。(否则它将使用中间件token.js
失效)
登录缩减器
export function submitLogin(values) {
return (dispatch, getState) => {
dispatch({ type: 'readLogin' });
return fetch({}) // !!! Call your API with the login & password !!!
.then((result) => {
dispatch(setToken(result));
setUserToken(result.token);
})
.catch(error => dispatch(addLoginError(error)));
};
}
export function setToken(result) {
return {
type: 'setToken',
...result,
};
}
export function addLoginError(error) {
return {
type: 'addLoginError',
error,
};
}
export function setLoginValues(values) {
return {
type: 'setLoginValues',
values,
};
}
export function setLoginErrors(errors) {
return {
type: 'setLoginErrors',
errors,
};
}
export function invalidateToken() {
return {
type: 'invalidateToken',
};
}
import { combineReducers } from 'redux';
import assign from 'lodash/assign';
import jwtDecode from 'jwt-decode';
export default combineReducers({
isInvalidated,
isFetching,
token,
tokenExpires,
userId,
values,
errors,
});
function isInvalidated(state = true, action) {
switch (action.type) {
case 'readLogin':
case 'invalidateToken':
return true;
case 'setToken':
return false;
default:
return state;
}
}
function isFetching(state = false, action) {
switch (action.type) {
case 'readLogin':
return true;
case 'setToken':
return false;
default:
return state;
}
}
export function values(state = {}, action) {
switch (action.type) {
case 'resetLoginValues':
case 'invalidateToken':
return {};
case 'setLoginValues':
return assign({}, state, action.values);
default:
return state;
}
}
export function token(state = null, action) {
switch (action.type) {
case 'invalidateToken':
return null;
case 'setToken':
return action.token;
default:
return state;
}
}
export function userId(state = null, action) {
switch (action.type) {
case 'invalidateToken':
return null;
case 'setToken': {
const { user_id } = jwtDecode(action.token);
return user_id;
}
default:
return state;
}
}
export function tokenExpires(state = null, action) {
switch (action.type) {
case 'invalidateToken':
return null;
case 'setToken':
return action.expire;
default:
return state;
}
}
export function errors(state = [], action) {
switch (action.type) {
case 'addLoginError':
return [
...state,
action.error,
];
case 'setToken':
return state.length > 0 ? [] : state;
default:
return state;
}
}
请随时问我任何问题,或者如果您需要我进一步解释哲学。这太好了。谢谢你发帖。我需要一些时间来全面分析它,但听起来我应该引入一个应用程序正在初始化的状态,我已经有了动作创建者。非常类似于使用
null
初始化,并且在值移动到true
或false
之前不信任状态。我肯定需要一个步骤来检查令牌的到期时间。这就是我可以非常有效地使其无效的地方。为了准确起见,我们使用Redis与各自的用户一起维护一个有效令牌列表。服务器维护用户的白名单,而不是被禁止用户的黑名单。通过这种方式,我们可以很容易地控制流量。您的示例代码在架构上非常强大。我认为您的根级别登录组件做得非常好。我可能会在完成后以另一个答案的形式更新我的问题,因为它相当复杂,应该显示给其他人进行示例。基本上,我所需要做的就是添加几行代码来检查JWT,并使用结果在redux中更新isInvalidated
,这将被输入根auth组件。