最近想要整理自己代码封装成库,也十分想把自己的设计思路贴出来让大家指正,奈何时间真的不随人意。

  想要使用 OWIN 做中间件服务,该服务中包含 管线、授权 两部分。于是决定使用 webapi 、OAuth2 来做。

  在搭建途中,几乎是步步遇坎,由于对 OAuth2 内部流转的不了解,在网上到处找大牛的文献介绍,也整理不少,最后贴出。

  在捋顺出验证整个内部过程后,遇到了如何使用 js 来发送请求达到验证,以及解决了遇到的跨域问题。

  目前仅整理出了 授权码模式 ,闲言少叙,说说自己的理解吧。

1. 授权码理论,此部分摘要网上介绍较为详细的贴图

1.1 结合例子来说,当我们与某网站进行合作,需要得到他们的授权信息,在双方协商后,确立了

  1.1.1 http://127.0.0.1:10000 对方授权地址

  1.1.2 grant_type : authorization_code 授权码模式

  1.1.3 response_type : code 授权类型

  1.1.4 client_id : lightxun 客户端ID

  1.1.5 redirect_uri : http://localhost:58632 返回接收 authorization_code 的地址

  1.1.6 state : login 状态,我用来做标识当前请求状态

1.2 当我们在某网站进行登录时,会可以快捷的使用QQ、微博等账号进行授权登录。那么我们第一步点击登录方式,页面会调转到 对方授权地址,同时携带以上参数,最终获得授权码,触发【A】Authorization Request

<a href="http://127.0.0.1:10000/authorize?grant_type=authorization_code&response_type=code&client_id=lightxun&redirect_uri=http://localhost:58632/&state=login" target="_blank">authorize</a>

  1.2.1  在某网站后台授权中 首先进行验证被注册的重定向url, 此处我的做法,在其内部将传来的 client_id 与 之前协商的 client_id 进行对比,如无误,则通过验证之前协商的 redirect_uri,为了安全,防止钓鱼,该方法对应为 OpenAuthorizationServerProvider 下的 ValidateClientRedirectUri 方法。此类为继承于 OAuthAuthorizationServerProvider ,并重写其中几部重要的处理方法。

/// <summary>
/// 验证 redirect_uri, 用于验证被注册的跳转Url
/// </summary>
public override async Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
  //验证uri 为了安全,防钓鱼
  if(context.ClientId == OpenAuthorizationClients.Client.Id)
  {
    //将传来的redirectUri 与 参数验证对比, 所以该参数最好取自数据库
    context.Validated(OpenAuthorizationClients.Client.RedirectUri);
  }
}

  1.2.2 在通过了上面的方法验证后,会验证 authorization_code 请求,该方法对应为 OpenAuthorizationServerProvider 下的 ValidateAuthorizeRequest 方法

/// <summary>
/// 验证 authorization_code 的请求
/// </summary>
public override async Task ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context)
{
  // IsAuthorizationCodeGrantType : 如果“response_type”查询字符串参数为“code”,则为 True
  // IsImplicitGrantType : 如果“response_type”查询字符串参数为“token”,则为 True
  if (context.AuthorizeRequest.ClientId == OpenAuthorizationClients.Client.Id &&
    (context.AuthorizeRequest.IsAuthorizationCodeGrantType || context.AuthorizeRequest.IsImplicitGrantType))
  {
    // 满足以上条件, 标记为已验证
    context.Validated();
  }
  else
  {
    context.Rejected();
  }
}

  1.2.3 接着开始处理 authorization_code 请求,来生成授权码,该过程当中整理了一下逻辑,通过 state 来判断当前请求的状态, 如果是 login 则证明需要登录,登录后会将state修改为 validate 并重新发送验证请求。如果是 validate 则说明已成功登录,可以生成授权码了。该方法对应为 OpenAuthorizationServerProvider 下的 AuthorizeEndpoint 方法

