IdentityService4 学习笔记之 Implicit Flow
隐式流验证模式:在这种模式下只需要对用户进行认证,而不需要对客户端进行认证,在这种模式下客户端是不被信任的。所以该模式特别适合于 SPA 程序。
目录
- 配置 is4 项目
- 前端页面
- 处理跨域问题
- 保护 Api
- 获取 token 中的某些信息
- 总结
配置 is4 项目
在这里我们需要修改的东西不多,只需要添加一个客户端即可。
new Client
{
//客户端id
ClientId = "spa",
//客户端显示名称
ClientName = "SPA Client",
//客户端地址
ClientUri = "http://localhost:4200",
//验证模式
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser=true,
//通过浏览器传递token?这个我记不清了请各位大佬指点
RequireConsent=true,
//过期时间
AccessTokenLifetime=60*10,
//是否需要验证客户端id
RequirePkce = true,
//是否需要客户端机密,因为客户端不被信任所以false
RequireClientSecret = false,
//可以重定向的地址
RedirectUris =
{
"http://localhost:4200/signin-oidc"
},
//登出地址
PostLogoutRedirectUris = { "http://localhost:4200" },
//允许跨域地址
AllowedCorsOrigins = { "http://localhost:4200" },
//可以请求的范围
AllowedScopes = { "openid", "profile", "api1" }
}
我们需要写的东西并不是很多,具体某些参数可以查看源码根据需要进行设置。
前端页面
我们使用 angular 作为 client,如果使用 Vue 过程大同小异。首先我们需要安装一个包:npm install oidc-client
以下整个过程使用杨老师的方法。视频地址:https://www.bilibili.com/video/av42364337?p=9
首先我们需要创建一个 service,service 主要负责:登录重定向/回调;登出重定向/回调;广播用户状态。
export class OidcService {
//创建一个user管理器
private userManage: UserManager = new UserManager(environment.oidc_Settings);
//存储当前用户
private currentUser: User;
//当有组件订阅的时候重放上一次触发的用户状态
userLoaded$ = new ReplaySubject<boolean>(1);
//检查用户是否登录
get userAvailable(): boolean {
return this.currentUser != null;
}
//返回用户信息
get user(): User {
return this.currentUser;
}
constructor() {
//清空状态
this.userManage.clearStaleState();
//当有用户登录的时候存储用户,并且广播一个ture表示登录
this.userManage.events.addUserLoaded(user => {
this.currentUser = user;
this.userLoaded$.next(true);
});
//当有用户登出的时候置空当前用户变量,并且广播一个false表示登出
this.userManage.events.addUserUnloaded(() => {
this.currentUser = null;
this.userLoaded$.next(false);
});
}
//登录方法
triggerSignIn() {
this.userManage.signinRedirect();
}
//登录回调
signInCallBack() {
this.userManage.signinCallback().then(() => {});
}
//登出回调
triggerSignOut() {
this.userManage.signoutCallback().then(() => {});
}
}
配置文件(创建 user 管理器的时候需要):
oidc_Settings: {
//认证地址
authority: "http://localhost:5001",
//客户端id
client_id: "spa",
//登入重定向地址
redirect_uri: "http://localhost:4200/signin-oidc",
//请求类型
response_type: "id_token token",
//请求范围
scope: "openid profile api1",
//登出地址
post_logout_redirect_uri: "http://localhost:4200",
//启用自动更新令牌
automaticSilentRenew: true,
//自动更新url
silence_redirect_uri: "http://localhost:4200/refresh-oidc"
}
此时运行 angular 项目,需要大家手动触发一个登录方法,我这里使用路由守卫触发,由于和本文关系不大,则不做介绍。触发之后自动跳转到 is4 的登录页面(我这个是授权页面,登陆的时候忘记截图了。。)
点击确定授权之后,is4 根据配置中的登录重定向地址,重定向到指定的页面,我这里是http://localhost:4200/signin-oidc
这个页面只是一个普通的组件请,自行创建。直接在组件中订阅 userLoaded$如果登录成功跳转到需要的页面即可。
this.oidc.userLoaded$.subscribe(x => {
if (x) {
console.log(this.oidc.user.profile);
console.log(this.oidc.user.access_token);
this.router.navigate(["./"]);
} else {
if (!environment.production) {
console.log("An error happened: user wasn't loaded.");
}
}
});
this.oidc.signInCallBack();
我们在这里打印一下 profile 和 token,看一下效果:
最后我们需要将 token 放到 http 头中,这个比较简单使用 http 拦截器加上就可以了:
export class HttpIncerept implements HttpInterceptor {
constructor(private oidc: OidcService) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (this.oidc.userAvailable) {
req = req.clone({
setHeaders: {
//注意这里有个坑,token_type和token之间有一个空格
Authorization: `${this.oidc.user.token_type} ${this.oidc.user.access_token}`
}
});
}
return next.handle(req);
}
}
处理跨域问题
既然我们已经拿到了 token 那么我们接下来需要去 Api 拿数据,这样不可避免的要处理跨域问题。在 angular 中一般使用代理来处理跨域问题。具体步骤如下:
代理配置文件:
{
"/api": {
"target": "http://localhost:5000",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}
修改 angular.json
`projects.client.architect.serve.option`中添加`"proxyConfig": "src/proxyconfig.json"`
注意:配置完代理后假如需要请求的地址为:`http://localhost:5000/api/meeting`,现在可以写成`/api/meeting`代理会进行自动处理。
保护 Api
如果上面的都做完了,那么 Api 就比较简单了,直接上代码:
services.AddAuthenticatio(IdentityServerAuthenticationDefaults.AuthenticationScheme).
AddJwtBearer(IdentityServerAuthenticationDefaults.AuthenticationScheme,o=> {
//资源名称与客户端中请求范围中的一致
o.Audience = "api1";
//认证地址
o.Authority = "http://localhost:5001";
//是否启用了https
o.RequireHttpsMetadata = false;
});
添加中间件:app.UseAuthentication();
注意在app.UseRouting();之前,在app.UseEndpoints();之后
给需要保护的Api添加[Authorize]特性
测试一下,在客户端中发送一个 get 请求已经可以获取到数据了
获取 token 中的某些信息
这里我们以 userid 为例,userid 在 token 解析完成后过来叫 sub。这里推荐一篇文章 https://www.cnblogs.com/CreateMyself/p/11123023.html (我也是看这个才知道有两种方法)
var idCliam = User.FindFirst(d => d.Type == JwtRegisteredClaimNames.Sub);
var idCliam2 = User.FindFirst(d => d.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name");
其实第一种方法的 Sub 就是常量"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"第一种方法需要安装一个包System.IdentityModel.Tokens.Jwt
这个包可以创建/验证/序列化token,还需要消除默认映射关系JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
因为我消除了映射关系,所以第二种就取不到了
总结
- 配置 is4
- angular 项目添加关于用户操作的服务(这个时候应该能拿到 token)
- 使用 http 拦截器将 token 放到 http 头中(一定要注意 type 和 token 之间的那个空格,为了这个空格我查了半小时)
- 保护 Api,配置服务,添加中间件,添加特性
- 读取想要的数据