ASP.NET Core实现OpenID Connect用户认证、授权

在前面的文章中,我们讲述了ASP.NET实现OAuth2.0的四种模式。那在.NET Core中怎么实现呢?如果使用前面的方法,你会发现是行不通的,.NET Core的架构已经改变了。

在讲述认证之前,我们需要先了解一个新的协议:OpenID Connect。它基于OAuth2.0协议,增加了OpenID这个概念,使用起来会更加简便。本文不对这个协议展开分析,有兴趣的读者请参阅其他资料。在ASP.NET Core中,我们使用IdentityServer4这个开源库实现用户认证和授权的功能。

一、实现用户认证的四种模式

1.1 项目准备

在前面的文章中,我们讲到认证服务过程中有四种角色,其中有两个是认证服务器和资源服务器。我们先实现认证服务器。

1、新建一个ASP.NET Core Web应用程序,后面的页面选择“空”即可。

2、添加NuGet程序包:IdentityServer4。

3、打开launchSettings.json,修改URL为localhost:5001,如下所示:

{
  "profiles": {
    "SelfHost": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "https://localhost:5001"
    }
  }
}

4、新建一个名为Config的类,用来存放一些认证的资源(实际会通过查数据库的方法去做):

public static class Config
{
    public static IEnumerable<IdentityResource> IdentityResources =>
        new IdentityResource[]
        {
            new IdentityResources.OpenId()
        };

    public static IEnumerable<ApiScope> ApiScopes =>
        new ApiScope[]
        {
        };

    public static IEnumerable<Client> Clients =>
        new Client[]
        {
        };

    public static List<TestUser> Users =>
        new List<TestUser>()
        {
        };
}

其中,IdentityResources存放认证资源,包括OpenID(必须)、Profile(用户名、邮箱、手机等)。ApiScopes相当于访问API的权限,例如可不可以获取照片,可不可以写入等。Clients是允许访问的客户端的定义。Users是允许访问的用户。

5、修改Startup.cs,如下:

public class Startup
{
    public IWebHostEnvironment Environment { get; }

    public Startup(IWebHostEnvironment environment)
    {
        Environment = environment;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        var builder = services.AddIdentityServer()
            .AddInMemoryIdentityResources(Config.IdentityResources)
            .AddInMemoryApiScopes(Config.ApiScopes)
            .AddInMemoryClients(Config.Clients)//在客户端模式中使用
            .AddTestUsers(Config.Users);//在密码模式中使用

        // not recommended for production - you need to store your key material somewhere secure
        builder.AddDeveloperSigningCredential();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseIdentityServer();
    }
}

1.2 客户端模式

要实现客户端模式,要做的事情很简单,只需要修改Config.cs即可。

首先,我们定义一个权限,添加到ApiScopes中:

public static IEnumerable<ApiScope> ApiScopes =>
    new ApiScope[]
    {
        new ApiScope("scope1")
    };

然后,我们增加一个允许以客户端模式访问的Client,如下所示:

public static IEnumerable<Client> Clients =>
    new Client[]
    {
        new Client
        {
            ClientId = "client1",
            AllowedGrantTypes = GrantTypes.ClientCredentials,
            ClientSecrets = { new Secret("secret".Sha256()) },
            AllowedScopes = { "scope1" }
        }
    };

这个Client有什么特点呢?它的ClientID为client1,ClientSecret为secret,它允许有scope1这个权限。

好了,客户端模式这样就实现好了。

在Postman中测试:

好,得到AccessToken之后,就可以去访问API了。

1.3 密码模式

同样的,只需要修改Config.cs。首先,再增加一个权限:

public static IEnumerable<ApiScope> ApiScopes =>
    new ApiScope[]
    {
        new ApiScope("scope1"),
        new ApiScope("scope2")//新增的
    };

然后我们增加一个Client,用于密码模式认证的:

new Client
{
    ClientId = "client2",
    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
    AllowedScopes = { "scope2" },
    RequireClientSecret=false
}

认证类型修改为密码模式ResourceOwnerPassword,同时不需要验证ClientSecret。

最后,我们增加允许访问的用户:

public static List<TestUser> Users =>
    new List<TestUser>()
    {
        new TestUser()
        {
            SubjectId="1",
            Username="user1",
            Password="password1",
            Claims=new Claim[]{ new Claim("role","admin") }
        },
        new TestUser()
        {
            SubjectId="2",
            Username="user2",
            Password="password2",
            Claims=new Claim[]{ new Claim("role","user") }
        }
    };

现在我们就可以在Postman中测试了:

1.4 授权码模式

授权码模式比较复杂,其流程如下:

