《使用 IdentityServer 保护 Web 应用(AntD Pro 前端 + SpringBoot 后端)》中记录了使用 IdentityServer 保护前后端的过程,其中的前端工程是以 UMI Js 为例。今天,再来记录一下使用 IdentityServer 保护 Vue 前端的过程,和 UMI Js 项目使用 umi plugin 的方式不同,本文没有使用 Vue 相关的插件,而是直接使用了 oidc-client js。
另外,我对 Vue 这个框架非常不熟,在 vue-router 这里稍微卡住了一段时间,后来瞎试居然又成功了。针对这个问题,我还去 StackOverflow 上问了,但并没有收到有效的回复:https://stackoverflow.com/questions/74769607/how-to-access-vues-methods-from-navigation-guard
准备工作首先,需要在 IdentityServer 服务器端注册该 Vue 前端应用,仍然以代码写死这个客户端为例:
(资料图)
new Client{ClientId = "vue-client",ClientSecrets = { new Secret("vue-client".Sha256()) },ClientName = "vue client",AllowedGrantTypes = GrantTypes.Implicit,AllowAccessTokensViaBrowser = true,RequireClientSecret = false,RequirePkce = true,RedirectUris ={"http://localhost:8080/callback","http://localhost:8080/static/silent-renew.html",},AllowedCorsOrigins = { "http://localhost:8080" },AllowedScopes = { "openid", "profile", "email" },AllowOfflineAccess = true,AccessTokenLifetime = 90,AbsoluteRefreshTokenLifetime = 0,RefreshTokenUsage = TokenUsage.OneTimeOnly,RefreshTokenExpiration = TokenExpiration.Sliding,UpdateAccessTokenClaimsOnRefresh = true,RequireConsent = false,};在 Vue 工程里安装 oidc-client
yarn add oidc-client在 Vue 里配置 IdentityServer 服务器信息
在项目里添加一个 src/security/security.js文件:
import Oidc from "oidc-client"function getIdPUrl() {return "https://id6.azurewebsites.net";}Oidc.Log.logger = console;Oidc.Log.level = Oidc.Log.DEBUG;const mgr = new Oidc.UserManager({authority: getIdPUrl(),client_id: "vue-client",redirect_uri: window.location.origin + "/callback",response_type: "id_token token",scope: "openid profile email",post_logout_redirect_uri: window.location.origin + "/logout",userStore: new Oidc.WebStorageStateStore({store: window.localStorage}),automaticSilentRenew: true,silent_redirect_uri: window.location.origin + "/silent-renew.html",accessTokenExpiringNotificationTime: 10,})export default mgr在 main.js 里注入登录相关的数据和方法数据
不借助任何状态管理包,直接将相关的数据添加到 Vue 的 app 对象上:
import mgr from "@/security/security";const globalData = {isAuthenticated: false,user: "",mgr: mgr}方法
const globalMethods = {async authenticate(returnPath) {console.log("authenticate")const user = await this.$root.getUser();if (user) {this.isAuthenticated = true;this.user = user} else {await this.$root.signIn(returnPath)}},async getUser() {try {return await this.mgr.getUser();} catch (err) {console.error(err);}},signIn(returnPath) {returnPath ? this.mgr.signinRedirect({state: returnPath}) : this.mgr.signinRedirect();}}修改 Vue 的实例化代码
new Vue({router,data: globalData,methods: globalMethods,render: h => h(App),}).$mount("#app")修改 router
在 src/router/index.js中,给需要登录的路由添加 meta 字段:
Vue.use(VueRouter)const router = new VueRouter({{path: "/private",name: "private page",component: resolve => require(["@/pages/private.vue"], resolve),meta: {requiresAuth: true}}});export default router
接着,正如在配置中体现出来的,需要一个回调页面来接收登录后的授权信息,这可以通过添加一个 src/views/CallbackPage.vue文件来实现:
<script>export default {async created() {try {const result = await this.$root.mgr.signinRedirectCallback();const returnUrl = result.state ?? "/";await this.$router.push({path: returnUrl})}catch(e){await this.$router.push({name: "Unauthorized"})}}}</script>Sign-in in progress... 正在登录中……
然后,需要在路由里配置好这个回调页面:
import CallbackPage from "@/views/CallbackPage.vue";Vue.use(VueRouter)const router = new VueRouter({routes: {path: "/private",name: "private page",component: resolve => require(["@/pages/private.vue"], resolve),meta: {requiresAuth: true}},{path: "/callback",name: "callback",component: CallbackPage}});export default router
同时,在这个 router 里添加一个所谓的“全局前置守卫”(https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%85%A8%E5%B1%80%E5%89%8D%E7%BD%AE%E5%AE%88%E5%8D%AB),注意就是这里,我碰到了问题,并且在 StackOverflow 上提了这个问题。在需要调用前面定义的认证方法时,不能使用 router.app.authenticate,而要使用 router.apps[1].authenticate,这是我通过 inspect router发现的:
...router.beforeEach(async function (to, from, next) {let app = router.app.$data || {isAuthenticated: false}if(app.isAuthenticated) {next()} else if (to.matched.some(record => record.meta.requiresAuth)) {router.apps[1].authenticate(to.path).then(()=>{next()})}else {next()}})export default router
到了这一步,应用就可以跑起来了,在访问 /private 时,浏览器会跳转到 IdentityServer 服务器的登录页面,在登录完成后再跳转回来。
添加 silent-renew.html注意 security.js,我们启用了 automaticSilentRenew,并且配置了 silent_redirect_uri的路径为 silent-renew.html。它是一个独立的引用了 oidc-client js 的 html 文件,不依赖 Vue,这样方便移植到任何前端项目。
oidc-client.min.js首先,将我们安装好的 oidc-client 包下的 node_modules/oidc-client/dist/oidc-client.min.js文件,复制粘贴到 public/static目录下。
然后,在这个目录下添加 public/static/silent-renew.html文件。
给 API 请求添加认证头Silent Renew Token <script src="oidc-client.min.js"></script><script>console.log("renewing tokens");new Oidc.UserManager({userStore: new Oidc.WebStorageStateStore({ store: window.localStorage })}).signinSilentCallback();</script>
最后,给 API 请求添加上认证头。前提是,后端接口也使用同样的 IdentityServer 来保护(如果是 SpringBoot 项目,可以参考《[使用 IdentityServer 保护 Web 应用(AntD Pro 前端 + SpringBoot 后端) - Jeff Tian的文章 - 知乎](https://zhuanlan.zhihu.com/p/533197284) 》);否则,如果 API 是公开的,就不需要这一步了。
对于使用 axios 的 API 客户端,可以利用其 request interceptors,来统一添加这个认证头,比如:
import router from "../router"import Vue from "vue";const v = new Vue({router})const service = axios.create({// 公共接口--这里注意后面会讲baseURL: process.env.BASE_API,// 超时时间 单位是ms,这里设置了3s的超时时间timeout: 20 * 1000});service.interceptors.request.use(config => {const user = v.$root.user;if(user) {const authToken = user.access_token;if(authToken){config.headers.Authorization = `Bearer ${authToken}`;}}return config;}, Promise.reject)export default service
关键词:
-
每日观察!使用 IdentityServer 保护 Vue 前端
前情提要《使用IdentityServer保护Web应用(AntDPro前端+SpringBoot后端)》中记录了使用IdentitySer
-
煌上煌: 关于非公开发行股票摊薄即期回报的风险提示及采取填补措施和相关主体承诺事项的公告
证券代码:002695 证券简称:煌上煌 公告编号:2022-064 江西煌上煌集团食品股份有限公司 关
-
华联控股董秘回复:公司产业转型如取得实质性进展,将及时进行披露
华联控股(000036)12月19日在投资者关系平台上答复了投资者关心的问题。投资者:半年前,恒裕资本已提请上市公司经营层在前期充分调研项目的基
-
汽车之家-S(02518.HK)早盘涨超7%,截至发稿,涨5.02%,报61.75港元,成交额129.66万港元
汽车之家-S(02518 HK)早盘涨超7%,截至发稿,涨5 02%,报61 75港元,成交额129 66万港元。
-
扶沟县:高标准开展“能力作风建设年”活动 打通服务企业“最后一公里” 全球观察
为深入贯彻落实省市县关于“能力作风建设年”活动有关要求,河南省扶沟县产业集聚区高度重视、精心部署,立足产业集聚区发展实际,以“能力作
-
热消息:破坏电信设施罪罚标准是怎样的?
构成破坏公用电信设施罪的处罚标准如下:一、破坏公用电信设施危害公共安全的,处三年以上七年以下有期徒刑;如果造成严重后果的,处七年以上
-
俄土领导人通话讨论粮食走廊、天然气枢纽问题
俄罗斯总统普京与土耳其总统埃尔多安11日通电话讨论粮食走廊、在土境内建立天然气枢纽等问题。埃尔多安随后表示同意,11月初,土耳其能源和自
-
团伙盗窃500元会怎样判?|每日热点
在共同盗窃犯罪中,各共犯人基于共同的故意,实施了共同的犯罪行为,应对共同盗窃行为所造成的危害后果负责。审理共同盗窃犯罪案件,应当根据
-
隆达股份: 隆达股份:关于延长股份锁定期的公告:天天播资讯
证券代码:688231 证券简称:隆达股份 公告编号:2022-023 江苏隆
-
纽约州制造业活动在12月陷入萎缩:焦点快报
纽约州的制造业活动在12月走软,结束了该行业因通胀和利率上升导致商品需求下降而失去动能的一年。纽约联邦储备银行周四表示,其纽约州制造业
-
淡水红鲳鱼的危害(白鲳鱼和红鲳鱼) 滚动
主要是鱼足够多,所以吃什么都是补充营养的好机会。s独自生活。什么都吃?现在市场是多少?身体侧面每个鳞片的底座都是灰黑色的。这两种鱼的营
-
恩捷股份(002812)股东玉溪合益投资有限公司质押216万股,占总股本0.24%:每日速递
恩捷股份(002812)12月15日公开信息显示,股东玉溪合益投资有限公司向平安证券股份有限公司合计质押216 0万股,占总股本0 24%。质押详情见下表:截止
-
全球速读:汽车股早盘集体走低,截至发稿,小鹏汽车-W(09868.HK)跌4.42%,报38.95港元
汽车股早盘集体走低,截至发稿,小鹏汽车-W(09868 HK)跌4 42%,报38 95港元;理想汽车-W(02015 HK)跌3 85%,报78 75港元;广
-
黔东南18岁孩子抚养费给付的方式有哪些|前沿资讯
1 有条件的,可以一次性给付;2 暂时不具备条件的,可以按月或定期给付,也可按季度或年度给付。如果协商一致,一方可以承担全部抚养费的,如果
-
每日快报!走进偃师区人民检察院特聘检察联络员——李玉环
大河网讯“要不是李书记,我们这日子真不知道该怎么过。”这是家住偃师区府店镇安里村的刘某,提到李玉环时常说的一句话。李玉环是偃师区府店
-
传推特已拖欠办公楼数周租金:焦点快报
据报道,在马斯克斥资440亿美元收购推特后,为了削减成本,这家社交媒体公司已停止支付租金。
-
中金公司(03908)拟发行不超45亿元公司债券:环球速看
智通财经APP讯,中金公司发布公告,公司2022年面向专业投资者公开发行公司债券(第二期)发行规模为不超过45亿元(含45亿元)。本期债券品种一的债
-
盐湖股份(000792)12月12日主力资金净卖出1.13亿元:全球百事通
截至2022年12月12日收盘,盐湖股份(000792)报收于24 62元,下跌3 3%,换手率0 87%,成交量47 17万手,成交额11 72亿元。
-
【速看料】pharmacabcine宣布与默沙东(MRK)达成合作协议
pharmaccine宣布,公司已与MSD、默沙东达成临床合作协议。该公司将启动新型抗vista抗体PMC-309与MSD的抗pd-1疗法KEYTRUDA联合的I期联合试验。
-
盐城移动:聚焦乡村现代化 实现行政村5G网络全覆盖
近日,江苏盐城市中闸村移动5G基站正式开通,这是盐城市最后一个通5G的行政村。至此,江苏移动盐城分公司(简称“盐城移动”)已实现了本地行