NETCORE - OpenId 鉴权模式

NETCORE - OpenId 鉴权模式

  环境:.net8 + efcore8 + mysql8 + openIddict7  ,不集成ASP.NET Core Identity ,使用自已的用户表登录

一、 修改数据库

  数据库使用 mysql,创建数据库 openauth,字符集为 utf8mb3

  

二、 创建服务端

1. 项目搭建

  项目框架:.net8 + webapi 

  项目插件:EfCore8 + OpenIddict7

  image

     

2. 配置文件 

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",

  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Port=3306;Database=openauth;Uid=root;Pwd=123456;Charset=utf8mb3;SslMode=Preferred;AllowPublicKeyRetrieval=true;"
  }
}

 

 

 3. 生成数据库上下文文件

在程序包管理器控制台中执行:

Scaffold-DbContext -Connection "server=localhost;userid=root;password=123456;database=openauth;" -Provider Pomelo.EntityFrameworkCore.MySql -OutputDir Models -ContextDir Context -Context DataBaseOpenAuth -UseDatabaseNames -Force -NoOnConfiguring

 

创建部分类 Context / DataBaseOpenAuth.OpenIddict.cs

using Microsoft.EntityFrameworkCore;
using OpenIddict.EntityFrameworkCore.Models;

namespace Rail.AuthServer.Context
{
    public partial class DataBaseOpenAuth
    {
        // OpenIddict实体集
        public DbSet<OpenIddictEntityFrameworkCoreApplication> Applications { get; set; }
        public DbSet<OpenIddictEntityFrameworkCoreAuthorization> Authorizations { get; set; }
        public DbSet<OpenIddictEntityFrameworkCoreScope> Scopes { get; set; }
        public DbSet<OpenIddictEntityFrameworkCoreToken> Tokens { get; set; }

        partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
        {
            // 配置OpenIddict
            modelBuilder.UseOpenIddict();

            // 这里可以添加其他自定义配置
        }
    }
}

 

 

 

4. 注入OpenId

编辑 Program.cs

 

using Microsoft.EntityFrameworkCore;
using OpenIddict.Abstractions;
using OpenIddict.Validation.AspNetCore;
using Rail.AuthServer.Context;
using static OpenIddict.Abstractions.OpenIddictConstants;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


//Scaffold-DbContext -Connection "server=localhost;userid=root;password=123456;database=openauth;" -Provider Pomelo.EntityFrameworkCore.MySql -OutputDir Models -ContextDir Context -Context DataBaseOpenAuth -UseDatabaseNames -Force -NoOnConfiguring




// 添加数据库上下文
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<DataBaseOpenAuth>(options =>
{
    options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
    options.UseOpenIddict();
});


// 添加OpenIddict服务
builder.Services.AddOpenIddict()
    .AddCore(options =>
    {
        options.UseEntityFrameworkCore()
               .UseDbContext<DataBaseOpenAuth>();
    })
    .AddServer(options =>
    {
        // 设置端点
        options.SetTokenEndpointUris("connect/token");
        options.SetAuthorizationEndpointUris("connect/authorize");

        // 配置支持的授权类型
        options.AllowPasswordFlow()
               .AllowRefreshTokenFlow()
               .AllowClientCredentialsFlow()
               .AllowAuthorizationCodeFlow();

        // 各种token的默认过期时间
        options.SetAccessTokenLifetime(TimeSpan.FromHours(1));          // 访问令牌:1小时
        options.SetRefreshTokenLifetime(TimeSpan.FromDays(30));         // 刷新令牌:30天
        options.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(10)); // 授权码:10分钟
        options.SetIdentityTokenLifetime(TimeSpan.FromHours(1));        // 身份令牌:1小时
        options.SetUserCodeLifetime(TimeSpan.FromMinutes(5));           // 用户码:5分钟
        options.SetDeviceCodeLifetime(TimeSpan.FromMinutes(15));        // 设备码:15分钟

        // 刷新令牌设置 ,滚动刷新令牌配置 - OpenIddict 7 方式
        options.SetRefreshTokenReuseLeeway(TimeSpan.Zero); // 设置为0表示禁用重用



        // 注册签名和加密证书
        options.AddDevelopmentEncryptionCertificate()
               .AddDevelopmentSigningCertificate();

        // 注册范围
        options.RegisterScopes(
            Scopes.Email,
            Scopes.Profile,
            Scopes.Roles,
            Scopes.OfflineAccess); // 包含刷新令牌

        // 注册ASP.NET Core主机并配置选项
        options.UseAspNetCore()
               .EnableTokenEndpointPassthrough()
               .EnableAuthorizationEndpointPassthrough();
         

    })
    .AddValidation(options =>
    {
        options.UseLocalServer();
        options.UseAspNetCore();
    });

