• 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. 读取想要的数据
  • 相关阅读:
    《Attack ML Models
    对抗攻击基础知识(完结)
    解决github release下载慢/下载失败的问题
    《Effective Python:编写高质量Python代码的59个有效方法》读书笔记(完结)
    MacBook常用软件
    解决python3读写中文txt时UnicodeDecodeError : 'ascii' codec can't decode byte 0xc4 in position 5595: ordinal not in range(128) on line 0的问题
    Shell中的函数库
    Mysql登录报1045错误
    宝塔面板关键目录解析
    CentOS7 yum报 Cannot retrieve metalink for repository: epel/x86_64. Please verify its path解决方法
  • 原文地址:https://www.cnblogs.com/zyz-Notes/p/12097826.html
Copyright © 2020-2023  润新知