ASP.NET Core分布式项目实战oauth2 + oidc 实现 server部分

Posted lonelyxmas

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ASP.NET Core分布式项目实战oauth2 + oidc 实现 server部分相关的知识,希望对你有一定的参考价值。

原文:【ASP.NET Core分布式项目实战】(二)oauth2 + oidc 实现 server部分

本博客根据http://video.jessetalk.cn/my/course/5视频整理(内容可能会有部分,推荐看源视频学习

资料

我们基于之前的MvcCookieAuthSample来做开发

MvcCookieAuthSample下载地址:https://files.cnblogs.com/files/wyt007/ASPNETCore%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8.rar

MvcCookieAuthSample教程地址:http://www.cnblogs.com/wyt007/p/8204731.html

正文

给网站设置默认地址     http://localhost:5000

技术图片

 

首先,我们将之前写的系统的Identity注释掉,在Startup.cs中

技术图片

第一步:添加Nuget包:IdentityServer4

我们可以在vscode中使用ctrl+P键来打开命令面板。然后输入nuget按回车,输入identityserver4后按回车来选择版本进行安装

第二步:添加Config.cs配置类

我们接下来添加一个Config.cs类,这个类是用来初始化IdentityServer的

using System.Collections;
using System.Collections.Generic;
using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;

namespace MvcCookieAuthSample
{
    public class Config
    {
        //所有可以访问的Resource
        public static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>
            {
                new ApiResource("api1","API Application")
            };
        }

        //客户端
        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client()
                {
                    ClientId="mvc",
                    AllowedGrantTypes= GrantTypes.Implicit,//模式:最简单的模式
                    ClientSecrets={//私钥
                        new Secret("secret".Sha256())
                    },
                    AllowedScopes={//可以访问的Resource
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.OpenId,
                    },
                    RedirectUris={"http://localhost:5001/signin-oidc"},//跳转登录到的客户端的地址
                    PostLogoutRedirectUris={"http://localhost:5001/signout-callback-oidc"},//跳转登出到的客户端的地址
                    RequireConsent=false//是否需要用户点击确认进行跳转
                }
            };
        }

        //测试用户
        public static List<TestUser> GetTestUsers()
        {
            return new List<TestUser>{
                new TestUser{
                    SubjectId="10000",
                    Username="wyt",
                    Password="password"
                }
            };
        }

        //定义系统中的资源
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email()
            };
        }

    }
}

 

以上使用IdentityServer4测试数据类添加数据,直接存在内存中。IdentityServer4 是支持持久化。

第三步:添加Startup配置

引用命名空间:

using IdentityServer4;

 然后打开Startup.cs 加入如下:

services.AddIdentityServer()
    .AddDeveloperSigningCredential()//添加开发人员签名凭据
    .AddInMemoryApiResources(Config.GetApiResources())//添加内存apiresource
    .AddInMemoryClients(Config.GetClients())//添加内存client
    .AddInMemoryIdentityResources(Config.GetIdentityResources())//添加系统中的资源
    .AddTestUsers(Config.GetTestUsers());//添加测试用户
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
 {
    ...
    //app.UseAuthentication();
    app.UseIdentityServer();
    ...

接着安装UI,UI部分也可以自己编写,也就是登录 注销 允许和错误。

可以到 https://github.com/IdentityServer/IdentityServer4.Quickstart.UI/tree/release 下载,然后解压到项目目录下。

也可以使用命令提示符快速安装:

powershell iex ((New-Object System.Net.WebClient).DownloadString(https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.ps1))

在项目目录下打开命令提示符,输入以上命令。

更多信息,可以查看官方readme:https://github.com/IdentityServer/IdentityServer4.Quickstart.UI/blob/release/README.md

大神博客:https://www.cnblogs.com/linezero/p/identityserver4openidconnect.html

修改LoginViewModel,将Email改为UserName,并修改强类型视图的引用部分Login.cshtml

namespace MvcCookieAuthSample.ViewModels
{
    public class LoginViewModel
    {
        //[Required]//必须的
        //[DataType(DataType.EmailAddress)]//内容检查是否为邮箱
        //public string Email { get; set; }

        [Required]
        public string UserName { get; set; }

        [Required]//必须的
        [DataType(DataType.Password)]//内容检查是否为密码
        public string Password { get; set; }
    }
}

 

修改AccountController,原来的_userManager和_signInManager不再使用

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.Cookies;
using System.Security.Claims;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
using MvcCookieAuthSample.Models;
using System.Threading.Tasks;
using MvcCookieAuthSample.ViewModels;
using IdentityServer4.Test;
using System;

namespace MvcCookieAuthSample.Controllers
{
    public class AccountController : Controller
    {
        // private UserManager<ApplicationUser> _userManager;//创建用户的
        // private SignInManager<ApplicationUser> _signInManager;//用来登录的

        private readonly TestUserStore _users;
        public AccountController(TestUserStore users)
        {
            _users=users;
        }

                // //依赖注入
        // public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
        // {
        //     _userManager = userManager;
        //     _signInManager = signInManager;
        // }

        //内部跳转
        private IActionResult RedirectToLocal(string returnUrl)
        {
            if (Url.IsLocalUrl(returnUrl))
            {//如果是本地
                return Redirect(returnUrl);
            }

            return RedirectToAction(nameof(HomeController.Index), "Home");
        }

        //添加验证错误
        private void AddError(IdentityResult result)
        {
            //遍历所有的验证错误
            foreach (var error in result.Errors)
            {
                //返回error到model
                ModelState.AddModelError(string.Empty, error.Description);
            }
        }




        public IActionResult Register(string returnUrl = null)
        {
            ViewData["returnUrl"] = returnUrl;
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel registerViewModel, string returnUrl = null)
        {
            // if (ModelState.IsValid)
            // {
            //     ViewData["returnUrl"] = returnUrl;

            //     var identityUser = new ApplicationUser
            //     {
            //         Email = registerViewModel.Email,
            //         UserName = registerViewModel.Email,
            //         NormalizedUserName = registerViewModel.Email
            //     };
            //     var identityResult = await _userManager.CreateAsync(identityUser, registerViewModel.Password);
            //     if (identityResult.Succeeded)
            //     {
            //         //注册完成登录生成cookies信息
            //         await _signInManager.SignInAsync(identityUser, new AuthenticationProperties { IsPersistent = true });

            //         return RedirectToLocal(returnUrl);
            //     }
            //     else//注册失败
            //     {
            //         //添加验证错误
            //         AddError(identityResult);
            //     }
            // }

            return View();
        }

        public IActionResult Login(string returnUrl = null)
        {
            ViewData["returnUrl"] = returnUrl;
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel  loginViewModel, string returnUrl = null)
        {
            if (ModelState.IsValid)
            {
                ViewData["returnUrl"] = returnUrl;

                //var user = await _userManager.FindByEmailAsync(loginViewModel.Email);
                var user = _users.FindByUsername(loginViewModel.UserName);

                if (user == null)
                {
                    ModelState.AddModelError(nameof(loginViewModel.UserName), "UserName not exists");
                }
                else
                {
                    if (_users.ValidateCredentials(loginViewModel.UserName,loginViewModel.Password))
                    {
                        //是否记住
                        var prop = new AuthenticationProperties()
                        {
                            IsPersistent = true,
                            ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(30))
                        };

                        //HttpContext.SignInAsync(user.SubjectId, user.Username, prop);
                        await Microsoft.AspNetCore.Http.AuthenticationManagerExtensions.SignInAsync(HttpContext, user.SubjectId, user.Username, prop);
                    }
                }
                //账号密码先不做验证,需要可以自己写
                //await _signInManager.SignInAsync(user, new AuthenticationProperties { IsPersistent = true });

                return RedirectToLocal(returnUrl);
            }

            return View();
            
        }

        //登出
        public async Task<IActionResult> Logout()
        {

            await HttpContext.SignOutAsync();
            return RedirectToAction("Index", "Home");
        }

    }
}

接下来我们需要将原来的Program.cs中的数据库初始化的内容注释掉

技术图片

然后我们就可以运行网站,输入用户名和密码进行登录了

技术图片

 

新建客户端 

新建一个MVC网站MvcClient 

dotnet new mvc --name MvcClient

给网站设置默认地址     http://localhost:5001

技术图片

MVC的网站已经内置帮我们实现了Identity,所以我们不需要再额外添加Identity引用

添加认证

services.AddAuthentication(options =>
{
    options.DefaultScheme = "Cookies";//使用Cookies认证
    options.DefaultChallengeScheme = "oidc";//使用oidc
})
.AddCookie("Cookies")//配置Cookies认证
.AddOpenIdConnect("oidc",options=> {//配置oidc
    options.SignInScheme = "Cookies";
    options.Authority = "http://localhost:5000";
    options.RequireHttpsMetadata = false;

    options.ClientId = "mvc";
    options.ClientSecret = "secret";
    options.SaveTokens = true;
});

在管道中使用Authentication

app.UseAuthentication();

 

接下来我们在HomeController上打上  [Authorize]  标签,然后启动运行

我们这个时候访问首页http://localhost:5001会自动跳转到ocalhost:5000/account/login登录

技术图片

登录之后会自动跳转回来

技术图片

我们可以在Home/About页面将claim的信息显示出来

@{
    ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dt>@claim.Value</dt>
    }
</dl>

技术图片

 

 这边的内容是根据我们在IdentityServer服务中定义的返回资源决定的

技术图片

 

以上是关于ASP.NET Core分布式项目实战oauth2 + oidc 实现 server部分的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Core分布式项目实战Docker制作dotnet core控制台程序镜像

ASP.NET Core分布式项目实战整理IdentityServer4 MVC授权Consent功能实现

无私分享:ASP.NET CORE 项目实战(第十二章)添加对SqlServerMySqlOracle的支持

ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js 搭建前后端分离项目

asp.net core 系列6 实战之 一个项目的完整结构

net core体系-web应用程序-4asp.net core2.0 项目实战-2项目说明和源码下载