// 添加认证服务
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
});

// 添加授权服务
builder.Services.AddAuthorization();
 


builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();








var app = builder.Build();

// 初始化OpenIddict数据库
using (var scope = app.Services.CreateScope())
{
    var context = scope.ServiceProvider.GetRequiredService<DataBaseOpenAuth>();
    await context.Database.EnsureCreatedAsync();

    var manager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>();

    // 创建客户端应用(如果不存在)
    if (await manager.FindByClientIdAsync("console_app") is null)
    {
        await manager.CreateAsync(new OpenIddictApplicationDescriptor
        {
            ClientId = "console_app",
            ClientSecret = "console_app_secret",
            DisplayName = "Console Application",
            Permissions =
            {
                Permissions.Endpoints.Token,
                Permissions.Endpoints.Authorization,
                Permissions.GrantTypes.Password,
                Permissions.GrantTypes.RefreshToken,
                Permissions.GrantTypes.ClientCredentials,
                Permissions.ResponseTypes.Code,
                Permissions.Scopes.Email,
                Permissions.Scopes.Profile,
                Permissions.Scopes.Roles
            }
        });
    }
}


// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();


app.UseAuthentication();
app.UseAuthorization();


app.MapControllers();

app.Run();

 

 

 

 

 5. 运行

  运行后自动生成4张权限表

  image

 

 

6. 发现文档

https://localhost:7231/.well-known/openid-configuration

 

 返回结果:

{
    "issuer": "https://localhost:7231/",
    "authorization_endpoint": "https://localhost:7231/connect/authorize",
    "token_endpoint": "https://localhost:7231/connect/token",
    "jwks_uri": "https://localhost:7231/.well-known/jwks",
    "grant_types_supported": [
        "password",
        "refresh_token",
        "client_credentials"
    ],
    "scopes_supported": [
        "openid",
        "offline_access"
    ],
    "claims_supported": [
        "aud",
        "exp",
        "iat",
        "iss",
        "sub"
    ],
    "id_token_signing_alg_values_supported": [
        "RS256"
    ],
    "subject_types_supported": [
        "public"
    ],
    "prompt_values_supported": [
        "consent",
        "login",
        "none",
        "select_account"
    ],
    "token_endpoint_auth_methods_supported": [
        "client_secret_post",
        "private_key_jwt",
        "client_secret_basic"
    ],
    "require_pushed_authorization_requests": false,
    "claims_parameter_supported": false,
    "request_parameter_supported": false,
    "request_uri_parameter_supported": false,
    "tls_client_certificate_bound_access_tokens": false,
    "authorization_response_iss_parameter_supported": true
}

 

 

7. 创建Token控制器

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using System.Security.Claims;

namespace Rail.AuthServer.Controllers
{
    [ApiController]
    [Route("connect")]
    public class TokenController : ControllerBase
    {
        private readonly IOpenIddictApplicationManager _applicationManager;

        public TokenController(IOpenIddictApplicationManager applicationManager)
        {
            _applicationManager = applicationManager;
        }

        [HttpPost("token")]
        public async Task<IActionResult> Exchange()
        {
            var request = HttpContext.GetOpenIddictServerRequest();
            if (request == null)
            {
                return BadRequest("Invalid OpenID Connect request.");
            }

            if (request.IsPasswordGrantType())
            {
                return await HandlePasswordGrantType(request);
            }
            else if (request.IsClientCredentialsGrantType())
            {
                return await HandleClientCredentialsGrantType(request);
            }
            else if (request.IsRefreshTokenGrantType())
            {
                return await HandleRefreshTokenGrantType(request);
            }

            return BadRequest("The specified grant type is not supported.");
        }

