antd pro默认访问后,直接进入主界面的欢迎。
我需要改变一下方式,所有用户需要登录后才可以访问主界面,还有配合后端返回的数据格式,以及使用token来判断是否登录。
大致思路:
- 登录成功后将token放入localStorage
- 退出登录的时候清除token
- Request拦截器:发送请求时,从localStorage获取token加入请求头中
- Request拦截器:接收返回时,统一处理错误
- SecurityLayout中,通过校验token来判断是否需要登录
冲冲冲…
更改mock数据~~/mock/user.js:
后端返回的数据格式为
{
code: {},
msg: {},
data: {},
}
因为和PRO自带的mock数据不太符合,先调整下方便测试。
登录发起请求是/api/login/account,调整下:
'POST /api/login/account': (req, res) => {
const { password, userName, type } = req.body;
if (password === 'ant.design' && userName === 'admin') {
res.send(
{
code: 1,
msg: '',
data: {
token: 'admin.token.2ijfhjf.jwijeiwfj.sjidjifs',
currentAuthority: 'admin',
type
},
}
);
return;
}
if (password === 'ant.design' && userName === 'user') {
res.send(
{
code: 1,
msg: '',
data: {
token: 'user.token.2ijfhjf.jwijeiwfj.sjidjifs',
currentAuthority: 'user',
type
},
}
);
return;
}
res.send({
code: 500,
msg: '用户名或密码错误',
data: {
token: '',
currentAuthority: 'guest',
type
},
});
},
token是后端用JWT生成
currentAuthority是就是用户所有角色,antd pro支持如‘admin’和[‘admin’,‘user’]
type是前端发起请求,从界面组件带过去参数,用来识别是登录方式,原本的mock数据把type又返回回去,暂时没研究后续有什么用处
将token放入localStorage~~/models/login.js:
变更下reducers,将token放入localStorage.
原本登录成功后,setAuthority会把用户的角色权限放入localStorage中,这里因为不打算在config中设置权限,改为通过后端校验,所以暂时先注释掉。
reducers: {
changeLoginStatus(state, { payload }) {
+ localStorage.setItem('user-token', payload.data.token);
- //setAuthority(payload.data.currentAuthority);
return { ...state, status: payload.code, type: payload.data.type };
},
},
退出登录的时候清除token~~/models/login.js:
logout() {
const { redirect } = getPageQuery(); // Note: There may be security issues, please note
if (window.location.pathname !== '/user/login' && !redirect) {
+ localStorage.removeItem('user-token');
router.replace({
pathname: '/user/login',
search: stringify({
redirect: window.location.href,
}),
});
}
},
Request拦截器~~/utils/request.js:
参考 官方request
前端在pro中打算统一通过Request发起请求,(后端对每个请求都需要识别和校验用户权限),添加拦截器把token加入请求头。
前端收到后端响应后,统一处理异常对后续开发相对方便。
默认代码里有提供errorHandler用来处理异常,
但后端响应请求的时候,比如权限不足等,我并没有返回相应的状态码。
直接返回相应状态码然后使用errorHandler来处理会更好,这里暂不考虑。
所以通过拦截器,解析响应对象来统一处理异常。
代码以后再优化:
//发送请求前,header加入token
request.interceptors.request.use(async (url, options) => {
const token = localStorage.getItem("user-token");
console.log("request-put header token: "+token);
const headers = {
'Content-Type': 'application/json',
};
if (token) {
headers['token'] = token;
}
return (
{
url: url,
options: { ...options, headers: headers },
}
);
})
//返回后的特殊处理 克隆响应对象做解析处理
request.interceptors.response.use(async (response) => {
const data = await response.clone().json();
console.log("request-json-data: " + JSON.stringify(data));
if(data.code){
//需要登录
if(data.code === 0){
window.g_app._store.dispatch({
type: 'login/logout',
});
notification.error({
message: '未登录或登录已过期,请重新登录。',
});
return;
}
if (data.code === 403) {
//router.push('/exception/403');
//return;
notification.error({
message: '权限不足,请联系管理员。',
});
}
if (data.code === 500) {
notification.error({
message: data.msg,
});
}
}
return response;
});
修改SecurityLayout:
使用token来判断是否已登录
render() {
const { isReady } = this.state;
const { children, loading } = this.props;
// 你可以把它替换成你自己的登录认证规则(比如判断 token 是否存在)
- // const { currentUser } = this.props;
- //const isLogin = currentUser && currentUser.userid;
+ const isLogin = localStorage.getItem("user-token");
const queryString = stringify({
redirect: window.location.href,
});
if ((!isLogin && loading) || !isReady) {
return <PageLoading />;
}
if (!isLogin) {
return <Redirect to={`/user/login?${queryString}`}></Redirect>;
}
return children;
}
需要登录后才进入欢迎界面的效果,去掉发送Action-fetchCurrent。在BasicLayout中,初始化也会fetchCurrent来获取用户信息,SecurityLayout里的这段猜测是方便演示用的吧(看代码的时候被这两个Layout都去fetchCurrent搞得我很晕,到现在都满是不确定性,注释掉后感觉很舒服)。
componentDidMount() {
this.setState({
isReady: true,
});
/*
const { dispatch } = this.props;
if (dispatch) {
dispatch({
type: 'user/fetchCurrent',
});
}
*/
}
因为没用到user的model,先注释掉了。原本的是{ loading , user }
export default connect(({ loading }) => ({
//currentUser: user.currentUser,
loading: loading.models.user,
}))(SecurityLayout);
清掉浏览器缓存测试下:
访问不再直接进主页了,需要登录;
用user登录,尝试访问下admin权限的http://localhost:8000/admin,跳出403页面;
这是BasicLayout原本的实现,组件Authorized中也有类似实现。
const noMatch = (
<Result
status="403"
title="403"
subTitle="Sorry, you are not authorized to access this page."
extra={
<Button type="primary">
<Link to="/user/login">Go Login</Link>
</Button>
}
/>
);
退出登录,输入个错的用户名,弹出“用户名或密码错误”的消息;
换admin登录,看到admin权限的管理页;
妥,暂时没发现什么问题。
还有登录页不需要手机登录/忘记密码/其他登录/注册用户等再慢慢调了。
先到这,后续再来实现权限及菜单。