什么是 OpenIddict?
OpenIddict 旨在提供一种多功能解决方案 ,以在任何 .NET 应用程序中实现OpenID Connect 客户端、服务器和令牌验证支持。
入门
如果您正在寻找不涉及编写自定义逻辑的交钥匙解决方案,请考虑查看ABP Framework和OrchardCore:它们开箱即用地支持 OpenIddict,并且是实现标准 OpenID Connect 服务器的最佳选择(即使您不熟悉该标准!)。
如果您更喜欢实施自定义解决方案,请阅读入门指南。
可以在"集成"中找到其他集成(免费或商业)。
为什么不选择IdentityServer?
identityserver是最灵活且符合标准的 ASP.NET Core OpenID Connect 和 OAuth 2.0 框架。但是由于从 IdentityServer4 的 Apache 2.0 协议转变为 Duende IdentityServer 的商业许可。有功能受限的 Community Edition ,以及需要付费的 Business Edition 和 Enterprise Edition。
总结与对比
项目 | 当前状态 | 许可证 | 是否免费? | 是否开源? |
---|---|---|---|---|
IdentityServer4 | 已停止维护 | Apache 2.0 | 是 | 是 |
Duende IdentityServer | 当前官方版本 | 商业许可证 | 有条件免费 (年收入<100万美金) | 否 |
OpenIddict | 当前官方版本 | 开源许可(Apache 2.0)。完全免费用于商业和个人项目 | 是 | 是 |
OpenIddict的实践
预期部署架构
项目 | 许可证 |
---|---|
授权中心服务 | https://xxx.com/passport |
资源API服务 | http://localhost:9000 |
搭建授权中心服务
新增代码入口Program.cs代码
cs
using BigData.Passport.API;
using BigData.Passport.Domain;
using BigData.Passport.Domain.Models;
using BigData.Passport.Domain.Services;
using BigData.SSO.Passport;
using BigData.SSO.Passport.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using OpenIddict.EntityFrameworkCore.Models;
using TrendLib.MiniWeb;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
// 数据库上下文配置
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
// Configure the context to use MySQL Server.
options.UseMySQL(configuration.GetConnectionString("DefaultConnection"));
// Register the entity sets needed by OpenIddict.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
options.UseOpenIddict();
});
// 配置cookie数据保护(持久化存储),避免服务器容器重启登录失效的问题
builder.Services.AddDataProtection()
.PersistKeysToDbContext<ApplicationDbContext>()
.SetApplicationName("Passport")
.SetDefaultKeyLifetime(TimeSpan.FromDays(90));
// Register the Identity services.
builder.Services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
// 放宽密码策略
options.Password.RequireDigit = false; // 不需要数字
options.Password.RequiredLength = 6; // 最小长6
options.Password.RequireLowercase = false; // 不需要小写字母
options.Password.RequireUppercase = false; // 不需要大写字母
options.Password.RequireNonAlphanumeric = false; // 不需要特殊字符
options.Password.RequiredUniqueChars = 1; // 唯一字符数
// 其他配置
options.SignIn.RequireConfirmedAccount = false;
options.User.RequireUniqueEmail = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// 配置 Application Cookie
builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "SSO.Auth";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Lax;
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Account/AccessDenied";
options.LogoutPath = "/Account/LogOut";
options.ExpireTimeSpan = TimeSpan.FromDays(7); // cookie:sso.auth,勾选记住我,可以保留7天,否则就是会话级别cookie
options.SlidingExpiration = true;
});
// 配置 OpenIddict 核心
builder.Services.AddOpenIddict()
.AddCore(options =>
{
// 使用内置的内存存储
options.UseEntityFrameworkCore().UseDbContext<ApplicationDbContext>()
.ReplaceDefaultEntities<OpenIddictApplications, OpenIddictAuthorization, OpenIddictScopes, OpenIddictToken, string>();
})
.AddServer(options =>
{
// 设置发行者地址
options.SetIssuer(configuration["IdentityServer:Authority"]);
// Enable the authorization, introspection and token endpoints.
options.SetAuthorizationEndpointUris("connect/authorize")
.SetTokenEndpointUris("connect/token")
.SetUserInfoEndpointUris("connect/userinfo")
.SetEndSessionEndpointUris("connect/logout")
.SetConfigurationEndpointUris(".well-known/openid-configuration"); // Discovery 端点
// Mark the "email", "profile" and "roles" scopes as supported scopes.
options.RegisterScopes(OpenIddictConstants.Scopes.OpenId, //// 必须包含 openid 才能返回 id_token
OpenIddictConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.Roles,
OpenIddictConstants.Scopes.OfflineAccess
);
// Note: this sample only uses the authorization code and refresh token
// flows but you can enable the other flows if you need to support implicit,
// password or client credentials.
options.AllowAuthorizationCodeFlow()
.AllowClientCredentialsFlow()
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.AllowHybridFlow()
.AllowImplicitFlow();
// Register the encryption credentials. This sample uses a symmetric
// encryption key that is shared between the server and the Api2 sample
// (that performs local token validation instead of using introspection).
//
// Note: in a real world application, this encryption key should be
// stored in a safe place (e.g in Azure KeyVault, stored as a secret).
// options.AddEncryptionKey(new SymmetricSecurityKey(Convert.FromBase64String("DRjd/GnduI3Efzen9V9BvbNUfc/VKgXltV7Kbk9sMkY=")));
// 加密和签名证书(开发环境)
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
// 配置令牌生命周期
options.SetAccessTokenLifetime(TimeSpan.FromDays(1));
options.SetRefreshTokenLifetime(TimeSpan.FromDays(7));
// 禁用访问令牌加密(开发环境)
options.DisableAccessTokenEncryption();
// 配置 ASP.NET Core 集成
options.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.EnableUserInfoEndpointPassthrough()
.EnableEndSessionEndpointPassthrough()
.DisableTransportSecurityRequirement(); // 关键:禁用传输安全要求
})
.AddValidation(options =>
{
options.UseLocalServer();
options.UseAspNetCore();
});
// Add services to the container.
builder.Services.AddControllersWithViews(options =>
{
// handle BusinessException
options.Filters.Add<BusinessExceptionFilter>();
});
builder.Services.AddNginx();
builder.Services.AddCors(options => options.AddDefaultPolicy(policy =>
policy.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin()));
// 配置客户端
builder.Services.AddHostedService<Worker>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
// Configure the HTTP request pipeline.
app.UseNginx();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
新增注册应用appliction,Scope范围域以及User用户信息代码Worker.cs
cs
using BigData.Passport.Domain;
using BigData.Passport.Domain.Models;
using BigData.SSO.Passport.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Abstractions;
using Org.BouncyCastle.Asn1.Ocsp;
using System.Text.Json;
using static OpenIddict.Abstractions.OpenIddictConstants;
namespace BigData.SSO.Passport
{
/// <summary>
/// 客户端配置
/// </summary>
public class Worker : IHostedService
{
/// <summary>
///
/// </summary>
private readonly IServiceProvider _serviceProvider;
/// <summary>
///
/// </summary>
/// <param name="serviceProvider"></param>
public Worker(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
public async Task StartAsync(CancellationToken cancellationToken)
{
using var scope = _serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await context.Database.EnsureCreatedAsync();
await CreateApplicationsAsync();
await CreateScopesAsync();
await CreateUserAsync();
async Task CreateApplicationsAsync()
{
var manager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>();
if (await manager.FindByClientIdAsync("console_app") is null)
{
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ApplicationType = ApplicationTypes.Native,
ClientId = "console_app",
ClientType = ClientTypes.Public,
DisplayName = "console_app名称",
RedirectUris =
{
new Uri("http://localhost/")
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.ResponseTypes.Code,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles,
Permissions.Prefixes.Scope + "api1",
Permissions.Prefixes.Scope + "api2"
}
});
}
if (await manager.FindByClientIdAsync("spa") is null)
{
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = "spa",
//ClientSecret = "4cb68986-a64f-11f0-9a7b-005056927c86",
ClientType = ClientTypes.Public,
DisplayName = "测试",
// 关键配置:设置同意类型为隐式同意
ConsentType = ConsentTypes.Implicit,
// 登入重定向 URI
RedirectUris =
{
new Uri("https://xxx/signin-callback"),
new Uri("https://xxx/silent-renew.html") // 静默续订
},
// 退出后重定向 URI
PostLogoutRedirectUris =
{
new Uri("https://xxx/signout-callback"),
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.EndSession,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.GrantTypes.RefreshToken,
Permissions.ResponseTypes.Code,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles,
Permissions.Scopes.Phone,
Permissions.Scopes.Address,
Permissions.Prefixes.Scope + "api1"
},
Requirements =
{
Requirements.Features.ProofKeyForCodeExchange,
},
});
}
if (await manager.FindByClientIdAsync("web") is null)
{
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ApplicationType = ApplicationTypes.Web,
ClientId = "web",
ClientSecret = "400fe3d8-a64f-11f0-9a7b-005056927c86",
//ClientType = ClientTypes.Public,
DisplayName = "web前端",
// 关键配置:设置同意类型为隐式同意
ConsentType = ConsentTypes.Implicit,
// 登入重定向 URI
RedirectUris =
{
new Uri("https://xxx/signin-callback"),
new Uri("https://xxx/silent-renew.html") // 静默续订
},
// 退出后重定向 URI
PostLogoutRedirectUris =
{
new Uri("https://xxx/signout-callback"),
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.EndSession,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.GrantTypes.ClientCredentials,
Permissions.GrantTypes.Password,
Permissions.GrantTypes.RefreshToken,
Permissions.ResponseTypes.Code,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles,
Permissions.Scopes.Phone,
Permissions.Scopes.Address,
Permissions.Prefixes.Scope + "api1"
},
Requirements =
{
Requirements.Features.ProofKeyForCodeExchange,
},
});
}
if (await manager.FindByClientIdAsync("client1") is null)
{
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = "client1",
ClientSecret = "846B62D0-DEF9-4215-A99D-86E6B8DAB342",
DisplayName = "第一个应用client1",
Permissions =
{
Permissions.Endpoints.Introspection,
Permissions.Endpoints.Token,
Permissions.GrantTypes.ClientCredentials,
Permissions.GrantTypes.Password,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles,
Permissions.Scopes.Phone,
Permissions.Scopes.Address,
Permissions.Prefixes.Scope + "api1",
}
});
}
// Note: no client registration is created for resource_server_2
// as it uses local token validation instead of introspection.
}
async Task CreateScopesAsync()
{
var manager = scope.ServiceProvider.GetRequiredService<IOpenIddictScopeManager>();
if (await manager.FindByNameAsync("api1") is null)
{
await manager.CreateAsync(new OpenIddictScopeDescriptor
{
Name = "api1",
Resources =
{
"resource_server_1"
}
});
}
if (await manager.FindByNameAsync("api2") is null)
{
await manager.CreateAsync(new OpenIddictScopeDescriptor
{
Name = "api2",
Resources =
{
"resource_server_2"
}
});
}
}
async Task CreateUserAsync()
{
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
if (await userManager.FindByNameAsync("admin") is null)
{
var applicationUser = new ApplicationUser
{
UserName = "admin",
NickName = "管理员",
Email = "admin@qq.com",
PhoneNumber = "1",
IsEnabled = true,
CreatedTime = DateTime.Now,
};
var result = await userManager.CreateAsync(applicationUser, "admin123456");
}
}
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
}
实现一个自定义的授权控制器AuthorizationController.cs
cs
using BigData.SSO.Passport.Models;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using System.Collections.Immutable;
using System.Security.Claims;
using Microsoft.EntityFrameworkCore;
using static OpenIddict.Abstractions.OpenIddictConstants;
using Microsoft.IdentityModel.Tokens;
using BigData.SSO.Passport.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Identity;
using System.Security.Principal;
using Polly;
using BigData.Passport.Domain.Models;
namespace BigData.SSO.Passport.Controllers
{
public class AuthorizationController : Controller
{
private readonly ILogger<AuthorizationController> _logger;
private readonly ApplicationDbContext _context;
private readonly IOpenIddictApplicationManager _applicationManager;
private readonly IOpenIddictAuthorizationManager _authorizationManager;
private readonly IOpenIddictScopeManager _scopeManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
// IAuthenticationSchemeProvider _schemeProvider;
private readonly IUserService _userService;
private const string applicationname = CookieAuthenticationDefaults.AuthenticationScheme;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="logger"></param>
/// <param name="context"></param>
/// <param name="applicationManager"></param>
/// <param name="authorizationManager"></param>
/// <param name="scopeManager"></param>
/// <param name="signInManager"></param>
/// <param name="userManager"></param>
/// <param name="userService"></param>
public AuthorizationController(ILogger<AuthorizationController> logger,
ApplicationDbContext context,
IOpenIddictApplicationManager applicationManager,
IOpenIddictAuthorizationManager authorizationManager,
IOpenIddictScopeManager scopeManager,
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager,
IUserService userService
)
{
_logger = logger;
_context = context;
_applicationManager = applicationManager;
_authorizationManager = authorizationManager;
_scopeManager = scopeManager;
_signInManager = signInManager;
_userManager = userManager;
_userService = userService;
}
#region 授权端点的操作 指定路由 这一步自己处理
/// <summary>
/// 授权
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
[HttpGet("~/connect/authorize")]
[HttpPost("~/connect/authorize")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Authorize()
{
_logger.LogInformation("开始授权Authorize.");
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
// Try to retrieve the user principal stored in the authentication cookie and redirect
// the user agent to the login page (or to an external provider) in the following cases:
//
// - If the user principal can't be extracted or the cookie is too old.
// - If prompt=login was specified by the client application.
// - If max_age=0 was specified by the client application (max_age=0 is equivalent to prompt=login).
// - If a max_age parameter was provided and the authentication cookie is not considered "fresh" enough.
//
// For scenarios where the default authentication handler configured in the ASP.NET Core
// authentication options shouldn't be used, a specific scheme can be specified here.
var result = await HttpContext.AuthenticateAsync();
if (result is not { Succeeded: true } ||
((request.HasPromptValue(PromptValues.Login) || request.MaxAge is 0 ||
(request.MaxAge is not null && result.Properties?.IssuedUtc is not null &&
DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) &&
TempData["IgnoreAuthenticationChallenge"] is null or false))
{
// If the client application requested promptless authentication,
// return an error indicating that the user is not logged in.
if (request.HasPromptValue(PromptValues.None))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in."
}));
}
// To avoid endless login endpoint -> authorization endpoint redirects, a special temp data entry is
// used to skip the challenge if the user agent has already been redirected to the login endpoint.
//
// Note: this flag doesn't guarantee that the user has accepted to re-authenticate. If such a guarantee
// is needed, the existing authentication cookie MUST be deleted AND revoked (e.g using ASP.NET Core
// Identity's security stamp feature with an extremely short revalidation time span) before triggering
// a challenge to redirect the user agent to the login endpoint.
TempData["IgnoreAuthenticationChallenge"] = true;
// For scenarios where the default challenge handler configured in the ASP.NET Core
// authentication options shouldn't be used, a specific scheme can be specified here.
return Challenge(new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
Request.HasFormContentType ? Request.Form : Request.Query)
});
}
// Retrieve the profile of the logged in user.
var user = await _userManager.GetUserAsync(result.Principal) ??
throw new InvalidOperationException("The user details cannot be retrieved.");
// Retrieve the application details from the database.
var application = await _applicationManager.FindByClientIdAsync(request.ClientId!) ??
throw new InvalidOperationException("Details concerning the calling client application cannot be found.");
// Retrieve the permanent authorizations associated with the user and the calling client application.
var authorizations = await _authorizationManager.FindAsync(
subject: await _userManager.GetUserIdAsync(user),
client: await _applicationManager.GetIdAsync(application),
status: Statuses.Valid,
type: AuthorizationTypes.Permanent,
scopes: request.GetScopes()).ToListAsync();
// 验证用户是否可以访问该客户端应用
var userClientRel = await _context.CustomUserClientRel
.Where(x => x.UserId == user.Id && x.ClientId == request.ClientId)
.Where(x=>x.IsEnabled == true)
.FirstOrDefaultAsync();
if (userClientRel == null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "该用户没有访问该应用的权限."
}));
}
var consentType = await _applicationManager.GetConsentTypeAsync(application);
switch (await _applicationManager.GetConsentTypeAsync(application))
{
// If the consent is external (e.g when authorizations are granted by a sysadmin),
// immediately return an error if no authorization can be found in the database.
case ConsentTypes.External when authorizations.Count is 0:
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"The logged in user is not allowed to access this client application."
}));
// If the consent is implicit or if an authorization was found,
// return an authorization response without displaying the consent form.
case ConsentTypes.Implicit:
case ConsentTypes.External when authorizations.Count is not 0:
case ConsentTypes.Explicit when authorizations.Count is not 0 && !request.HasPromptValue(PromptValues.Consent):
// Create the claims-based identity that will be used by OpenIddict to generate tokens.
//var identity = new ClaimsIdentity(
// authenticationType: TokenValidationParameters.DefaultAuthenticationType,
// nameType: Claims.Name,
// roleType: Claims.Role);
//// Add the claims that will be persisted in the tokens.
//identity.SetClaim(Claims.Subject, user.Id)
// .SetClaim(Claims.Email, user.Email)
// .SetClaim(Claims.Name, user.UserName)
// .SetClaim(Claims.Nickname, user.NickName)
// //.SetClaim(Claims.PhoneNumber, user.PhoneNumber)
// .SetClaims(Claims.Role, (await _userManager.GetRolesAsync(user)).ToImmutableArray());
// 设置声明
var identity = await this.SetClaims(user, request);
// Note: in this sample, the granted scopes match the requested scope
// but you may want to allow the user to uncheck specific scopes.
// For that, simply restrict the list of scopes before calling SetScopes.
identity.SetScopes(request.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
// Automatically create a permanent authorization to avoid requiring explicit consent
// for future authorization or token requests containing the same scopes.
var authorization = authorizations.LastOrDefault();
authorization ??= await _authorizationManager.CreateAsync(
identity: identity,
subject: await _userManager.GetUserIdAsync(user),
client: (await _applicationManager.GetIdAsync(application))!,
type: AuthorizationTypes.Permanent,
scopes: identity.GetScopes());
identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
identity.SetDestinations(GetDestinations);
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
// At this point, no authorization was found in the database and an error must be returned
// if the client application specified prompt=none in the authorization request.
case ConsentTypes.Explicit when request.HasPromptValue(PromptValues.None):
case ConsentTypes.Systematic when request.HasPromptValue(PromptValues.None):
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"Interactive user consent is required."
}));
// In every other case, render the consent form.
default:
return View(new AuthorizeViewModel
{
ApplicationName = await _applicationManager.GetLocalizedDisplayNameAsync(application),
Scope = request.Scope
});
}
}
/// <summary>
/// 同意授权
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
[Authorize]
[HttpPost("~/connect/authorize")]
[HttpPost("~/connect/authorize/accept")]
public async Task<IActionResult> Accept()
{
_logger.LogInformation("同意授权Authorize.Accept");
// 手动创建 OpenIddict 请求
var request = new OpenIddictRequest(Request.Form);
// 方法 2: 或者直接从 HttpContext 获取(确保参数正确传递)
var directRequest = HttpContext.GetOpenIddictServerRequest();
if (directRequest != null)
{
request = directRequest;
}
if (request == null)
{
return BadRequest("无法处理授权请求");
}
// var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
// Retrieve the profile of the logged in user.
var user = await _userManager.GetUserAsync(User) ??
throw new InvalidOperationException("The user details cannot be retrieved.");
// Retrieve the application details from the database.
var application = await _applicationManager.FindByClientIdAsync(request.ClientId!) ??
throw new InvalidOperationException("Details concerning the calling client application cannot be found.");
// Retrieve the permanent authorizations associated with the user and the calling client application.
var authorizations = await _authorizationManager.FindAsync(
subject: await _userManager.GetUserIdAsync(user),
client: await _applicationManager.GetIdAsync(application),
status: Statuses.Valid,
type: AuthorizationTypes.Permanent,
scopes: request.GetScopes()).ToListAsync();
// Note: the same check is already made in the other action but is repeated
// here to ensure a malicious user can't abuse this POST-only endpoint and
// force it to return a valid response without the external authorization.
if (authorizations.Count is 0 && await _applicationManager.HasConsentTypeAsync(application, ConsentTypes.External))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"The logged in user is not allowed to access this client application."
}));
}
// Create the claims-based identity that will be used by OpenIddict to generate tokens.
//var identity = new ClaimsIdentity(
// authenticationType: TokenValidationParameters.DefaultAuthenticationType,
// nameType: Claims.Name,
// roleType: Claims.Role);
//// Add the claims that will be persisted in the tokens.
//identity.SetClaim(Claims.Subject, user.Id)
// .SetClaim(Claims.Email, user.Email)
// .SetClaim(Claims.Name, user.UserName)
// .SetClaim(Claims.Nickname, user.NickName)
// //.SetClaim(Claims.PhoneNumber, user.PhoneNumber)
// .SetClaims(Claims.Role, (await _userManager.GetRolesAsync(user)).ToImmutableArray());
// 设置声明
var identity = await this.SetClaims(user, request);
// Note: in this sample, the granted scopes match the requested scope
// but you may want to allow the user to uncheck specific scopes.
// For that, simply restrict the list of scopes before calling SetScopes.
identity.SetScopes(request.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
// Automatically create a permanent authorization to avoid requiring explicit consent
// for future authorization or token requests containing the same scopes.
var authorization = authorizations.LastOrDefault();
authorization ??= await _authorizationManager.CreateAsync(
identity: identity,
subject: await _userManager.GetUserIdAsync(user),
client: (await _applicationManager.GetIdAsync(application))!,
type: AuthorizationTypes.Permanent,
scopes: identity.GetScopes());
identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
identity.SetDestinations(GetDestinations);
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
// return Authorize(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
/// <summary>
/// 拒绝授权
/// </summary>
/// <returns></returns>
[Authorize]
[HttpPost("~/connect/authorize")]
[HttpPost("~/connect/authorize/deny")]
// Notify OpenIddict that the authorization grant has been denied by the resource owner
// to redirect the user agent to the client application using the appropriate response_mode.
public IActionResult Deny()
{
_logger.LogInformation("拒绝授权Authorize.Deny");
return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
#endregion
#region 获取Token地址 包括所有方式
/// <summary>
/// 可以指定不同的获取Token的客户端逻辑
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
[HttpPost("~/connect/token"), Produces("application/json")]
public async Task<IActionResult> Exchange()
{
try
{
_logger.LogInformation("开始处理令牌请求");
var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("OIDC请求不存在.");
if (request == null)
{
_logger.LogError("无法获取 OpenIddict 请求");
return BadRequest(new OpenIddictResponse
{
Error = OpenIddictConstants.Errors.InvalidRequest,
ErrorDescription = "无效的请求"
});
}
_logger.LogInformation("授权类型: {GrantType}", request.GrantType);
// 根据授权类型路由到不同的处理方法
return request.GrantType switch
{
GrantTypes.AuthorizationCode => await HandleAuthorizationCodeGrantTypeAsync(request),
GrantTypes.Password => await HandlePasswordGrantTypeAsync(request),
GrantTypes.ClientCredentials => await HandleClientCredentialsGrantTypeAsync(request),
GrantTypes.RefreshToken => await HandleRefreshTokenGrantTypeAsync(request),
_ => HandleUnsupportedGrantType(request)
};
}
catch (Exception ex)
{
_logger.LogError(ex, "处理令牌请求时发生异常");
return BadRequest(new OpenIddictResponse
{
Error = OpenIddictConstants.Errors.ServerError,
ErrorDescription = "服务器内部错误"
});
}
}
/// <summary>
/// 处理授权码流程
/// </summary>
private async Task<IActionResult> HandleAuthorizationCodeGrantTypeAsync(OpenIddictRequest request)
{
_logger.LogInformation("处理授权码流程,客户端:{clientId}", request.ClientId);
// Retrieve the claims principal stored in the authorization code/refresh token.
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
// Retrieve the user profile corresponding to the authorization code/refresh token.
var user = await _userManager.FindByIdAsync(result.Principal!.GetClaim(Claims.Subject)!);
if (user is null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid."
}));
}
// Ensure the user is still allowed to sign in.
if (!await _signInManager.CanSignInAsync(user))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
}));
}
//var identity = new ClaimsIdentity(result.Principal!.Claims,
// authenticationType: TokenValidationParameters.DefaultAuthenticationType,
// nameType: Claims.Name,
// roleType: Claims.Role);
//// Override the user claims present in the principal in case they
//// changed since the authorization code/refresh token was issued.
//identity.SetClaim(Claims.Subject, user.Id)
// .SetClaim(Claims.Email, user.Email)
// .SetClaim(Claims.Name, user.UserName)
// .SetClaim(Claims.Nickname, user.NickName)
// //.SetClaim(Claims.PhoneNumber, user.PhoneNumber)
// .SetClaims(Claims.Role, (await _userManager.GetRolesAsync(user)).ToImmutableArray());
var identity = await this.SetClaims(user, request);
identity.SetDestinations(GetDestinations);
_logger.LogInformation("授权码流程成功: {UserId}", user.Id);
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
/// <summary>
/// 处理密码授权流程
/// </summary>
private async Task<IActionResult> HandlePasswordGrantTypeAsync(OpenIddictRequest request)
{
_logger.LogInformation("处理密码授权流程,客户端:{clientId},用户名: {Username}", request.ClientId, request.Username);
// 验证必需参数
if (string.IsNullOrEmpty(request.Username) || string.IsNullOrEmpty(request.Password))
{
_logger.LogWarning("用户名或密码为空");
return BadRequest(new OpenIddictResponse
{
Error = OpenIddictConstants.Errors.InvalidRequest,
ErrorDescription = "用户名和密码不能为空"
});
}
// 查找用户
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null)
{
_logger.LogWarning("用户不存在: {Username}", request.Username);
return Unauthorized(new OpenIddictResponse
{
Error = OpenIddictConstants.Errors.InvalidGrant,
ErrorDescription = "用户名或密码不正确"
});
}
// 验证密码
var passwordValid = await _userManager.CheckPasswordAsync(user, request.Password);
if (!passwordValid)
{
_logger.LogWarning("密码验证失败: {Username}", request.Username);
return Unauthorized(new OpenIddictResponse
{
Error = OpenIddictConstants.Errors.InvalidGrant,
ErrorDescription = "用户名或密码不正确"
});
}
// 设置声明
var identity = await this.SetClaims(user, request);
// Set the list of scopes granted to the client application.
identity.SetScopes(request.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
identity.SetDestinations(GetDestinations);
_logger.LogInformation("密码授权成功: {Username}", request.Username);
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
/// <summary>
/// 处理客户端凭据流程
/// </summary>
private async Task<IActionResult> HandleClientCredentialsGrantTypeAsync(OpenIddictRequest request)
{
_logger.LogInformation("处理客户端凭据流程,客户端:{clientId}", request.ClientId);
var application = await _applicationManager.FindByClientIdAsync(request.ClientId!);
if (application == null)
{
throw new InvalidOperationException("The application details cannot be found in the database.");
}
// Create the claims-based identity that will be used by OpenIddict to generate tokens.
var identity = new ClaimsIdentity(
authenticationType: TokenValidationParameters.DefaultAuthenticationType,
nameType: Claims.Name,
roleType: Claims.Role);
// Add the claims that will be persisted in the tokens (use the client_id as the subject identifier).
identity.SetClaim(Claims.Subject, await _applicationManager.GetClientIdAsync(application));
identity.SetClaim(Claims.Name, await _applicationManager.GetDisplayNameAsync(application));
// Note: In the original OAuth 2.0 specification, the client credentials grant
// doesn't return an identity token, which is an OpenID Connect concept.
//
// As a non-standardized extension, OpenIddict allows returning an id_token
// to convey information about the client application when the "openid" scope
// is granted (i.e specified when calling principal.SetScopes()). When the "openid"
// scope is not explicitly set, no identity token is returned to the client application.
// Set the list of scopes granted to the client application in access_token.
identity.SetScopes(request.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());
identity.SetDestinations(GetDestinations);
_logger.LogInformation("客户端凭据授权成功: {ClientId}", request.ClientId);
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
/// <summary>
/// 处理刷新令牌流程
/// </summary>
private async Task<IActionResult> HandleRefreshTokenGrantTypeAsync(OpenIddictRequest request)
{
_logger.LogInformation("处理刷新令牌流程");
// Retrieve the claims principal stored in the refresh token.
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
// Retrieve the user profile corresponding to the refresh token.
var user = await _userManager.FindByIdAsync(result.Principal!.GetClaim(Claims.Subject)!);
if (user == null)
{
var properties = new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The refresh token is no longer valid."
});
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
// Ensure the user is still allowed to sign in.
if (!await _signInManager.CanSignInAsync(user))
{
var properties = new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
});
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
//var identity = new ClaimsIdentity(result.Principal!.Claims,
// authenticationType: TokenValidationParameters.DefaultAuthenticationType,
// nameType: Claims.Name,
// roleType: Claims.Role);
//// Override the user claims present in the principal in case they changed since the refresh token was issued.
//identity.SetClaim(Claims.Subject, user.Id)
// .SetClaim(Claims.Email, user.Email)
// .SetClaim(Claims.Name, user.UserName)
// .SetClaim(Claims.Nickname, user.NickName)
// //.SetClaim(Claims.PhoneNumber, user.PhoneNumber)
// .SetClaims(Claims.Role, (await _userManager.GetRolesAsync(user)).ToImmutableArray());
// 设置声明
var identity = await this.SetClaims(user, request);
identity.SetDestinations(GetDestinations);
_logger.LogInformation("刷新令牌成功: {UserId}", user.Id);
return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
/// <summary>
/// 处理不支持的授权类型
/// </summary>
private IActionResult HandleUnsupportedGrantType(OpenIddictRequest request)
{
_logger.LogWarning("不支持的授权类型: {GrantType}", request.GrantType);
return BadRequest(new OpenIddictResponse
{
Error = OpenIddictConstants.Errors.UnsupportedGrantType,
ErrorDescription = $"不支持的授权类型: {request.GrantType}"
});
}
/// <summary>
/// 设置声明
/// </summary>
/// <param name="user"></param>
/// <param name="request"></param>
/// <returns></returns>
private async Task<ClaimsIdentity> SetClaims(ApplicationUser user, OpenIddictRequest request)
{
// Create the claims-based identity that will be used by OpenIddict to generate tokens.
var identity = new ClaimsIdentity(
authenticationType: TokenValidationParameters.DefaultAuthenticationType,
nameType: Claims.Name,
roleType: Claims.Role);
// 获取请求的范围
var scopes = request.GetScopes();
// Add the claims that will be persisted in the tokens.
identity.SetClaim(Claims.Subject, user.Id)
.SetClaim(Claims.Name, user.UserName);
// 根据范围添加声明
if (scopes.Contains(Scopes.Profile))
{
identity.SetClaim(Claims.Nickname, user.NickName);
}
if (scopes.Contains(Scopes.Email))
{
identity.SetClaim(Claims.Email, user.Email);
}
// 只有明确请求 roles 范围时才包含角色
if (scopes.Contains(Scopes.Roles))
{
identity.SetClaims(Claims.Role, (await _userManager.GetRolesAsync(user)).ToImmutableArray());
}
return identity;
}
/// <summary>
/// 退出
/// </summary>
/// <param name="claim"></param>
/// <returns></returns>
private static IEnumerable<string> GetDestinations(Claim claim)
{
// Note: by default, claims are NOT automatically included in the access and identity tokens.
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
// whether they should be included in access tokens, in identity tokens or in both.
switch (claim.Type)
{
case Claims.Name or Claims.PreferredUsername:
yield return Destinations.AccessToken;
if (claim.Subject!.HasScope(Scopes.Profile))
yield return Destinations.IdentityToken;
yield break;
case Claims.Email:
yield return Destinations.AccessToken;
if (claim.Subject!.HasScope(Scopes.Email))
yield return Destinations.IdentityToken;
yield break;
case Claims.Role:
yield return Destinations.AccessToken;
if (claim.Subject!.HasScope(Scopes.Roles))
yield return Destinations.IdentityToken;
yield break;
// Never include the security stamp in the access and identity tokens, as it's a secret value.
case "AspNet.Identity.SecurityStamp": yield break;
default:
yield return Destinations.AccessToken;
yield break;
}
}
#endregion
/// <summary>
/// 前端调用-退出
/// </summary>
/// <returns></returns>
[HttpGet("~/connect/logout")]
public async Task<IActionResult> Logout()
{
_logger.LogInformation("用户退出开始.");
// Ask ASP.NET Core Identity to delete the local and external cookies created
// when the user agent is redirected from the external identity provider
// after a successful authentication flow (e.g Google or Facebook).
await _signInManager.SignOutAsync();
_logger.LogInformation("用户退出结束.");
// Returning a SignOutResult will ask OpenIddict to redirect the user agent
// to the post_logout_redirect_uri specified by the client application or to
// the RedirectUri specified in the authentication properties if none was set.
return SignOut(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties
{
RedirectUri = Url.Content("~/")
});
}
/// <summary>
/// 前端调用-退出
/// </summary>
/// <returns></returns>
[ActionName(nameof(Logout)), HttpPost("~/connect/logout"), ValidateAntiForgeryToken]
public async Task<IActionResult> LogoutPost()
{
_logger.LogInformation("用户退出开始.");
// Ask ASP.NET Core Identity to delete the local and external cookies created
// when the user agent is redirected from the external identity provider
// after a successful authentication flow (e.g Google or Facebook).
await _signInManager.SignOutAsync();
_logger.LogInformation("用户退出结束.");
// Returning a SignOutResult will ask OpenIddict to redirect the user agent
// to the post_logout_redirect_uri specified by the client application or to
// the RedirectUri specified in the authentication properties if none was set.
return SignOut(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties
{
RedirectUri = Url.Content("~/")
});
}
/// <summary>
/// 获取用户信息
/// </summary>
/// <returns></returns>
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
[HttpGet("~/connect/userinfo"), HttpPost("~/connect/userinfo"), Produces("application/json")]
public async Task<IActionResult> Userinfo()
{
_logger.LogInformation("获取用户信息开始.");
var user = await _userManager.FindByIdAsync(User.GetClaim(Claims.Subject)!);
if (user == null)
{
return Challenge(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidToken,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"The specified access token is bound to an account that no longer exists."
}));
}
_logger.LogInformation("获取用户信息,{userName}.", user.UserName);
var claims = new Dictionary<string, object?>(StringComparer.Ordinal)
{
// Note: the "sub" claim is a mandatory claim and must be included in the JSON response.
[Claims.Subject] = user.Id,
[Claims.Name] = user.UserName,
[Claims.Nickname] = user.NickName,
};
if (User.HasScope(Scopes.Email))
{
claims[Claims.Email] = user.Email;
}
if (User.HasScope(Scopes.Phone))
{
claims[Claims.PhoneNumber] = user.PhoneNumber;
}
if (User.HasScope(Scopes.Roles))
{
claims[Claims.Role] = await _userManager.GetRolesAsync(user);
}
// Note: the complete list of standard claims supported by the OpenID Connect specification
// can be found here: http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
_logger.LogInformation("获取用户信息结束,用户信息:{claims}", claims);
return Ok(claims);
}
}
}
新增配置appsettings.Development.json
cs
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=openiddict;User=root;Password=123456;SSL Mode=None;"
},
"IdentityServer": {
"Authority": "https://xxx.com/passport"
}
搭建资源API服务
新增代码入口程序
cs
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Validation.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddOpenIddict()
.AddValidation(options =>
{
// Note: the validation handler uses OpenID Connect discovery
// to retrieve the issuer signing keys used to validate tokens.
options.SetIssuer(configuration["IdentityServer:Authority"]);
options.AddAudiences(configuration["IdentityServer:Audience"]);
// Register the encryption credentials. This sample uses a symmetric
// encryption key that is shared between the server and the API project.
//
// Note: in a real world application, this encryption key should be
// stored in a safe place (e.g in Azure KeyVault, stored as a secret).
// options.AddEncryptionKey(new SymmetricSecurityKey(Convert.FromBase64String("DRjd/GnduI3Efzen9V9BvbNUfc/VKgXltV7Kbk9sMkY=")));
//options.AddEncryptionKey(new SymmetricSecurityKey(Convert.FromBase64String("DRjd/GnduI3Efzen9V9BvbNUfc/VKgXltV7Kbk9sMkY=")));
// Register the System.Net.Http integration.
options.UseSystemNetHttp();
// Register the ASP.NET Core host.
options.UseAspNetCore();
});
builder.Services.AddCors(options => options.AddDefaultPolicy(policy =>
policy.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin()));
builder.Services.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
builder.Services.AddAuthorization(options =>
{
// 定义 ITAdminOnly 策略:需要 role=admin AND name=zhangsan
options.AddPolicy("ZhangSanAdminOnly", policy =>
{
policy.RequireAuthenticatedUser(); // 必须认证
policy.RequireRole("admin"); // 必须拥有 Admin 角色
policy.RequireClaim("name", "zhangsan"); // 必须拥有 name=zhangsan 声明
});
// 可选:其他相关策略
options.AddPolicy("AdminOnly", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("admin"); // 必须拥有 Admin 角色
});
options.AddPolicy("ITDepartment", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("department", "IT");// 必须拥有 department=IT 声明
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
新增测试控制器WeatherForecastController.cs
cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace BigData.Sample.Api1.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
/// <summary>
/// 需要认证后才能访问
/// </summary>
/// <returns></returns>
[HttpGet("Get")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
/// <summary>
/// 基于policy授权的token才可以访问
/// </summary>
/// <returns></returns>
[Authorize("ZhangSanAdminOnly")]
[HttpGet("GetTime")]
public string GetTime()
{
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
/// <summary>
/// 基于角色的授权的token才可以访问
/// </summary>
/// <returns></returns>
[Authorize(Roles ="admin")]
[HttpGet("GetTime1")]
public string GetTime1()
{
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
}
}
新增配置appsettings.Development.json
cs
{
"IdentityServer": {
"Authority": "https://xxx.com/passport",
"Audience": "resource_server_1"
}
}
Postman测试结果
-
使用password方式获取token
-
使用token访问资源api

总结:
- 一个轻量级、易用、高性能的开源替代品。它更"现代化"和"敏捷",非常适合 ASP.NET Core 应用、API 保护和移动/SPA 应用集成。
- 完全开源免费