        private async Task<IActionResult> HandlePasswordGrantType(OpenIddictRequest request)
        {
            // 这里实现您的用户验证逻辑
            // 注意:这不使用ASP.NET Core Identity

            var username = request.Username;
            var password = request.Password;

            // 示例:验证用户凭据
            if (username == "admin" && password == "password")
            {
                var identity = new ClaimsIdentity(
                    OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                    OpenIddictConstants.Claims.Name,
                    OpenIddictConstants.Claims.Role);

                // 添加声明
                identity.AddClaim(OpenIddictConstants.Claims.Subject, username);
                identity.AddClaim(OpenIddictConstants.Claims.Name, username);
                identity.AddClaim(ClaimTypes.Role, "Administrator");

                var principal = new ClaimsPrincipal(identity);
                principal.SetScopes(new[]
                {
                    OpenIddictConstants.Scopes.OpenId,
                    OpenIddictConstants.Scopes.Email,
                    OpenIddictConstants.Scopes.Profile,
                    OpenIddictConstants.Scopes.Roles
                });

                return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
            }

            return BadRequest("Invalid username or password.");
        }

        private async Task<IActionResult> HandleClientCredentialsGrantType(OpenIddictRequest request)
        {
            // 验证客户端凭据
            var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
            if (application == null)
            {
                return BadRequest("The specified client identifier is invalid.");
            }

            var identity = new ClaimsIdentity(
                OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                OpenIddictConstants.Claims.Name,
                OpenIddictConstants.Claims.Role);

            identity.AddClaim(OpenIddictConstants.Claims.Subject, request.ClientId);
            identity.AddClaim(OpenIddictConstants.Claims.Name, await _applicationManager.GetDisplayNameAsync(application));

            var principal = new ClaimsPrincipal(identity);
            principal.SetScopes(request.GetScopes());

            return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }

        private async Task<IActionResult> HandleRefreshTokenGrantType(OpenIddictRequest request)
        {
            // 获取当前用户信息(从认证信息中)
            var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
            if (!result.Succeeded)
            {
                return BadRequest("Invalid refresh token.");
            }

            var identity = new ClaimsIdentity(result.Principal.Claims,
                OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                OpenIddictConstants.Claims.Name,
                OpenIddictConstants.Claims.Role);

            var principal = new ClaimsPrincipal(identity);
            principal.SetScopes(request.GetScopes());

            return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }
    }
}

 

 

8.  创建受保护的API控制器

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace Rail.AuthServer.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    [Authorize]
    public class ProtectedController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok(new
            {
                Message = "This is a protected resource!",
                User = User.Identity.Name,
                Claims = User.Claims.Select(c => new { c.Type, c.Value })
            });
        }
    }
}

 

9. 测试

测试1

https://localhost:7231/connect/token

 

image

 

 

 

 

 

测试2

 

image

 

 

 测试3

image

 

10. 过期时间设置

  1) 全局设置  

