187 lines
6.5 KiB
Markdown
187 lines
6.5 KiB
Markdown
# 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`
|
|
|
|
```csharp
|
|
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:
|
|
```bash
|
|
dotnet run
|
|
```
|
|
|
|
Test with curl:
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|