详解.NET实现OAuth2.0四种模式(4)授权码模式

一、授权码模式认证流程

授权码模式是最为安全,同时也最为复杂的一种模式。其认证流程如下图所示:

可能有的人会问,为什么要多出授权码这个东西呢,没有它不行吗?(我自己一开始也有这个疑问)确实是不行,我们来看一下。

我们拿“同城交友”APP微信登录这个例子来解释。首先,用户名、密码就是我们的微信号和密码,我们是不能交给“同城交友”这个APP的。所以,我们在微信官方的登录页上完成了微信号和密码的输入(当然,也可能是手机扫一扫)。完成之后,我们能不能直接把AccessToken交给APP去访问资源呢?还不能,因为任何APP都可以打开微信登录页,如果这时APP就拿到了AccessToken,安全性就难以保证了。这时,如果我们提出授权码这个概念,这个问题就能解决。APP在拿到授权码之后,它并不能直接访问资源。它需要用自己的AppID和AppSecret去换AccessToken。因为APP都要在微信平台上注册之后,才会有AppSecret,所以这个APP就被认定为安全的。这样一来,安全性就能够得到保障了。

二、服务器配置

首先,我们需要修改网站入口类Startup。其完整代码如下所示:

[assembly: OwinStartup(typeof(AuthorizationCodeMode.Startup))]//让整个网站的入口为Startup这个类
namespace AuthorizationCodeMode
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            //配置OAuth
            ConfigureOAuth(app);

            //配置网站路由等信息
            HttpConfiguration config = new HttpConfiguration();
            Register(config);

            //允许跨域访问
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

            app.UseWebApi(config);
        }

        private void ConfigureOAuth(IAppBuilder app)
        {
            OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp = true,//允许http而非https访问
                AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,//激活授权码模式
                TokenEndpointPath = new PathString("/token"),//访问host/token获取AccessToken
                AuthorizeEndpointPath = new PathString("/auth"),//访问host/auth获取授权码
                AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),//AccessToken在30分钟后过期
                Provider = new AuthorizationServerProvider(),//AccessToken的提供类
                AuthorizationCodeProvider = new AuthorizationCodeProvider()//授权码的提供类                
            };

            app.UseOAuthAuthorizationServer(OAuthServerOptions);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

        }

        private static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
            jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        }
    }
}

跟密码模式相比,改动的就是服务器的配置项。添加了AuthenticationMode、AuthorizeEndpointPath和AuthorizationCodeProvider三个属性。

认证流程可分为两步,一是获取授权码,二是获取AccessToken。

三、获取授权码

在获取授权码时,我们需要请求host/auth这个地址,输入的参数有以下要求:

(1)grant_type,必须为authorization_code。

(2)response_type,必须为code。

(3)client_id,客户端ID。

(4)redirect_uri,重定向地址,如为http://abc.com/,则请求授权码完成后,将会重定向到:http://abc.com/code=[授权码]。

(5)scope,授权范围,可选。

(6)state,客户端状态,可选。

首先,我们创建一个授权码的提供类,如下所示:

public class AuthorizationCodeProvider : IAuthenticationTokenProvider
{
    private Dictionary<string, string> codes = new Dictionary<string, string>();

    public void Create(AuthenticationTokenCreateContext context)
    {
        string new_code = Guid.NewGuid().ToString("n");
        context.SetToken(new_code);
        codes.Add(new_code, context.SerializeTicket());
    }

    public Task CreateAsync(AuthenticationTokenCreateContext context)
    {
        Create(context);
        return Task.FromResult<object>(null);
    }

    public void Receive(AuthenticationTokenReceiveContext context)
    {
        string code = context.Token;
        if (codes.ContainsKey(code))
        {
            string value = codes[code];
            codes.Remove(code);
            context.DeserializeTicket(value);
        }
    }

    public Task ReceiveAsync(AuthenticationTokenReceiveContext context)
    {
        Receive(context);
        return Task.FromResult<object>(null);
    }
}

其实现就是用一个GUID作为code,调用Create的时候放进字典中,调用Receive的时候从字典中移除。

接着,我们需要修改AuthorizationServerProvider类。在获取授权码的过程中,这个类有以下函数被调用:

(1)ValidateClientRedirectUri,验证重定向URI是否合法。

(2)ValidateAuthorizeRequest,验证请求的参数是否合法。

(3)AuthorizeEndpoint,生成Code(调用AuthorizationCodeProvider.Create),将结果添加到重定向URI中。

完整示例代码如下:

public override async Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
    context.Validated(context.RedirectUri);
}

public override async Task ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context)
{
    if (context.AuthorizeRequest.ClientId.StartsWith("AAA"))
    {
        context.Validated();
    }
    else
    {
        context.Rejected();
    }
}

public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
    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.Write(Uri.EscapeDataString(authorizeCodeContext.Token));//为了测试方便,直接打印出code
    //context.Response.Redirect(redirectUri + "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token));//正常使用时是把code加在重定向网址后面

    context.RequestCompleted();
}

使用Postman测试结果:

四、获取AccessToken

在获取AccessToken时,我们需要请求host/token这个地址,输入的参数有以下要求:

(1)grant_type,必须为authorization_code。

(2)code,上面所获取到的授权码。

(3)client_id,客户端ID。

(4)redirect_uri,重定向地址,必须跟上面的一致。

同样,在获取AccessToken过程中,AuthorizationServerProvider类中有几个函数会被调用:

(1)ValidateClientAuthentication,验证客户端是否合法。

(2)AuthorizationCodeProvider.Receive,调出授权码背后的信息。

(3)ValidateTokenRequest,验证请求是否合法。

完整测试代码如下:

public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
    context.TryGetFormCredentials(out string clientId, out string clientSecret);

    if (!clientId.StartsWith("AAA"))
    {
        context.SetError("invalid_client", "未授权的客户端");
        return;
    }
    context.Validated();
}

public override async Task ValidateTokenRequest(OAuthValidateTokenRequestContext context)
{
    if (context.TokenRequest.IsAuthorizationCodeGrantType)
    {
        context.Validated();
    }
    else
    {
        context.Rejected();
    }
}

使用Postman测试的结果如下图所示:

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值