// 添加OpenIddict服务
builder.Services.AddOpenIddict()
    .AddCore(options =>
    {
        options.UseEntityFrameworkCore()
               .UseDbContext<DataBaseOpenAuth>();
    })
    .AddServer(options =>
    {
        // 各种token的默认过期时间
        options.SetAccessTokenLifetime(TimeSpan.FromHours(1));          // 访问令牌:1小时
        options.SetRefreshTokenLifetime(TimeSpan.FromDays(30));         // 刷新令牌:30天
        options.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(10)); // 授权码:10分钟
        options.SetIdentityTokenLifetime(TimeSpan.FromHours(1));        // 身份令牌:1小时
        options.SetUserCodeLifetime(TimeSpan.FromMinutes(5));           // 用户码:5分钟
        options.SetDeviceCodeLifetime(TimeSpan.FromMinutes(15));        // 设备码:15分钟

        // 刷新令牌设置 ,滚动刷新令牌配置 - OpenIddict 7 方式
        options.SetRefreshTokenReuseLeeway(TimeSpan.Zero); // 设置为0表示禁用重用
    })

 

 

  2)为特定客户端设置

    // Client Credentials 客户端
    if (await manager.FindByClientIdAsync("service_app") is null)
    {
        await manager.CreateAsync(new OpenIddictApplicationDescriptor
        {
            ClientId = "service_app",
            ClientSecret = "service_secret",
            DisplayName = "Service Application",
            Permissions = { Permissions.Endpoints.Token, Permissions.GrantTypes.ClientCredentials },
            Settings =
            {
                // 为特定客户端设置不同的过期时间
                [OpenIddictConstants.Settings.TokenLifetimes.AccessToken] = "01:00:00", // 1小时
                [OpenIddictConstants.Settings.TokenLifetimes.RefreshToken] = "30.00:00:00" // 30天
            }
        });
    }

 

  3)动态设置

   在TokenController中动态设置过期时间
 
        private async Task<IActionResult> HandlePasswordGrantType(OpenIddictRequest request)
        {
            var username = request.Username;
            var password = request.Password;

            // 自定义用户验证逻辑
            if (await ValidateUserCredentialsAsync(username, password))
            {
                var identity = new ClaimsIdentity(
                    authenticationType: TokenValidationParameters.DefaultAuthenticationType,
                    nameType: Claims.Name,
                    roleType: Claims.Role);

                // 添加声明
                identity.AddClaim(Claims.Subject, username);
                identity.AddClaim(Claims.Name, username);
                identity.AddClaim(Claims.Email, $"{username}@example.com");
                identity.AddClaim(Claims.Role, "User");

                // 设置主体和范围
                var principal = new ClaimsPrincipal(identity);
                principal.SetScopes(
                    Scopes.OpenId,
                    Scopes.Email,
                    Scopes.Profile,
                    Scopes.OfflineAccess); // 包含刷新令牌

                principal.SetResources("resource_server");

                //return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

                // OpenIddict 7 中设置自定义属性的新方式
                var properties = new AuthenticationProperties(new Dictionary<string, string?>
                {
                    // 使用新的常量名称
                    ["oi_atm"] = TimeSpan.FromHours(2).TotalSeconds.ToString(),  // Access Token Lifetime
                    ["oi_rtm"] = TimeSpan.FromDays(14).TotalSeconds.ToString(),  // Refresh Token Lifetime
                    ["oi_itim"] = TimeSpan.FromHours(2).TotalSeconds.ToString()  // Identity Token Lifetime
                });
                return SignIn(principal, properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
            }

   期于角色设置

        private async Task<IActionResult> HandlePasswordGrantType(OpenIddictRequest request)
        {
            var username = request.Username;
            var password = request.Password;

            // 自定义用户验证逻辑
            if (await ValidateUserCredentialsAsync(username, password))
            {
                var identity = new ClaimsIdentity(
                    authenticationType: TokenValidationParameters.DefaultAuthenticationType,
                    nameType: Claims.Name,
                    roleType: Claims.Role);

                // 添加声明
                identity.AddClaim(Claims.Subject, username);
                identity.AddClaim(Claims.Name, username);
                identity.AddClaim(Claims.Email, $"{username}@example.com");
                identity.AddClaim(Claims.Role, "User");

                // 设置主体和范围
                var principal = new ClaimsPrincipal(identity);
                principal.SetScopes(
                    Scopes.OpenId,
                    Scopes.Email,
                    Scopes.Profile,
                    Scopes.OfflineAccess); // 包含刷新令牌

                principal.SetResources("resource_server");

                //return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

                //// OpenIddict 7 中设置自定义属性的新方式

                TimeSpan accessTokenLifetime;
                TimeSpan refreshTokenLifetime;

                // 根据用户角色设置不同的过期时间
                if (principal.IsInRole("Administrator"))
                {
                    accessTokenLifetime = TimeSpan.FromHours(4);    // 管理员4小时
                    refreshTokenLifetime = TimeSpan.FromDays(60);   // 管理员60天
                }
                else
                {
                    accessTokenLifetime = TimeSpan.FromHours(1);    // 普通用户1小时
                    refreshTokenLifetime = TimeSpan.FromDays(7);    // 普通用户7天
                }

                var properties = new AuthenticationProperties(new Dictionary<string, string?>
                {
                    // 使用新的常量名称
                    ["oi_atm"] = accessTokenLifetime.TotalSeconds.ToString(),  // Access Token Lifetime
                    ["oi_rtm"] = refreshTokenLifetime.TotalSeconds.ToString(),  // Refresh Token Lifetime
                });
                return SignIn(principal, properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
            }

            return BadRequest(new OpenIddictResponse
            {
                Error = Errors.InvalidGrant,
                ErrorDescription = "Invalid username or password."
            });
        }

 

 

 

10. 使令牌失效

在Program.cs里面使用 Reference Token 模式

//使用 Reference Token
options.UseReferenceAccessTokens(); 
options.UseReferenceRefreshTokens();

 

 

创建退出登录接口、管理员使用户退出接口

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions;
using OpenIddict.Validation.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace Rail.AuthServer.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    [Authorize]
    public class LogoutController : ControllerBase
    {
        private readonly IOpenIddictTokenManager _tokenManager;

        public LogoutController(IOpenIddictTokenManager tokenManager)
        {
            _tokenManager = tokenManager;
        }

        [HttpPost("logout")]
        public async Task<IActionResult> Logout()
        {
            // 这里的 sub 是用户的唯一标识(UserId)
            var userId = User.FindFirst(OpenIddictConstants.Claims.Subject)?.Value;
            if (string.IsNullOrEmpty(userId))
            {
                return BadRequest("User id not found in token.");
            }

            await foreach (var token in _tokenManager.FindBySubjectAsync(userId))
            {
                await _tokenManager.TryRevokeAsync(token);
            }

            return Ok(new { message = "User logged out, all tokens revoked." });
        }

        [HttpPost("revoke-user-tokens")]
        public async Task<IActionResult> RevokeUserTokens([FromBody] string userId)
        {
            await foreach (var token in _tokenManager.FindBySubjectAsync(userId))
            {
                await _tokenManager.TryRevokeAsync(token);
            }

            return Ok(new { message = $"All tokens for user {userId} revoked." });
        }


        [HttpPost("revoke-client")]
        public async Task<IActionResult> RevokeClientTokens([FromBody] string clientId, [FromServices] IOpenIddictApplicationManager applicationManager, [FromServices] IOpenIddictTokenManager tokenManager)
        {
            // 先找到 Application
            var application = await applicationManager.FindByClientIdAsync(clientId);
            if (application == null)
            {
                return NotFound(new { message = $"Client {clientId} not found." });
            }

            // 再查这个 Application 下面的所有 Token
            await foreach (var token in tokenManager.FindByApplicationIdAsync(await applicationManager.GetIdAsync(application)))
            {
                await tokenManager.TryRevokeAsync(token);
            }

            return Ok(new { message = $"All tokens for client {clientId} revoked." });
        }
    }
}

 

 

 

11. 使刷新令牌

  通过用户登录获取到刷新令牌 

1756709407701

 

 

1756709728904

 

 

12. 集成到 Swagger 

在 Program.cs 里面中配置

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new() { Title = "My API", Version = "v1" });

    // 定义 Bearer 安全方案
    options.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
    {
        Name = "Authorization",
        Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http,
        Scheme = "bearer",
        BearerFormat = "JWT",
        In = Microsoft.OpenApi.Models.ParameterLocation.Header,
        Description = "在下方输入 'Bearer {token}'"
    });

    // 在每个请求中应用安全要求
    options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
    {
        {
            new Microsoft.OpenApi.Models.OpenApiSecurityScheme
            {
                Reference = new Microsoft.OpenApi.Models.OpenApiReference
                {
                    Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] {}
        }
    });
});
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(options =>
    {
        options.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
        options.DocumentTitle = "My API Docs";
    });
}

 

