理清这个流程有助于打通vue端到node端的逻辑链,加深对于项目的理解。
1. Vue层
1.1 app.js
vuex还没怎么学,这里可能理解会出错,之后学好了再来修改补充。
该文件中定义好vuex中的state,提供userLogin这个action用于dispatch触发,在其中通过api异步请求回数据后将用户名和用户角色等信息commit出去。
const state = {
userName: '',
userAuthrity: null,
userRole: []
};
const getters = {
userName: state => state.userName,
userRole: state => state.userRole
};
const setUserName = 'setUserName';
const setUserRole = 'setUserRole';
const actions = {
userLogin({commit, rootGetters}, payload) {
return new Promise((resolve, reject) => {
UserAPI.getUserInfo().then(res => {
if (res.data.code !== 0) {
return;
}
commit(setUserName, res.data.data.userName);
commit(setUserRole, res.data.data.roles);
resolve(res);
}).catch(error => {
reject(error);
});
});
}
};
const mutations = {
[setUserName](state, res) {
state.userName = res;
},
[setUserRole](state, res) {
state.userRole = res;
}
};
1.2 main.js
该文件中通过store.dispatch触发上面提到的userLogin这个action,随后通过工具类判断角色权限并进行权限分发。如果已经有权限则直接下一步,否则跳到401路径。
// 路由表根据权限动态加载
router.beforeEach((to, from, next) => {
let roles = store.state.App.userRole;
if (to.meta && to.meta.role) {
if (!roles || roles.length === 0) {
// 获取用户权限信息
store.dispatch('App/userLogin').then(res => {
roles = store.state.App.userRole;
if (Permission.hasPermission(roles, to.meta.role)) {
next();
} else {
next({path: '/401'});
}
}).catch(err => {
console.log(err);
});
} else {
if (Permission.hasPermission(roles, to.meta.role)) {
next();
} else {
next({path: '/401'});
}
}
} else {
next();
}
});
1.3 User.js
1.1中提到了通过api将用户的信息请求回来,这里的api统一管理在User.js这个文件中。
static getUserInfo() {
return APIHelper.get(`${prefix}/user`);
}
1.4 APIHelper.js
这里面做了一些axios的配置,比如创建axios实例和配置拦截器等等。重点关注两部分,第一部分是axios实例中配置了和node层连通的baseUrl,第二部分是拦截器的作用。
拦截器判断response的状态码是否为401,如果是的话说明还没有权限,则将url改为触发node层单点登录逻辑的url,留意这里拼接出来的路径(原来的url拼接上$/sso/login):
_axios.interceptors.response.use(function (res) {
if (res.data.code === 401) {
let sso = Globals.getVariables().apiUrl();
location.href = `${sso}/sso/login?callback=${encodeURIComponent(location.href).toLowerCase()}`;
}
return res;
}, function (error) {
return Promise.reject(error);
});
2. node层
2.1 UserAuthrity.js
这里对应上面的api请求,通过匹配上面vue层中接口的路径连接后端,接收到后端返回的内容后再返回给vue层。
app.get(`${prefix}/user`, function (req, res) {
let userName = req.session.userName;
res.send(responseObject(0, {userName, roles: getUserRoles(userName)}, null));
res.end();
});
2.2 SSO.js
这里进行了node层实现单点登录的核心处理,我们上面看到了axios拦截器在未鉴权的情况下会将路径替换成这个:
`${sso}/sso/login?callback=${encodeURIComponent(location.href).toLowerCase()}`
那么这里就是描述了url替换成这个之后触发的反应,可以看到上面路径的后缀是/sso/login,那么会匹配上这个接口:
app.get('/sso/login', function (req, res) {
let callback = req.query.callback || '';
let ssoUrl = `${serviceHost}/sso/redirect?callback=${encodeURIComponent(callback).toLowerCase()}`;
let target = `${ssoHost}/login?service=${encodeURIComponent(ssoUrl).toLowerCase()}&appKey=${appKey}`;
res.redirect(target);
res.end();
});
第一第二行,callback是原本的url,ssoHost是node层的地址(匹配的是1.4中的Globals.getVariables().apiUrl())
第三行,ssoHost是百度的统一认证中心地址,所以这个target相当于进入到node层后真正要被重定向到的地址。
这里不确定是怎么触发到/sso/redirect这个接口的,再看看
然后来看这个接口:
app.get('/sso/redirect', function (req, res) {
let callback = req.query.callback || '';
let ssoUrl = `${serviceHost}/sso/redirect?callback=${encodeURIComponent(callback).toLowerCase()}`;
axios.get(`${ssoHost}/serviceValidate`, {
params: {
ticket: req.query.ticket,
appKey,
service: ssoUrl
}
}).then(response => {
xmlToStr(response.data, function (err, json) {
if (!json['cas:serviceResponse']['cas:authenticationSuccess']) {
res.send('Unrecongize Ticket!');
res.end();
return;
}
let username = json['cas:serviceResponse']['cas:authenticationSuccess'][0]['cas:user'][0];
req.session.userName = username;
res.redirect(callback);
res.end();
});
}).catch(error => {
res.send('Server Error!');
res.end();
});
});
上面的/sso/login中会触发这个接口,这个接口首先向统一认证中心的某个路径请求到数据,然后经过一堆操作将用户名username提取出来存到req.session中,最后redirect回最初要请求的地址,完成了整体的闭环。
3. 总结
以下是整体流程:
- main.js中dispatch出去的action中的api用于请求用户信息
- 如果请求返回的状态码是401,说明还没登录或者权限不足,则触发APIHelper.js中的axios响应拦截器
- 响应拦截器中先根据location.href获取初始地址,再将初始地址加上/node层地址/sso/login前缀进入node层
- 触发node层中/sso/login接口
- 携带着拼接地址作为参数重定向到统一认证中心,统一认证中心会触发/sso/redirect接口 (这里不确定,但感觉应该是这么触发的),携带拼接url,appKey和统一认证中心返回的ticket作为参数发出请求。
- 从请求返回结果中获取到username后存到session中,并重定向回初始地址;如果取不到username,返回'Unrecongize Ticket!'。