/// <summary>
/// 处理登录逻辑
/// <summary>
[HttpPost]
[Route("OAuth/Login")]
public Model.ApiResult Login([FromBody]dynamic obj)
{
  ///验证用户名密码   IOwinContext _context = (OwinContext)Request.Properties["MS_OwinContext"];
  IOwinRequest _request = _context.Request;
  IOwinResponse _response = _context.Response;   string _redirectUri = HttpUtility.UrlDecode(_request.Headers["redirect_uri"]);
  string _clientId = _request.Headers["client_id"];
  string _host = _request.Host.Value;   return new Model.ApiResult
  {
    Data = $"/authorize?grant_type=authorization_code&response_type=code&client_id={_clientId}&redirect_uri={_redirectUri}&state=validate",
    Msg = "for test"
  };
、}
/// <summary>
/// 生成 authorization_code(authorization code 授权方式)、生成 access_token (implicit 授权模式)
/// </summary>
public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{   //implicit 授权方式
  if (context.AuthorizeRequest.IsImplicitGrantType)
  {
    var identity = new ClaimsIdentity("Bearer");
    context.OwinContext.Authentication.SignIn(identity);
    context.RequestCompleted();
  }
  //authorization code 授权方式
  else if (context.AuthorizeRequest.IsAuthorizationCodeGrantType)
  {
    // 通过state 来判断, 是登录还是 已登录的获取 code阶段
    switch (context.AuthorizeRequest.State)
    {
      //如果是登录状态, 则直接跳转, 进行账户验证
      case "login":
        context.Response.Redirect("http://" + context.Request.Host.Value + "/Page/OAuth/Login.html");
        context.RequestCompleted();
        break;
      case "validate":
        var redirectUri = context.Request.Query["redirect_uri"];
        var clientId = context.Request.Query["client_id"];
        var identity = new ClaimsIdentity(new GenericIdentity(clientId, OAuthDefaults.AuthenticationType));         var authorizeCodeContext = new AuthenticationTokenCreateContext(
          context.OwinContext,
          context.Options.AuthorizationCodeFormat,
          new AuthenticationTicket(
            identity,
            new AuthenticationProperties(new Dictionary<string, string>
            {
              {"client_id", clientId},
              {"redirect_uri", redirectUri}
            })
            {
              IssuedUtc = DateTimeOffset.UtcNow,
              ExpiresUtc = DateTimeOffset.UtcNow.Add(context.Options.AuthorizationCodeExpireTimeSpan)
            }));         await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext);
        context.Response.Redirect(redirectUri + "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token));
        context.RequestCompleted();
        break;
      default:         break;
    }
  }
}

  1.2.4 生成 authorization_code 并返回 , 该方法对应 OpenAuthorizationCodeProvider 下的 Create 方法。该类继承于 AuthenticationTokenProvider, 触发【B】Authorization Grant

/// <summary>
/// 生成 authorization_code
/// </summary>
public override void Create(AuthenticationTokenCreateContext context)
{
  context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
  _authenticationCodes[context.Token] = context.SerializeTicket();
}

1.3 在接收到授权码之后,我们携带授权码、授权类型、重定向地址,以及设置请求header中加入 Authorization 参数。去寻要 token。触发【C】Authorization Grant

  1.3.1 发送请求 js 代码如下

$.ajax({
  async: true,
  type: 'post',
  url: 'http://127.0.0.1:10000/token',
  beforeSend: function(xhr){
    xhr.setRequestHeader('Authorization', "Basic " + Base64_Encode("lightxun:shinichi"))
  },
  data: {
    grant_type: 'authorization_code',
    code: _code,  //授权码
    redirect_uri: "http://localhost:58632/"
  },
  dataType: 'json',
  contentType: 'application/json;charset=utf-8',
  success: function (data) {
    _token = data.access_token;
    _refreshToken = data.refresh_token;
  }
});

  1.3.2 后台接收请求处理,首先验证 client 身份信息(ClientId 及 ClientSecret),该方法对应 OpenAuthorizationServerProvider 下的 ValidateClientAuthentication