image

 

   

13. 获取用户信息

增加控制器 UserInfoController 

[ApiController]
[Route("connect")]
public class UserInfoController : ControllerBase
{
    [HttpGet("userinfo")]
    [HttpPost("userinfo")]
    [Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
    public async Task<IActionResult> UserInfo()
    {
        var claims = new Dictionary<string, object>
        {
            [Claims.Subject] = User.FindFirst(Claims.Subject)?.Value ?? "",
            [Claims.Name] = User.FindFirst(Claims.Name)?.Value ?? "",
            [Claims.Email] = User.FindFirst(Claims.Email)?.Value ?? "",
            [Claims.Role] = User.FindFirst(Claims.Role)?.Value ?? "User"
        };

        return Ok(claims);
    }
}

 

image

 

14. 单独客户端项目 使用权限服务

1)鉴权服务项目

并不需要 /connect/authorize 这个控制器

需要设置 resource_server ,在/connect/token接口里面,每个登录校验里面 设置

principal.SetResources("resource_server");

 

 

2)API 项目

创建 .net8  客户端项目 Rail.TmClient

增加 nuget 包 OpenIddict.Validation.AspNetCore ,版本:7.0.0

增加 nuget 包 OpenIddict.Validation.SystemNetHttp ,版本:7.0.0

