OpenIddict是一个ASP.NET Core身份验证库,可帮助您添加OpenID Connect和OAuth 2.0支持到ASP.NET Core应用程序中。下面是OpenIddict使用教程的步骤:

  1. 安装OpenIddict,在项目中添加OpenIddict.Core和OpenIddict.EntityFrameworkCore Nuget包。

  2. 配置OpenIddict,在Startup.cs文件中添加OpenIddict服务的配置。您可以选择使用内存或EFCore进行配置。以下是使用EF Core进行配置的示例:

services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.UseOpenIddict();
}); services.AddCustomOpenIddictApplication();
services.AddCustomOpenIddictAuthorization();
services.AddCustomOpenIddictScope();
services.AddCustomOpenIddictToken();
services.AddCustomOpenIddictValidation();
services.AddCustomOpenIddictUser(); services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>()
.ReplaceDefaultEntities<ApplicationDbContext>();
})
.AddServer(options =>
{
options.UseMvc();
options.EnableAuthorizationEndpoint("/connect/authorize")
.EnableLogoutEndpoint("/connect/logout")
.EnableTokenEndpoint("/connect/token")
.EnableUserinfoEndpoint("/connect/userinfo");
options.RegisterScopes("openid", "profile", "email", "offline_access"); options.AllowImplicitFlow();
options.DisableHttpsRequirement(); options.AddSigningCertificate(File.ReadAllBytes(Configuration["Auth:Certificates:Path"]),
Configuration["Auth:Certificates:Password"]); options.DisableAccessTokenEncryption();
options.SetAccessTokenLifetime(TimeSpan.FromHours(6));
});
  1. 添加授权策略,在Startup.cs文件添加需要的授权策略。以下是一个例子:
services.AddAuthorization(options =>
{
options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
options.AddPolicy("AdministratorOnly", policy => policy.RequireRole("Administrator"));
});
  1. 在您的应用程序中使用OpenIddict,您可以使用OpenIddict来实现您的OAuth 2.0或OpenID Connect需求。以下是一些常见的用例:

4.1 登录页面

使用OpenIddict进行身份验证,您可以使用如下代码在您的控制器中。您可以使用请求重定向到触发OpenID Connect流:

[HttpGet("~/login")]
public IActionResult Login()
{
var request = HttpContext.GetOpenIddictServerRequest(); return View(new LoginViewModel
{
Nonce = RandomNumberGenerator.GetInt32(),
ReturnUrl = request.RedirectUri,
Ticket = request.GetOpenIddictServerTransactionId(),
});
} [HttpPost("~/login")]
public IActionResult Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByNameAsync(model.Username);
if (user == null)
{
ModelState.AddModelError("Username", "Username or password is incorrect.");
}
else if (!await _userManager.IsEmailConfirmedAsync(user))
{
ModelState.AddModelError("Email", "You must have a confirmed email to log in.");
}
else if (!await _userManager.CheckPasswordAsync(user, model.Password))
{
ModelState.AddModelError("Username", "Username or password is incorrect.");
}
else
{
// 创建一个新的身份验证票据.
var ticket = await CreateTicketAsync(user); return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
} ViewData["returnUrl"] = model.ReturnUrl;
ViewData["nonce"] = model.Nonce;
ViewData["transactionId"] = model.Ticket;
return View(model);
}

4.2 注册页面

您还可以使用OpenIddict来实现您的注册页面。以下是一个例子:

[HttpGet("~/register")]
public IActionResult Register()
{
return View();
} [HttpPost("~/register")]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName,
}; var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var code = await _userManager.GenerateEmailConfirmationAsync(user);
var callbackUrl= Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); await _emailSender.SendEmailAsync(model.Email, "Confirm your email",
$"Please confirm your account by clicking this link: {callbackUrl}"); return RedirectToAction(nameof(RegisterConfirmation));
} foreach (var error in result.Errors)
{
ModelState.AddModelError("Email", error.Description);
}
} return View(model);
} [HttpGet("~/register/confirmation")]
public IActionResult RegisterConfirmation()
{
return View();
}

4.3 访问受保护的资源

最后,您可以使用OpenIddict来实现访问受保护资源的身份验证和授权。以下是一个例子:

[HttpGet("~/manager")]
[Authorize(Roles = "Manager")]
public IActionResult ManagerDashboard()
{
return View();
} [HttpGet("~/employee")]
[Authorize(Policy = "EmployeeOnly")]
public IActionResult EmployeeDashboard()
{
return View();
} [HttpGet("~/administrator")]
[Authorize(Policy = "AdministratorOnly")]
public IActionResult AdministratorDashboard()
{
return View();
}
  1. 通过OpenIddict实现Token刷新

当访问受保护的API时,您可以使用OpenIddict来实现使用token刷新。以下是实现Token刷新的一个示例方法:

[HttpPost("~/api/token/refresh")]
public async Task<IActionResult> Refresh([FromForm]string refreshToken)
{
var info = await HttpContext.AuthenticateAsync(OpenIddictServerDefaults.AuthenticationScheme); if (info == null)
{
return BadRequest(new
{
error = OpenIddictConstants.Errors.InvalidRequest,
error_description = "The refresh token is no longer valid."
});
} var principal = info.Principal; var user = await _userManager.GetUserAsync(principal);
if (user == null)
{
return BadRequest(new
{
error = OpenIddictConstants.Errors.InvalidRequest,
error_description = "The refresh token is no longer valid."
});
} // 确保刷新令牌没有被撤销.
if (!await _tokenManager.ValidateAsync(
principal.GetId(),
principal.GetClaim(OpenIddictConstants.Claims.JwtId)))
{
return BadRequest(new
{
error = OpenIddictConstants.Errors.InvalidRequest,
error_description = "The refresh token is no longer valid."
});
} // 从数据库得到客户端应用程序详细信息
var application = await _applicationManager.FindByClientIdAsync(
principal.GetClaim(OpenIddictConstants.Claims.ClientId));
if (application == null)
{
return BadRequest(new
{
error = OpenIddictConstants.Errors.InvalidRequest,
error_description = "The client application associated with this token is no longer valid."
});
} var identity = await _userManager.CreateIdentityAsync(user, principal.GetScopes()); var ticket = await CreateTicketAsync(application, identity, principal); return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
  1. 通过OpenIddict实现密码恢复流程OpenIddict还可以实现忘记密码流程的重置密码,以下是一个简单的示例:
[HttpPost("~/forgot-password")]
[AllowAnonymous]
public async Task<IActionResult> ForgotPassword([FromForm] string email)
{
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
// 不要显示用户不存在,懂的都懂~
return Ok();
} var code = await _userManager.GeneratePasswordResetTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Reque await _emailSender.SendEmailAsync(
email,
"Password Reset",
$"Please reset your password by clicking here: <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>link</a>."); return Ok();
} [HttpGet("~/reset-password")]
[AllowAnonymous]
public IActionResult ResetPassword(string code = null, string userId = null)
{
return View(new ResetPasswordViewModel { Code = code, UserId = userId });
} [HttpPost("~/reset-password")]
[AllowAnonymous]
public async Task<IActionResult> ResetPassword([FromForm] ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
} var user = await _userManager.FindByIdAsync(model.UserId);
if (user == null)
{
// 不要显示用户不存在
return View("ResetPasswordConfirmation");
} var decodedCode = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(model.Code));
var result = await _userManager.ResetPasswordAsync(user, decodedCode, model.Password);
if (result.Succeeded)
{
return RedirectToAction(nameof(ResetPasswordConfirmation));
} foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
} return View(model);
} [HttpGet("~/reset-password-confirmation")]
[AllowAnonymous]
public IActionResult ResetPasswordConfirmation()
{
return View();
}
  1. 使用OpenIddict实现自定义Token发布方案

OpenIddict支持自定义Token发布方案,以适应各种需求。在以下示例中,我们将实现自定义发布方案来控制Token的过期时间:

public class CustomTokenEndpointHandler : OpenIddictServerHandler<OpenIddictServerOptions>
{
public CustomTokenEndpointHandler(IServiceProvider services)
: base(services)
{
} public override async Task HandleAsync([NotNull] OpenIddictServerHandleContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
} // 从数据库检索客户机应用程序.
var application = await context.HttpContext.GetOpenIddictServerApplicationAsync();
if (application == null)
{
throw new InvalidOperationException("The client application cannot be retrieved.");
} // 从授权服务器设置检索用户主体.
var principal = context.HttpContext.User; // 确保允许应用程序使用指定的授权类型。
if (!await ValidateClientRedirectUriAsync(application, context.Request))
{
throw new InvalidOperationException("The grant type is not allowed for this application.");
} //注意:这个自定义令牌终端点总是忽略“scopes”参数,并根据授予的scopes/roles自动定义声明。
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(),
OpenIddictServerDefaults.AuthenticationScheme); // 根据请求的自定义授权类型自定义令牌生命周期.
if (string.Equals(context.Request.GrantType, "urn:custom_grant", StringComparison.OrdinalIgnoreCase))
{
// Set the token expiration to 1 hour.
ticket.Properties.ExpiresUtc = context.Options.SystemClock.UtcNow.AddHours(1);
}
else
{
// 将令牌过期时间设置为默认持续时间(5分钟)
ticket.Properties.ExpiresUtc = context.Options.SystemClock.UtcNow.Add(
context.Options.AccessTokenLifetime ?? TimeSpan.FromMinutes(5));
} context.Logger.LogInformation("The custom token request was successfully processed."); await context.HttpContext.SignInAsync(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); // 将响应标记为已处理,以跳过管道的其余部分.
context.HandleRequest();
}
}

您需要将其添加到OpenIddict配置中:

services.AddOpenIddict()
.AddCore(options =>
{
// ...
})
.AddServer(options =>{
// ... options.Handlers.Add(new CustomTokenEndpointHandler(services)); // ...
})
.AddValidation(options =>
{
// ...
});

此时,您可以使用urn:custom_grant授权类型来发出过期时间为1小时的Token,这可以通过以下方式完成:

var client = new HttpClient();