/// <summary>
/// 验证Client的身份(ClientId以及ClientSecret)
/// 验证 client 信息, 验证从Basic架构的请求头或Form表单提交过来的客户端凭证
/// </summary>
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
  string clientId;
  string clientSecret;
  if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
  {
    context.TryGetFormCredentials(out clientId, out clientSecret);
  }   if (clientId != OpenAuthorizationClients.Client.Id)
  {
    context.SetError("invalid_client", "client is not valid");
    return;
  }
  context.Validated();
}

  1.3.3  验证后,开始将 authorization_code 解析成 access_token,该方法对应 OpenAuthorizationCodeProvider 下的 Receive

/// <summary>
/// 由 authorization_code 解析成 access_token
/// </summary>
public override void Receive(AuthenticationTokenReceiveContext context)
{
  string value;
  if (_authenticationCodes.TryRemove(context.Token, out value))
  {
    context.DeserializeTicket(value);
  }
}

  1.3.4 验证 token,该方法对应 OpenAuthorizationServerProvider 下的 ValidateTokenRequest

/// <summary>
/// 验证 access_token 的请求
/// </summary>
public override async Task ValidateTokenRequest(OAuthValidateTokenRequestContext context)
{
  if (context.TokenRequest.IsAuthorizationCodeGrantType || context.TokenRequest.IsRefreshTokenGrantType)
  {
    context.Validated();
  }
  else
  {
    context.Rejected();
  }
}

  1.3.5 生成 token,该方法对应 OpenRefreshTokenProvider 下的 Create 。触发【D】Access Token

/// <summary>
/// 生成 refresh_token
/// </summary>
public override void Create(AuthenticationTokenCreateContext context)
{
  context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;
  context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(60);   context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
  _refreshTokens[context.Token] = context.SerializeTicket();
}

1.4 最后我们携带着 token 去请求资源即可。触发【E】Access Token 和 【F】Protected Resource

$.ajax({
  async: true,
  type: 'post',
  url: 'http://127.0.0.1:10000/token',
  beforeSend: function (xhr) {
    xhr.setRequestHeader('Authorization', "Basic " + Base64_Encode("lightxun:shinichi"))
  },
  data: {
    grant_type: 'refresh_token',
    refresh_token: _refreshToken,
  },
  dataType: 'json',
  contentType: 'application/json;charset=utf-8',
  success: function (data) {
    _token = data.access_token;
    _refreshToken = data.refresh_token;
  }
})

今天整理的有点儿多,还有许多没有写到位,后续慢慢补充,也会把全额代码贴出来,包括 OAuth 部分全额配置及代码。

期间参考过的大牛博文连接如下

https://www.cnblogs.com/xishuai/p/aspnet-webapi-owin-oauth2.html

https://www.cnblogs.com/YamatAmain/p/5029466.html

https://www.code996.cn/post/2018/token-front/

https://www.cnblogs.com/xizz/archive/2015/12/18/5056195.html

https://cloud.tencent.com/developer/article/1090017

https://cloud.tencent.com/developer/article/1340117

https://cloud.tencent.com/developer/article/1157890

https://cloud.tencent.com/developer/article/1096046

http://blogread.cn/it/article/7808?f=wb_blogread

---- 以下为跨域文献

http://jcblog.net.cn/2016/07/19/webapi%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%EF%BC%88cors%EF%BC%89%E9%85%8D%E7%BD%AE/

https://www.cnblogs.com/baiyunchen/p/5769884.html