image

 

 调整Program.cs

using OpenIddict.Validation.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

// 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 =>
    {
        options.SetIssuer("https://localhost:7231/"); // 授权服务器的地址
        options.AddAudiences("resource_server"); // 资源服务器的标识

        // Reference Token → 必须用 introspection
        options.UseIntrospection()
               .SetClientId("service_app")   // 你在 AuthServer 注册的 client_id
               .SetClientSecret("service_secret");     // 你在 AuthServer 注册的 client_secret

        options.UseSystemNetHttp(); // 使用 System.Net.Http 进行 HTTP 请求
        options.UseAspNetCore(); // 与 ASP.NET Core 集成
    });

// 添加认证服务
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
});

builder.Services.AddAuthorization();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthentication(); // 注意顺序
app.UseAuthorization();
app.MapControllers();

app.Run();

 

 增加控制器

[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    // 需要 token 才能访问
    [HttpGet]
    [Authorize]
    public IActionResult GetOrders()
    {
        return Ok(new[] { "Order1", "Order2" });
    }

    // 允许匿名访问
    [HttpGet("public")]
    [AllowAnonymous]
    public IActionResult PublicInfo()
    {
        return Ok("任何人都能访问,不需要 Token");
    }
}

 

 

 调用结果

image

 

 

15. scopes 的使用

 在 授权服务器配置里,需要注册可用 Scope

// 添加OpenIddict服务
builder.Services.AddOpenIddict()
    .AddServer(options =>
    {
        // 注册范围
        options.RegisterScopes(
            Scopes.Email,
            Scopes.Profile,
            Scopes.Roles,
            Scopes.OfflineAccess,// 包含刷新令牌
            "api",
            "client_tm_server.read",
            "client_tm_server.write");
    })

 

ScopeResource/Audience 在 OpenIddict 中对齐

在 /connect/token 获取token校验接口里面设置,这里以客户端凭据为例,还需要设置 密码凭据,refresh_token 凭据

private async Task<IActionResult> HandleClientCredentialsGrantType(OpenIddictRequest request)
{
    var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
    if (application is null)
    {
        return BadRequest(new OpenIddictResponse
        {
            Error = Errors.InvalidClient,
            ErrorDescription = "The specified client identifier is invalid."
        });
    }

    var identity = new ClaimsIdentity(
        authenticationType: TokenValidationParameters.DefaultAuthenticationType,
        nameType: Claims.Name,
        roleType: Claims.Role);

    identity.AddClaim(Claims.Subject, request.ClientId);
    identity.AddClaim(Claims.Name, await _applicationManager.GetDisplayNameAsync(application));

    var principal = new ClaimsPrincipal(identity);
    principal.SetScopes(request.GetScopes());
    //principal.SetResources("client_tm_server");
    // 根据 Scope 设置 Resource / Audience
    var resources = new List<string>();
    if (principal.HasScope("client_tm_server.read") || principal.HasScope("client_tm_server.write"))
    {
        resources.Add("client_tm_server");
    }

    return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}

 

 
在 API 里面控制
方法1 
[Authorize]
[HttpGet("values")]
public IActionResult GetValues()
{
    // 获取 token 中的 scope
    var scopes = User.GetScopes(); // 扩展方法
    if (!scopes.Contains("rail_tmclient_api.read"))
    {
        return Forbid();
    }

    return Ok(new[] { "value1", "value2" });
}

 

方法2

可以在 Program.csStartup.cs 注册策略: 

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ReadRailTmclient", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("scope", "rail_tmclient_api.read");
    });

    options.AddPolicy("WriteRailTmclient", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("scope", "rail_tmclient_api.write");
    });
});

然后在 Controller 或 Action 上使用策略: 

[Authorize(Policy = "ReadRailTmclient")]
[HttpGet("values")]
public IActionResult GetValues()
{
    return Ok(new[] { "value1", "value2" });
}

[Authorize(Policy = "WriteRailTmclient")]
[HttpPost("values")]
public IActionResult CreateValue([FromBody] string value)
{
    return Ok();
}

 

  

 

 

 

 

 

 

 

 

 

 

 

 

待优化

3. 反代 

 

 

 

 

 

 

 

 

 

 

 

 

 

end

posted @ 2025-08-28 14:00  无心々菜  阅读(24)  评论(0)    收藏  举报