mirror of
https://gitee.com/dotnetchina/OpenAuth.Net.git
synced 2026-07-14 03:23:48 +08:00
🔄refactor: 使用OpenIddict 重构Identity
This commit is contained in:
165
OpenAuth.Identity/Controllers/AuthorizationController.cs
Normal file
165
OpenAuth.Identity/Controllers/AuthorizationController.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Infrastructure;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenAuth.App;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Server.AspNetCore;
|
||||
using static OpenIddict.Abstractions.OpenIddictConstants;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理 OpenID Connect 授权请求
|
||||
/// </summary>
|
||||
public class AuthorizationController : Controller
|
||||
{
|
||||
private readonly UserManagerApp _userManager;
|
||||
|
||||
public AuthorizationController(UserManagerApp userManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理授权端点请求 /connect/authorize
|
||||
/// </summary>
|
||||
[HttpGet("~/connect/authorize")]
|
||||
[HttpPost("~/connect/authorize")]
|
||||
public async Task<IActionResult> Authorize()
|
||||
{
|
||||
var request = HttpContext.GetOpenIddictServerRequest()
|
||||
?? throw new InvalidOperationException("无法获取 OpenID Connect 请求。");
|
||||
|
||||
// 尝试获取已登录用户
|
||||
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
// 未登录 → 重定向到登录页面
|
||||
return Challenge(
|
||||
authenticationSchemes: new[] { CookieAuthenticationDefaults.AuthenticationScheme },
|
||||
properties: new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
|
||||
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
|
||||
});
|
||||
}
|
||||
|
||||
// 已登录 → 从 Cookie 中获取用户信息
|
||||
var userId = result.Principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return Challenge(
|
||||
authenticationSchemes: new[] { CookieAuthenticationDefaults.AuthenticationScheme },
|
||||
properties: new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
|
||||
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
|
||||
});
|
||||
}
|
||||
|
||||
// 加载用户信息
|
||||
var user = GetUser(userId);
|
||||
if (user == null)
|
||||
{
|
||||
return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
// 构建 Claims
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(Claims.Subject, user.Id),
|
||||
new Claim(Claims.Name, user.Name ?? user.Account),
|
||||
new Claim(ClaimTypes.Name, user.Account),
|
||||
};
|
||||
|
||||
var identity = new ClaimsIdentity(claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
// 设置请求的 scopes
|
||||
principal.SetScopes(request.GetScopes());
|
||||
|
||||
// 设置 access_token 的 audience(对应 WebApi 的 options.Audience)
|
||||
principal.SetResources("openauthapi");
|
||||
|
||||
// 设置 Claims 目标(决定哪些 Claims 出现在 access_token / id_token 中)
|
||||
foreach (var claim in principal.Claims)
|
||||
{
|
||||
claim.SetDestinations(GetDestinations(claim, principal));
|
||||
}
|
||||
|
||||
// 签发授权码
|
||||
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理登出端点请求 /connect/logout
|
||||
/// </summary>
|
||||
[HttpGet("~/connect/logout")]
|
||||
[HttpPost("~/connect/logout")]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
// 清除本地 Cookie
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
// 通知 OpenIddict 登出完成
|
||||
return SignOut(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户ID获取用户
|
||||
/// </summary>
|
||||
private OpenAuth.Repository.Domain.SysUser GetUser(string id)
|
||||
{
|
||||
if (id == Define.SYSTEM_USERNAME)
|
||||
{
|
||||
return new OpenAuth.Repository.Domain.SysUser
|
||||
{
|
||||
Account = Define.SYSTEM_USERNAME,
|
||||
Id = Define.SYSTEM_USERNAME,
|
||||
Name = Define.SYSTEM_USERNAME
|
||||
};
|
||||
}
|
||||
|
||||
return _userManager.Get(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确定 Claim 的目标(access_token / id_token)
|
||||
/// </summary>
|
||||
private static IEnumerable<string> GetDestinations(Claim claim, ClaimsPrincipal principal)
|
||||
{
|
||||
switch (claim.Type)
|
||||
{
|
||||
case Claims.Name:
|
||||
yield return Destinations.AccessToken;
|
||||
if (principal.HasScope(Scopes.Profile))
|
||||
yield return Destinations.IdentityToken;
|
||||
yield break;
|
||||
|
||||
case ClaimTypes.Name:
|
||||
yield return Destinations.AccessToken;
|
||||
if (principal.HasScope(Scopes.Profile))
|
||||
yield return Destinations.IdentityToken;
|
||||
yield break;
|
||||
|
||||
case Claims.Subject:
|
||||
yield return Destinations.AccessToken;
|
||||
yield return Destinations.IdentityToken;
|
||||
yield break;
|
||||
|
||||
default:
|
||||
yield return Destinations.AccessToken;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
135
OpenAuth.Identity/Controllers/TokenController.cs
Normal file
135
OpenAuth.Identity/Controllers/TokenController.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Infrastructure;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenAuth.App;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Server.AspNetCore;
|
||||
using static OpenIddict.Abstractions.OpenIddictConstants;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理 OpenID Connect Token 请求
|
||||
/// </summary>
|
||||
public class TokenController : Controller
|
||||
{
|
||||
private readonly UserManagerApp _userManager;
|
||||
|
||||
public TokenController(UserManagerApp userManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理令牌端点请求 /connect/token
|
||||
/// </summary>
|
||||
[HttpPost("~/connect/token")]
|
||||
public async Task<IActionResult> Exchange()
|
||||
{
|
||||
var request = HttpContext.GetOpenIddictServerRequest()
|
||||
?? throw new InvalidOperationException("无法获取 OpenID Connect 请求。");
|
||||
|
||||
if (request.IsAuthorizationCodeGrantType())
|
||||
{
|
||||
// 从授权码中还原 ClaimsPrincipal
|
||||
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return Forbid(
|
||||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
||||
properties: new AuthenticationProperties(new Dictionary<string, string>
|
||||
{
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "授权码无效或已过期。"
|
||||
}));
|
||||
}
|
||||
|
||||
var principal = result.Principal;
|
||||
|
||||
// 可选:补充最新的用户信息到 Claims
|
||||
var userId = principal.GetClaim(Claims.Subject);
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
{
|
||||
var user = GetUser(userId);
|
||||
if (user == null || user.Status != 0)
|
||||
{
|
||||
return Forbid(
|
||||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
||||
properties: new AuthenticationProperties(new Dictionary<string, string>
|
||||
{
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户不存在或已被禁用。"
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// 设置 Claims 目标
|
||||
foreach (var claim in principal.Claims)
|
||||
{
|
||||
claim.SetDestinations(GetDestinations(claim, principal));
|
||||
}
|
||||
|
||||
// 确保 access_token 包含正确的 audience
|
||||
principal.SetResources("openauthapi");
|
||||
|
||||
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("不支持的授权类型。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户ID获取用户
|
||||
/// </summary>
|
||||
private OpenAuth.Repository.Domain.SysUser GetUser(string id)
|
||||
{
|
||||
if (id == Define.SYSTEM_USERNAME)
|
||||
{
|
||||
return new OpenAuth.Repository.Domain.SysUser
|
||||
{
|
||||
Account = Define.SYSTEM_USERNAME,
|
||||
Id = Define.SYSTEM_USERNAME,
|
||||
Name = Define.SYSTEM_USERNAME,
|
||||
Status = 0
|
||||
};
|
||||
}
|
||||
|
||||
return _userManager.Get(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确定 Claim 的目标
|
||||
/// </summary>
|
||||
private static IEnumerable<string> GetDestinations(Claim claim, ClaimsPrincipal principal)
|
||||
{
|
||||
switch (claim.Type)
|
||||
{
|
||||
case Claims.Name:
|
||||
yield return Destinations.AccessToken;
|
||||
if (principal.HasScope(Scopes.Profile))
|
||||
yield return Destinations.IdentityToken;
|
||||
yield break;
|
||||
|
||||
case ClaimTypes.Name:
|
||||
yield return Destinations.AccessToken;
|
||||
if (principal.HasScope(Scopes.Profile))
|
||||
yield return Destinations.IdentityToken;
|
||||
yield break;
|
||||
|
||||
case Claims.Subject:
|
||||
yield return Destinations.AccessToken;
|
||||
yield return Destinations.IdentityToken;
|
||||
yield break;
|
||||
|
||||
default:
|
||||
yield return Destinations.AccessToken;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
OpenAuth.Identity/Controllers/UserinfoController.cs
Normal file
92
OpenAuth.Identity/Controllers/UserinfoController.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Infrastructure;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OpenAuth.App;
|
||||
using OpenIddict.Abstractions;
|
||||
using OpenIddict.Server.AspNetCore;
|
||||
using static OpenIddict.Abstractions.OpenIddictConstants;
|
||||
|
||||
namespace OpenAuth.IdentityServer.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理 OpenID Connect Userinfo 请求
|
||||
/// </summary>
|
||||
public class UserinfoController : Controller
|
||||
{
|
||||
private readonly UserManagerApp _userManager;
|
||||
|
||||
public UserinfoController(UserManagerApp userManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理 /connect/userinfo 请求,返回已认证用户的个人信息
|
||||
/// </summary>
|
||||
[HttpGet("~/connect/userinfo")]
|
||||
[HttpPost("~/connect/userinfo")]
|
||||
public async Task<IActionResult> Userinfo()
|
||||
{
|
||||
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return Challenge(
|
||||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
||||
properties: new AuthenticationProperties(new Dictionary<string, string>
|
||||
{
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidToken,
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "访问令牌无效或已过期。"
|
||||
}));
|
||||
}
|
||||
|
||||
var userId = result.Principal.GetClaim(Claims.Subject);
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return Challenge(
|
||||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
||||
properties: new AuthenticationProperties(new Dictionary<string, string>
|
||||
{
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidToken,
|
||||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "令牌中缺少用户标识。"
|
||||
}));
|
||||
}
|
||||
|
||||
// 构建 userinfo 响应
|
||||
var claims = new Dictionary<string, object>
|
||||
{
|
||||
[Claims.Subject] = userId
|
||||
};
|
||||
|
||||
// 如果请求了 profile scope,返回用户名称信息
|
||||
if (result.Principal.HasScope(Scopes.Profile))
|
||||
{
|
||||
var user = GetUser(userId);
|
||||
if (user != null)
|
||||
{
|
||||
claims[Claims.Name] = user.Name ?? user.Account;
|
||||
claims[Claims.PreferredUsername] = user.Account;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(claims);
|
||||
}
|
||||
|
||||
private OpenAuth.Repository.Domain.SysUser GetUser(string id)
|
||||
{
|
||||
if (id == Define.SYSTEM_USERNAME)
|
||||
{
|
||||
return new OpenAuth.Repository.Domain.SysUser
|
||||
{
|
||||
Account = Define.SYSTEM_USERNAME,
|
||||
Id = Define.SYSTEM_USERNAME,
|
||||
Name = Define.SYSTEM_USERNAME
|
||||
};
|
||||
}
|
||||
|
||||
return _userManager.Get(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user