Owin + WebApi + OAuth2 搭建授权模式(授权码模式 Part I)的更多相关文章

  1. OAuth2.0学习(1-6)授权方式3-密码模式(Resource Owner Password Credentials Grant)

    授权方式3-密码模式(Resource Owner Password Credentials Grant) 密码模式(Resource Owner Password Credentials Grant ...

  2. IdentityServer4实现OAuth2.0四种模式之授权码模式

    接上一篇:IdentityServer4实现OAuth2.0四种模式之隐藏模式 授权码模式隐藏码模式最大不同是授权码模式不直接返回token,而是先返回一个授权码,然后再根据这个授权码去请求token ...

  3. webapi框架搭建系列博客

    webapi框架搭建系列博客 webapi框架搭建-创建项目(一) webapi框架搭建-创建项目(二)-以iis为部署环境的配置 webapi框架搭建-创建项目(三)-webapi owin web ...

  4. 基于OWIN WebAPI 使用OAUTH2授权服务【授权码模式(Authorization Code)】

    之前已经简单实现了OAUTH2的授权码模式(Authorization Code),但是基于JAVA的,今天花了点时间调试了OWIN的实现,基本就把基于OWIN的OAUHT2的四种模式实现完了.官方推 ...

  5. Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)

    oauth2 server 微服务授权中心,    github源码  https://github.com/spring-cloud/spring-cloud-security 对微服务接口做一些权 ...

  6. Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端

    Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端 目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Se ...

  7. 基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】

    适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...

  8. SimpleSSO:使用Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端

    目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Security.SimpleSSO模拟OpenID认证 通过authorization co ...

  9. (转)基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】

    适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...

随机推荐

  1. 循环retian

    1.循环retian基本概念 循环retain的场景 比如A对象retain了B对象,B对象retain了A对象 循环retain的弊端 这样会导致A对象和B对象永远无法释放 循环retain的解决方 ...

  2. Solution -「CodeChef JUMP」Jump Mission

    \(\mathcal{Description}\)   Link.   有 \(n\) 个编号 \(1\sim n\) 的格子排成一排,并有三个权值序列 \(\{a_n\},\{h_n\},\{p_n ...

  3. VS Code Java 2 月更新!教育特别版:单元测试、GUI开发支持、Gradle项目创建、以及更多!

    新春快乐!欢迎来到 Visual Studio Code Java 的 2 月更新,这个月我们给大家带来了一期教育特别版.每年的年初是许多学校开学的时间,为了给学生和教师提供在 Visual Stud ...

  4. 用 Python 简单生成 WAV 波形声音文件

    Python 简单生成 WAV 波形声音文件 让机器发出声响,本身就是一件充满魔法的事情.有没有想过,用一段简单的代码,生成一个最简单的声音呢?Python 这门脚本语言的库十分丰富,借助于其中的三个 ...

  5. JMM之synchronized关键字

    对于通讯,涉及两个关键字volatile和synchronized: Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝(虽然对象及其成员变量分配的内存实在共享 ...

  6. 我的平安夜-Merry Christmas

    我的平安夜-Merry Christmas 平安夜给自己买的第一个"苹果",嘻嘻. 今夜,不想去学习技术知识点什么的, 我们就想到哪里写哪里,就简单聊聊思维方式吧. 其实我不想做今 ...

  7. 利用SQL语句(命令方式)创建数据库(以及句子解释)

    create database 课程管理    //1:create database为SQL语句,用于创建数据库.执行完之后会创建一个新数据库及存储该数据库的文件,或从先前创建的数据库文件中附加数据 ...

  8. Django整体模型理解(1)

    Django大概理解 *models:  设计一个模型,即在数据库中设计一个表,一个模型就是对应一个数据库中的表:models是一个类,类名就是表名,而类的属性就是表的字段.如下例子设计了两个表: f ...

  9. [Golang]一些书城项目中出现错误的原因和解决办法(三)

    跟着B站尚硅谷的GoWeb教程写书城项目,整理一下自己写的时候出现的错误和解决办法. 错误五:订单管理界面无法显示订单内容. 解决办法:我是直接把 day06 里的 order 文件夹粘贴过来了,or ...

  10. k8s被删除的pod一直Terminating状态

    微服务项目,部分服务无法delete,一直处于Terminating状态 kubectl get po -n gift 强制删除product:kubectl delete -n gift po/pr ...