var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:5000/connect/token");
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "urn:custom_grant",
["client_id"] = "your_client_id",
["client_secret"] = "your_client_secret",
["scope"] = "your_scopes_separated_by_spaces"
}); var response = await client.SendAsync(request);
var payload = await response.Content.ReadAsStringAsync();

总结

本文介绍了如何使用OpenIddict创建一个基本的身份验证和授权服务器。当然,在实现身份验证和授权服务器时有很多细节需要考虑,例如维护安全性、处理错误、管理用户和客户端应用程序等。希望这篇文章对您有所帮助!

OpenIddict使用教程的更多相关文章

  1. Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求

    上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...

  2. Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数

    上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...

  3. Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数

    上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...

  4. Angular2入门系列教程4-服务

    上一篇文章 Angular2入门系列教程-多个组件,主从关系 在编程中,我们通常会将数据提供单独分离出来,以免在编写程序的过程中反复复制粘贴数据请求的代码 Angular2中提供了依赖注入的概念,使得 ...

  5. Angular2入门系列教程1-使用Angular-cli搭建Angular2开发环境

    一直在学Angular2,百忙之中抽点时间来写个简单的教程. 2016年是前端飞速发展的一年,前端越来越形成了(web component)组件化的编程模式:以前Jquery通吃一切的田园时代一去不复 ...

  6. wepack+sass+vue 入门教程(三)

    十一.安装sass文件转换为css需要的相关依赖包 npm install --save-dev sass-loader style-loader css-loader loader的作用是辅助web ...

  7. wepack+sass+vue 入门教程(二)

    六.新建webpack配置文件 webpack.config.js 文件整体框架内容如下,后续会详细说明每个配置项的配置 webpack.config.js直接放在项目demo目录下 module.e ...

  8. wepack+sass+vue 入门教程(一)

    一.安装node.js node.js是基础,必须先安装.而且最新版的node.js,已经集成了npm. 下载地址 node安装,一路按默认即可. 二.全局安装webpack npm install ...

  9. Virtual Box配置CentOS7网络(图文教程)

    之前很多次安装CentOS7虚拟机,每次配置网络在网上找教程,今天总结一下,全图文配置,方便以后查看. Virtual Box可选的网络接入方式包括: NAT 网络地址转换模式(NAT,Network ...

  10. webpack之傻瓜式教程

    接触webpack也有挺长一段时间了,公司的项目也是一直用着webpack在打包处理,但前几天在教新人的情况下,遇到了一个问题,那就是:尽管网上的webpack教程满天飞,但是却很难找到一个能让新人快 ...

随机推荐

  1. captura怎样解决FFmpeg解析错误问题

    captura怎样解决FFmpeg解析错误问题?captura软件里大家在进行屏幕录制的工作得时候都会用到captura软件,软件得功能可以满足大家的需求,可以轻松的录制屏幕,进行屏幕截屏等,但是又小 ...

  2. SQL取系统时间的前一个月的月份和年份

    SQL SERVER 提供了一些时间函数:取当前时间:select getdate()取前一个月的时间:SELECT DATEADD(MONTH,-1,GETDATE()) 月份减一个月取年份:SEL ...

  3. 另类方式实现.Net下的多语言

    前言 关于.Net下的多语言方案已经有很多成熟的方案,例如:# Avalonia 国际化之路:Resx 资源文件的深度应用与探索,或者# Avalonia使用XML文件实现国际化,基本都围绕官方的Sa ...

  4. Linux - centos6.6不使用ssh如何在服务器之间传输文件?

    根据上一篇的设定,如果升级openssh失败之后,又无法使用ssh,该怎么传输文件呢? 可以使用busybox进行文件传输,首先需要准备两台centos6.6服务器:ctos66-01和ctos66- ...

  5. Linux - Centos操作系统iso文件下载

    CENTOS VERSION DOWNLOAD LINK CentOS 8.5(2111) Download CentOS 8.4(2105) Download CentOS 8.3(2011) Do ...

  6. C# fleck websocket使用

    转载于:https://www.itspeeding.com/article/28 1.web页面 1 <html lang="en" xmlns="http:// ...

  7. 【Verilog】表达式位宽与符号判断机制

    缘起于p1课下alu算数位移设计.查了好多资料,最后发现还是主要在翻译官方文档.浪费了超多时间啊,感觉还是没搞透,还是先以应用为导向放一放,且用且归纳 1.表达式位宽 expression bit l ...

  8. 单词搜索 & 周赛第二道

    单词搜索 描述: 给定一个二维网格和一个单词,找出该单词是否存在于网格中.单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中"相邻"单元格是那些水平相邻或垂直相邻的单元格.同 ...

  9. Golang 入门 : 浮点数

    浮点数介绍 Go语言提供了两种精度的浮点数:float32 和 float64.它们的算术规范由IEEE754浮点数国际标准定义,该浮点数规范被所有现代的CPU支持. 这些浮点数类型的范围可以从很微小 ...

  10. go math/rand包详解

    go math/rand package rand import "math/rand" rand包实现了伪随机数生成器. math_rand go官方标准文档 随机数从资源生成. ...