Core篇——初探IdentityServer4(OpenID Connect模式)
Core篇——初探IdentityServer4(OpenID Connect客户端验证)
目录
1、Oauth2协议授权码模式介绍
2、IdentityServer4的OpenID Connect客户端验证简单实现
Oauth2协议授权码模式介绍
- 授权码模式是Oauth2协议中最严格的认证模式,它的组成以及运行流程是这样
1、用户访问客户端,客户端将用户导向认证服务器
2、用户在认证服务器输入用户名密码选择授权,认证服务器认证成功后,跳转至一个指定好的"跳转Url",同时携带一个认证码。
3、用户携带认证码请求指定好的"跳转Url"再次请求认证服务器(这一步后台完成,对用户不可见),此时,由认证服务器返回一个Token
4、客户端携带token请求用户资源
- OpenId Connect运行流程为
1、用户访问客户端,客户端将用户导向认证服务器
2、用户在认证服务器输入用户名密码认证授权
3、认证服务器返回token和资源信息
IdentityServer4的OpenID Connect客户端验证简单实现
Server部分
- 添加一个Mvc项目,配置Config.cs文件
public class Config
{
//定义要保护的资源(webapi)
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1", "My API")
};
}
//定义可以访问该API的客户端
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "mvc",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.Implicit, //简化模式
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
RequireConsent =true, //用户选择同意认证授权
RedirectUris={ "http://localhost:5001/signin-oidc" }, //指定允许的URI返回令牌或授权码(我们的客户端地址)
PostLogoutRedirectUris={ "http://localhost:5001/signout-callback-oidc" },//注销后重定向地址 参考https://identityserver4.readthedocs.io/en/release/reference/client.html
LogoUri="https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3298365745,618961144&fm=27&gp=0.jpg",
// scopes that client has access to
AllowedScopes = { //客户端允许访问个人信息资源的范围
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Address,
IdentityServerConstants.StandardScopes.Phone
}
}
};
}
public static List<TestUser> GeTestUsers()
{
return new List<TestUser>
{
new TestUser
{
SubjectId = "",
Username = "alice",
Password = "password"
},
new TestUser
{
SubjectId = "",
Username = "bob",
Password = "password"
}
};
}
//openid connect
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email()
};
}
}Config
- 添加几个ViewModel 用来接收解析跳转URL后的参数
public class InputConsentViewModel
{
public string Button { get; set; }
public IEnumerable<string> ScopesConsented { get; set; } public bool RemeberConsent { get; set; }
public string ReturnUrl { get; set; }
}
//解析跳转url后得到的应用权限等信息
public class ConsentViewModel:InputConsentViewModel
{
public string ClientId { get; set; }
public string ClientName { get; set; }
public string ClientUrl { get; set; }
public string ClientLogoUrl { get; set; }
public IEnumerable<ScopeViewModel> IdentityScopes { get; set; }
public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }
}
//接收Scope
public class ScopeViewModel
{
public string Name { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public bool Emphasize { get; set; }
public bool Required { get; set; }
public bool Checked { get; set; }
}
public class ProcessConsentResult
{
public string RedirectUrl { get; set; }
public bool IsRedirectUrl => RedirectUrl != null;
public string ValidationError { get; set; }
public ConsentViewModel ViewModel { get; set; }
}ViewModel
- 配置StartUp,将IdentityServer加入到DI容器,这里有个ConsentService,用来处理解析跳转URL的数据,这个Service在下面实现。
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddDeveloperSigningCredential() //添加登录证书
.AddInMemoryIdentityResources(Config.GetIdentityResources()) //添加IdentityResources
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GeTestUsers());
services.AddScoped<ConsentService>();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentityServer();//引用IdentityServer中间件
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Startup配置IdentityServer
- 添加一个ConsentService,用来根据Store拿到Resource
public class ConsentService
{
private readonly IClientStore _clientStore;
private readonly IResourceStore _resourceStore;
private readonly IIdentityServerInteractionService _identityServerInteractionService; public ConsentService(IClientStore clientStore,
IResourceStore resourceStore,
IIdentityServerInteractionService identityServerInteractionService)
{
_clientStore = clientStore;
_resourceStore = resourceStore;
_identityServerInteractionService = identityServerInteractionService;
} public async Task<ConsentViewModel> BuildConsentViewModel(string returnUrl)
{
//根据return url 拿到ClientId 等信息
var request = await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);
if (returnUrl == null)
return null;
var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);//根据请求的scope 拿到resources return CreateConsentViewModel(request, client, resources);
} private ConsentViewModel CreateConsentViewModel(AuthorizationRequest request, Client client, Resources resources)
{
var vm = new ConsentViewModel();
vm.ClientName = client.ClientName;
vm.ClientLoggoUrl = client.LogoUri;
vm.ClientUrl = client.ClientUri;
vm.RemeberConsent = client.AllowRememberConsent; vm.IdentityScopes = resources.IdentityResources.Select(i => CreateScopeViewModel(i));
//api resource
vm.ResourceScopes = resources.ApiResources.SelectMany(i => i.Scopes).Select(x => CreateScopeViewModel(scope: x));
return vm;
}
//identity 1个scopes
private ScopeViewModel CreateScopeViewModel(IdentityResource identityResource)
{
return new ScopeViewModel
{
Name = identityResource.Name,
DisplayName = identityResource.DisplayName,
Description = identityResource.Description,
Required = identityResource.Required,
Checked = identityResource.Required,
Emphasize = identityResource.Emphasize
};
}
//apiresource
private ScopeViewModel CreateScopeViewModel(Scope scope)
{
return new ScopeViewModel
{
Name = scope.Name,
DisplayName = scope.DisplayName,
Description = scope.Description,
Required = scope.Required,
Checked = scope.Required,
Emphasize = scope.Emphasize
};
}
}ConsentService
- 添加一个ConsentController,用来显示授权登录页面,以及相应的跳转登录逻辑。
public class ConsentController : Controller
{
private readonly ConsentService _consentService;
public ConsentController(ConsentService consentService)
{
_consentService = consentService;
} public async Task<IActionResult> Index(string returnUrl)
{
//调用consentService的BuildConsentViewModelAsync方法,将跳转Url作为参数传入,解析得到一个ConsentViewModel
var model =await _consentService.BuildConsentViewModelAsync(returnUrl);
if (model == null)
return null;
return View(model);
}
[HttpPost]
public async Task<IActionResult> Index(InputConsentViewModel viewModel)
{
//用户选择确认按钮的时候,根据选择按钮确认/取消,以及勾选权限
var result = await _consentService.PorcessConsent(viewModel);
if (result.IsRedirectUrl)
{
return Redirect(result.RedirectUrl);
}
if (!string.IsNullOrEmpty(result.ValidationError))
{
ModelState.AddModelError("", result.ValidationError);
}
return View(result.ViewModel);
}
}
ConsentController
- 配置服务端的登录Controller
public class AccountController : Controller
{
private readonly TestUserStore _user; //放入DI容器中的TestUser(GeTestUsers方法),通过这个对象获取
public AccountController(TestUserStore user)
{
_user = user;
} public IActionResult Login(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
} [HttpPost]
public async Task<IActionResult> Login(LoginViewModel loginViewModel,string returnUrl)
{
//用户登录
if (ModelState.IsValid)
{
ViewData["ReturnUrl"] = returnUrl;
var user = _user.FindByUsername(loginViewModel.Email);
if (user == null)
{
ModelState.AddModelError(nameof(loginViewModel.Email), "Email not exists");
}
else
{
var result = _user.ValidateCredentials(loginViewModel.Email, loginViewModel.Password);
if(result)
{
var props = new AuthenticationProperties()
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes()
};
await Microsoft.AspNetCore.Http.AuthenticationManagerExtensions.SignInAsync( //Id4扩展方法和HttpContext扩展方法重名,这里强制使用命名空间方法
this.HttpContext,
user.SubjectId,
user.Username,
props);
return RedirectToLoacl(returnUrl);
}
else
{
ModelState.AddModelError(nameof(loginViewModel.Email), "Wrong password");
}
}
} return View();
}AccountController
- 接下来给Consent控制器的Index添加视图
@using mvcCookieAuthSample.ViewModels
@model ConsentViewModel
<h2>ConsentPage</h2>
@*consent*@
<div class="row page-header">
<div class="col-sm-10">
@if (!string.IsNullOrWhiteSpace(Model.ClientLogoUrl))
{
<div>
<img src="@Model.ClientLogoUrl" style="width:50px;height:50px" />
</div>
}
<h1>@Model.ClientName</h1>
<p>希望使用你的账户</p>
</div>
</div>
@*客户端*@
<div class="row">
<div class="col-sm-8">
<div asp-validation-summary="All" class="danger"></div>
<form asp-action="Index" method="post">
<input type="hidden" asp-for="ReturnUrl"/>
@if (Model.IdentityScopes.Any())
{
<div class="panel">
<div class="panel-heading">
<span class="glyphicon glyphicon-user"></span>
用户信息
</div>
<ul class="list-group">
@foreach (var scope in Model.IdentityScopes)
{
@Html.Partial("_ScopeListitem.cshtml", scope);
}
</ul>
</div>
}
@if (Model.ResourceScopes.Any())
{
<ul class="list-group">
@foreach (var scope in Model.ResourceScopes)
{
@Html.Partial("_ScopeListitem.cshtml", scope);
}</ul>
}
<div>
<label>
<input type="checkbox" asp-for="RemeberConsent"/>
<strong>记住我的选择</strong>
</label>
</div>
<div>
<button name="button" value="yes" class="btn btn-primary" autofocus>同意</button>
<button name="button" value="no">取消</button>
@if (!string.IsNullOrEmpty(Model.ClientUrl))
{
<a href="@Model.ClientUrl" class="pull-right btn btn-default">
<span class="glyphicon glyphicon-info-sign" ></span>
<strong>@Model.ClientUrl</strong>
</a>
}
</div>
</form>
</div>
</div>
//这里用到了一个分部视图用来显示用户允许授权的身份资源和api资源
@using mvcCookieAuthSample.ViewModels
@model ScopeViewModel;
<li>
<label>
<input type="checkbox"
name="ScopesConsented"
id="scopes_@Model.Name"
value="@Model.Name"
checked=@Model.Checked
disabled=@Model.Required/>
@if (Model.Required)
{
<input type="hidden" name="ScopesConsented" value="@Model.Name" />
}
<strong>@Model.Name</strong>
@if (Model.Emphasize)
{
<span class="glyphicon glyphicon-exclamation-sign"></span>
}
</label>
@if(string.IsNullOrEmpty(Model.Description))
{
<div>
<label for="scopes_@Model.Name">@Model.Description</label>
</div>
}
</li>Index.cshtml
- 添加客户端,依旧添加一个mvc项目,配置startup,Home/Index action打上Authorize标签。
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options => {
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";//openidconnectservice
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc",options=> {
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000"; //设置认证服务器
options.RequireHttpsMetadata = false;
options.ClientId = "mvc"; //openidconfig的配置信息
options.ClientSecret = "secret";
options.SaveTokens = true;
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}客户端配置Startup
设置服务端端口5000,运行服务器端;设置客户端端口5001,运行客户端。我们可以看到,localhost:5001会跳转至认证服务器
然后看下Url=》
使用config配置的testuser登录系统,选择允许授权的身份权限。登录成功后看到我们的Claims
总结
- 最后来总结一下
用户访问客户端(5001端口程序),客户端将用户导向认证服务器(5000程序),用户选择允许授权的身份资源和api资源后台解析(这两个资源分别由Resources提供,resources 由IResourceStore解析returnurl后的Scopes提供),最后由ProfileService返回数条Claim。(查看ConsentService的各个方法)
Core篇——初探IdentityServer4(OpenID Connect模式)的更多相关文章
- Core篇——初探IdentityServer4(客户端模式,密码模式)
Core篇——初探IdentityServer4(客户端模式,密码模式) 目录 1.Oatuth2协议的客户端模式介绍2.IdentityServer4客户端模式实现3.Oatuth2协议的密码模式介 ...
- Core篇——初探Core配置管理
文章目录 1.命令行配置 2.Json文件配置 3.配置文件文本至C#对象实例的映射 4.配置文件热更新 5.总结 命令行的配置 我们首先来创建一个.net core 的控制台项目,然后引入.net ...
- Core篇——初探Core的认证,授权机制
目录 1.Cookie-based认证的实现 2.Jwt Token 的认证与授权 3.Identity Authentication + EF 的认证 Cookie-based认证的实现 cooki ...
- Core篇——初探Core的Http请求管道&&Middleware
目录: 1.Core 处理HTTP请求流程 2.中间件(Middleware)&&处理流程 3.创建自定义中间件&&模拟Core的请求管道 Core 处理HTTP请求流 ...
- Core篇——初探依赖注入
目录 1.DI&&IOC简单介绍 2.UML类图中六种关联关系 3..net core 中DI的使用 4..net core DI初始化源码初窥 DI&&IOC简单介绍 ...
- 使用 IdentityServer4 实现 OAuth 2.0 与 OpenID Connect 服务
IdentityServer4 是 ASP.NET Core 的一个包含 OIDC 和 OAuth 2.0 协议的框架.最近的关注点在 ABP 上,默认 ABP 也集成 IdentityServer4 ...
- asp.net core系列 53 IdentityServer4 (IS4)介绍
一.概述 在物理层之间相互通信必须保护资源,需要实现身份验证和授权,通常针对同一个用户存储.对于资源安全设计包括二个部分,一个是认证,一个是API访问. 1 认证 认证是指:应用程序需要知道当前用户的 ...
- IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习保护API
IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习之保护API. 使用IdentityServer4 来实现使用客户端凭据保护ASP.N ...
- ASP.NET Core的身份认证框架IdentityServer4(9)-使用OpenID Connect添加用户认证
OpenID Connect OpenID Connect 1.0是OAuth 2.0协议之上的一个简单的身份层. 它允许客户端基于授权服务器执行的身份验证来验证最终用户的身份,以及以可互操作和类似R ...
随机推荐
- 个人作业—Alpha测试
这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/SoftwareEngineeringClass1/homework/3338 这个作业要求在哪里 htt ...
- 团体程序设计天梯赛-练习集-L1-047. 装睡
L1-047. 装睡 你永远叫不醒一个装睡的人 —— 但是通过分析一个人的呼吸频率和脉搏,你可以发现谁在装睡!医生告诉我们,正常人睡眠时的呼吸频率是每分钟15-20次,脉搏是每分钟50-70次.下面给 ...
- MATLAB图形界面设计(下)
文章参考Blue Mountain https://www.cnblogs.com/BlueMountain-HaggenDazs/p/4307777.html 一.菜单设计 1.建立菜单项 (1)建 ...
- eas之树
如何设置树的深度(即树总共有几级) // 设置树的深度为3,即树包括0.1.2三级结点 table.getTreeColumn().setDepth(3); 如何设置树的方向 树的方向包括两种:自上向 ...
- HDU 4405 Aeroplane chess(概率dp,数学期望)
题目 http://kicd.blog.163.com/blog/static/126961911200910168335852/ 根据里面的例子,就可以很简单的写出来了,虽然我现在还是不是很理解为什 ...
- 洛谷P1057 传球游戏【递归+搜索】
上体育课的时候,小蛮的老师经常带着同学们一起做游戏.这次,老师带着同学们一起做传球游戏. 游戏规则是这样的:nn个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把 ...
- python爬虫06 | 你的第一个爬虫,爬取当当网 Top 500 本五星好评书籍
来啦,老弟 我们已经知道怎么使用 Requests 进行各种请求骚操作 也知道了对服务器返回的数据如何使用 正则表达式 来过滤我们想要的内容 ... 那么接下来 我们就使用 requests 和 re ...
- PAT 1088. Rational Arithmetic
For two rational numbers, your task is to implement the basic arithmetics, that is, to calculate the ...
- elasticsearch 权威指南搜索阅读笔记(四)
多索引多type搜索 分页搜索 每页5条 查询一到3页数据 第一页:http://127.0.0.1:9200/blogs2/product/_search?size=5&from=0 第二页 ...
- SQL Server 通过备份文件初始化复制 – 听风吹雨
一.背景 MySQL在对有历史数据的数据库进行搭建复制(Master/Slave)的时候,可以通过在Master服务器备份历史数据,利用这个备份文件 在Slave进行还原:这样做的好处是可以更加快速的 ...