6.5 KiB
6.5 KiB
Task 5: Create OpenIddict Endpoints
Task Description
Files:
- Create:
src/Fengling.AuthService/Controllers/AuthorizationController.cs
Implementation Steps
Step 1: Create authorization endpoints
Create: src/Fengling.AuthService/Controllers/AuthorizationController.cs
using Fengling.AuthService.Models;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;
namespace Fengling.AuthService.Controllers;
public class AuthorizationController : Controller
{
private readonly IOpenIddictApplicationManager _applicationManager;
private readonly IOpenIddictAuthorizationManager _authorizationManager;
private readonly IOpenIddictScopeManager _scopeManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<AuthorizationController> _logger;
public AuthorizationController(
IOpenIddictApplicationManager applicationManager,
IOpenIddictAuthorizationManager authorizationManager,
IOpenIddictScopeManager scopeManager,
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager,
ILogger<AuthorizationController> logger)
{
_applicationManager = applicationManager;
_authorizationManager = authorizationManager;
_scopeManager = scopeManager;
_signInManager = signInManager;
_userManager = userManager;
_logger = logger;
}
[HttpPost("~/connect/token")]
[Produces("application/json")]
public async Task<IActionResult> Exchange()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
if (request.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null || user.IsDeleted)
{
return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户不存在"
}));
}
var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false);
if (!result.Succeeded)
{
return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户名或密码错误"
}));
}
var principal = await _signInManager.CreateUserPrincipalAsync(user);
var claims = new List<System.Security.Claims.Claim>
{
new(Claims.Subject, user.Id.ToString()),
new(Claims.Name, user.UserName ?? string.Empty),
new(Claims.Email, user.Email ?? string.Empty),
new("tenant_id", user.TenantId.ToString())
};
var roles = await _userManager.GetRolesAsync(user);
foreach (var role in roles)
{
claims.Add(new Claim(Claims.Role, role));
}
principal.SetScopes(request.GetScopes());
principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync());
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
return Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.UnsupportedGrantType,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "不支持的授权类型"
}));
}
[HttpGet("~/connect/authorize")]
[HttpPost("~/connect/authorize")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Authorize()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
var result = await HttpContext.AuthenticateAsync();
if (result == null || !result.Succeeded)
{
return Challenge(
new AuthenticationProperties
{
RedirectUri = Request.Path + Request.QueryString
},
OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
return Ok(new { message = "Authorization endpoint" });
}
[HttpPost("~/connect/logout")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync();
return SignOut(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
}
Step 2: Run to verify
Run:
dotnet run
Test with curl:
curl -X POST http://localhost:5000/connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password" \
-d "username=admin" \
-d "password=Admin@123" \
-d "scope=api offline_access"
Expected: 400 error (user doesn't exist yet, but endpoint is working)
Step 3: Commit
git add src/Fengling.AuthService/Controllers/AuthorizationController.cs
git commit -m "feat(auth): add OpenIddict authorization endpoints"
Context
This task creates OpenIddict OAuth2/OIDC endpoints including token exchange, authorize, and logout. The token endpoint supports password flow for direct authentication.
Tech Stack: OpenIddict 7.2.0, ASP.NET Core
Verification
- AuthorizationController created
- Token endpoint supports password flow
- Tenant ID added to token claims
- Build succeeds
- Committed to git
Notes
- Token endpoint returns JWT with tenant_id claim
- Logout endpoint clears session