没错我又来折腾前后端啦!
书接上回 AntD Pro 和 OIDC 对接,最近需要继续给前后端增加权限系统,使得不同权限的用户能够访问到的 API 接口不同。
前端配置
上一篇文章提到,可以在 AntD Pro 中的 access.ts
文件中集中管理不同的权限类别,因为增加了 oidc-client-ts
,所以我们可以使用它提供的 User
中的 profile
属性来获取角色 role
值。然后就可以根据角色来设定不同的权限。
export default function access(initialState: { currentUser?: User } | undefined) { const { currentUser } = initialState ?? {}; return { canAdmin: currentUser && currentUser.profile.role === 'Administrator', canViewOnly: currentUser && currentUser.profile.role === 'Guest' }; }
最简单的方式是直接在路由中按照模块的方式进行权限分配。routes.ts
中,每一组路由都可以设置 access
属性,值就是上面在 access.ts
中返回的对象里的 Key,例如:
export default [ { path: '/welcome', name: 'welcome', icon: 'smile', component: './Welcome', }, { path: '/', redirect: '/welcome', }, { name: 'sensitive', access: 'canAdmin', path: '/sensitive', }, { name: 'public', access: 'canAdmin, canViewOnly', path: '/public', }, // Notice that 404 must be placed at the end of routes { component: './404', }, ];
值得一提的是,access
属性是可以指定多个权限类型的,用逗号隔开即可。这一点在 AntD Pro 的官网文档中并没有说明,只在 Github 上的 Issues 和 PR 里有体现。
后端配置
API 服务器
.NET Core 已经将权限封装得相当完善了。对于想要鉴权的 Endpoint 或者 Controller,只需要贴上 [Authorize]
特性即可。而角色管理只需要设置特性的 Roles
属性即可。例如 [Authorize(Roles="Administrator")]
。同样地,可以用逗号隔开多个角色,例如 [Authorize(Roles="
。Administrator
, Guest")]
贴好特性之后,.NET Core 会在运行时自动从 JWT 中读取角色信息,默认会读取 Token 根中的 role
字段。如果 role
位于 profile
中,可以通过 ClaimActions.MapJsonKey
来进行映射。具体可以看官方文档:在 ASP.NET Core 中保留来自外部提供程序的附加声明和令牌 | Microsoft Docs。
认证服务器
上面提到,需要 Token 中带有角色信息,才能让后端正常进行判断。在鉴权成功之后,访问 OIDC 服务器的 /token
Endpoint,可以获取到 access_token
和 id_token
两种 Token。使用任意一个都可以让 API 服务器成功验证用户身份,但默认情况下 Access Token 中并不会包含诸如用户名称(name,不是 username)和用户角色之类的信息,而 ID Token 中是有的。我们当然可以直接使用 ID Token 来代替 Access Token,但这并不符合最佳实践,具体会在后面阐述。
因此我们需要进行一些配置,让认证服务器能够在 Access Token 中包含这些信息。进入 IS 的管理后台,在 Api Scopes 中,找到之前添加的作用域,在 User Claims(用户声明)中,添加要包含在 Access Token 中的信息,这里我添加了 role
和 name
。并不推荐在 Access Token 中包含用户的敏感信息。
保存之后,下一次通过 /token
获取到的 Access Token 里,就会含有我们指定的信息,这样客户端也就能够进行解析并进行进一步的操作了。
Access Token、ID Token 与 /userinfo
Endpoint
OAuth 2.0 中定义了 Access Token,而 OIDC 在 OA 2.0 的基础上增加了 ID Token。Access Token 用于在访问 API 时向 API 服务器出示,以便其能够验证访问者的身份。ID Token 则是认证服务器返回给客户端,用来标识认证过程中的信息。
在每一次请求 API 时,都需要携带 Access Token,如果我们将一些信息为了方便就放在里面(例如上面的 name
),那么就增大了信息泄露的风险。同时因为携带内容过多,也会造成不必要的带宽浪费。
设计上来说,Access Token 是在访问 API Endpoint 时需要的,如果包含了过多的用户信息,那么从功能上来讲就属于耦合了。
不过,如果只是一次性获取用户信息,认证服务器可以选择只返回 ID Token,而忽略 Access Token。
/userinfo
则是 OIDC 中定义的用于通过 Access Token 获取用户信息的 Endpoint。就本文的权限验证来讲,很自然可以想到在服务器每次收到请求的时候去访问 /userinfo
来获取用户角色,不过从设计上来说,/userinfo
是面向客户端而不是服务器的。对于服务器来说,这是不必要的开销。因此,更好的办法是在 Access Token 中来携带最少且必要的 User Claim。