实习-项目单点登录流程

loading 2023年05月05日 84次浏览

理清这个流程有助于打通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. 总结

以下是整体流程:

  1. main.js中dispatch出去的action中的api用于请求用户信息
  2. 如果请求返回的状态码是401,说明还没登录或者权限不足,则触发APIHelper.js中的axios响应拦截器
  3. 响应拦截器中先根据location.href获取初始地址,再将初始地址加上/node层地址/sso/login前缀进入node层
  4. 触发node层中/sso/login接口
  5. 携带着拼接地址作为参数重定向到统一认证中心,统一认证中心会触发/sso/redirect接口 (这里不确定,但感觉应该是这么触发的),携带拼接url,appKey和统一认证中心返回的ticket作为参数发出请求。
  6. 从请求返回结果中获取到username后存到session中,并重定向回初始地址;如果取不到username,返回'Unrecongize Ticket!'。