IdentityService4 学习笔记之 Implicit Flow

  隐式流验证模式:在这种模式下只需要对用户进行认证,而不需要对客户端进行认证,在这种模式下客户端是不被信任的。所以该模式特别适合于 SPA 程序。

目录

  1. 配置 is4 项目
  2. 前端页面
  3. 处理跨域问题
  4. 保护 Api
  5. 获取 token 中的某些信息
  6. 总结

配置 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();

  因为我消除了映射关系,所以第二种就取不到了

总结

  1. 配置 is4
  2. angular 项目添加关于用户操作的服务(这个时候应该能拿到 token)
  3. 使用 http 拦截器将 token 放到 http 头中(一定要注意 type 和 token 之间的那个空格,为了这个空格我查了半小时)
  4. 保护 Api,配置服务,添加中间件,添加特性
  5. 读取想要的数据
原文地址:https://www.cnblogs.com/zyz-Notes/p/12097826.html