1、客户端向认证服务器请求授权,假设其要求的权限为scope3,重定向链接为url。

2、认证服务器向用户提供登录页面,用户完成登录和确认授权。

3、认证服务器生成授权码code,跳转至url,把code附在其后。

4、url的后台程序获取到code之后,再向认证服务器请求token。

5、认证服务器返回token。

 

首先,我们增加一个权限:

public static IEnumerable<ApiScope> ApiScopes =>
    new ApiScope[]
    {
        new ApiScope("scope1"),
        new ApiScope("scope2"),
        new ApiScope("scope3")//新增
    };

然后,我们增加一个Client:

new Client
{
    ClientId = "client3",
    AllowedGrantTypes = GrantTypes.Code,
    ClientSecrets =
    {
        new Secret("secret".Sha256())
    },
    AllowedScopes = { "scope3" },
    RedirectUris={ "https://localhost:5001/callback.html" },
    RequirePkce=false
}

现在,我们尝试在浏览器中请求授权码,输入以下地址:

https://localhost:5001/connect/authorize?client_id=client3&response_type=code&scope=scope3&redirect_uri=https://localhost:5001/callback.html

我们会发现,页面将跳转到Account/Login这个地址,当然,因为没有这个页面,404了。这是Identity4默认的登录页地址,我们可以修改(在Startup.cs的ConfigureServices函数里):

var builder = services.AddIdentityServer(options =>
{
    options.UserInteraction.LoginUrl = "/login.html";
})

这样,登录页我们改成了login.html。接下来,我们需要在这个页面完成用户名密码输入并验证这些工作。完成之后,我们需要通知Identity4,用户验证通过了。

为了实现通知,我们加入一个Controller,加入一个Post方法:

[HttpPost]
public async Task<IActionResult> Post()
{
    string returnUrl = Request.Query["returnUrl"];

    AuthenticationProperties props = new AuthenticationProperties
    {
        IsPersistent = true,
        ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromDays(30))
    };
    var isuser = new IdentityServerUser("1");

    await HttpContext.SignInAsync(isuser, props);

    return Redirect(returnUrl);
}

可以看到,这个方法获取了传进来的returnUrl这个参数,完成用户验证登记之后,就跳转到该地址了。

接下来,我们在login.html里调用这个Controller方法即可。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <script src="axios.min.js"></script>
</head>
<body>
    <div id="display_url"></div>
    <script type="text/javascript">
        let url = window.location.href;
        let pos = url.indexOf("ReturnUrl=");
        let returnUrl = url.substr(pos + 10);

        axios({
            method: 'post',
            url: '/test',
            params: {
                returnUrl: returnUrl
            }
        })
            .then(function (response) {
                console.log(response);
            })
            .catch(function (error) {
                console.log(error);
            });
    </script>
</body>
</html>

到目前为此,假设我们用浏览器再次请求授权码,将会得到以下结果:

https://localhost:5001/callback.html?code=C22C07AD90C74140F8F46415D5FA8E150F2B28E5A30023D4F915AF878D71FB37&scope=scope3

那个code就是我们要的授权码。拿到之后,我们在Postman上获取AccessToken:

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ASP.NET身份验证是一种用于验证用户身份的机制,它可以确保只有经过身份验证的用户才能访问应用程序的受保护部分。ASP.NET Core提供了多种身份验证方式,包括Cookie身份验证、JWT身份验证、OpenID Connect身份验证等。其中,Cookie身份验证是最常用的一种方式。 以下是ASP.NET Core中使用Cookie身份验证的示例代码: 1. 在Startup.cs文件的ConfigureServices方法中添加身份验证服务: ```csharp services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/Account/Login"; options.LogoutPath = "/Account/Logout"; }); ``` 2. 在Startup.cs文件的Configure方法中启用身份验证中间件: ```csharp app.UseAuthentication(); ``` 3. 在需要进行身份验证的Controller或Action上添加[Authorize]特性: ```csharp [Authorize] public class HomeController : Controller { // ... } ``` 4. 在登录Controller中使用SignInAsync方法进行登录: ```csharp public async Task<IActionResult> Login(LoginViewModel model) { // 验证用户名和密码 if (/* 验证通过 */) { // 创建用户标识 var claims = new List<Claim> { new Claim(ClaimTypes.Name, model.UserName) }; var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); // 登录 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity)); // 跳转到首页 return RedirectToAction("Index", "Home"); } // 验证失败,返回登录页面 return View(model); } ``` 5. 在注销Controller中使用SignOutAsync方法进行注销: ```csharp public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return RedirectToAction("Index", "Home"); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值