.Net6基于IdentityServer4搭建认证授权服务

发布时间 2023-05-04 14:35:09作者: kele-cc

新建一个名为Ids4.Server.Net6的空项目,引用包源IdentityServer4

添加Config配置类

using IdentityServer4.Models;
using static IdentityServer4.IdentityServerConstants;

namespace Ids4.Server;

public class Config
{
    public static IEnumerable<IdentityResource> IdentityResources =>
        new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile()
        };

    public static IEnumerable<ApiScope> ApiScopes =>
        new ApiScope[]
        {
            new ApiScope("api2", "My Api2")
        };

    public static IEnumerable<Client> Clients =>
        new List<Client>
        {
            new Client
            {
                ClientId = "client_api2",
				// 要使用RefreshToken时,必须要把AllowedGrantTypes设置为授权代码、混合和资源所有者密码凭证流
                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, //GrantTypes.ClientCredentials,
                ClientSecrets =
                {
                    new Secret("secret".Sha256()) //secret加密密钥 Sha256加密方式
                },
                AllowedScopes =
                {
                    "api2",
                    StandardScopes.OfflineAccess,
                },
				// 刷新Token时RefreshToken保持不变
                RefreshTokenUsage = TokenUsage.ReUse,
                RefreshTokenExpiration = TokenExpiration.Sliding,
				// RefreshToken过期时间
                SlidingRefreshTokenLifetime = 3600,
				// 明确授权请求刷新令牌
                AllowOfflineAccess = true,
				// TOken过期时间
                AccessTokenLifetime = 60,
            }
        };
}

添加UserInfoModel类,模拟数据库用户信息实体

namespace Ids4.Server;

public class UserInfoModel
{
    public string Id { get; set; }

    public string Username { get; set; }

    public string Password { get; set; }

    public string Role { get; set; }
}

添加静态类UserData,模拟数据库数据

namespace Ids4.Server;

public static class UserData
{
    /// <summary>
    /// 模拟数据库存储的用户信息
    /// </summary>
    /// <returns></returns>
    public static List<UserInfoModel> GetListUsers()
    {
        return new List<UserInfoModel>
        {
            new UserInfoModel
            {
                Id = "1",
                Username = "zhangsan",
                Password = "123456",
                Role = "admin"
            },
            new UserInfoModel
            {
                Id = "2",
                Username = "lisi",
                Password = "123456",
                Role = "Test1,Test2"
            },
            new UserInfoModel
            {
                Id = "2",
                Username = "lisi",
                Password = "123456",
                Role = "Test2"
            }
        };
    }
}

添加ResourceOwnerPasswordValidator类继承IResourceOwnerPasswordValidator实现ValidateAsync。该方法用于客户端请求获取Token时校验用户信息是否存在

using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Validation;

namespace Ids4.Server;

public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
    public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        // 根据username和password查询用户是否存在
        var user = UserData.GetListUsers().FirstOrDefault(p => p.Username == context.UserName && p.Password == context.Password);
        if (user != null)
        {
            // 返回Id,为下一步获取角色权限做准备
            context.Result = new GrantValidationResult(user.Id, OidcConstants.AuthenticationMethods.Password);
        }
        else
        {
            context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Invalid credentials");
        }
    }
}

添加ProfileService类继承IProfileService实现GetProfileDataAsync。该方法用于将用户的角色信息添加到Token

using System.Security.Claims;
using IdentityModel;
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using IdentityServer4.Services;

namespace Ids4.Server;

public class ProfileService : IProfileService
{
    /// <summary>
    /// 根据Id拿到用户所属角色,添加到Token中
    /// </summary>
    /// <param name="context"></param>
    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var user = UserData.GetListUsers().FirstOrDefault(p => p.Id == context.Subject.GetSubjectId());

        // 存在多个权限时
        var roleArr = user.Role.Split(",");

        var claims = new List<Claim>();
        foreach (var item in roleArr)
        {
            claims.Add(new Claim(JwtClaimTypes.Role, item));
        }

        context.IssuedClaims = claims;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        context.IsActive = true;
    }
}

添加RefreshTokenService类继承IRefreshTokenService实现CreateRefreshTokenAsyncUpdateRefreshTokenAsyncValidateRefreshTokenAsync三个方法

using System.Security.Claims;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Validation;

namespace Ids4.Server;

public class RefreshTokenService : IRefreshTokenService
{
    /// <summary>
    /// 模拟RefreshToken存在缓存中
    /// </summary>
    private readonly static Dictionary<string, RefreshToken> _refreshTokens = new Dictionary<string, RefreshToken>();

    /// <summary>
    /// 创建刷新token
    /// </summary>
    /// <param name="subject"></param>
    /// <param name="accessToken"></param>
    /// <param name="client"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    public async Task<string> CreateRefreshTokenAsync(ClaimsPrincipal subject, Token accessToken, Client client)
    {
        var handle = Guid.NewGuid().ToString();
        var refreshToken = new RefreshToken
        {
            AccessToken = accessToken,
            CreationTime = DateTime.UtcNow,
            Lifetime = client.RefreshTokenExpiration == TokenExpiration.Sliding ? client.SlidingRefreshTokenLifetime : client.AbsoluteRefreshTokenLifetime,
            //Subject = subject,
            Version = 1
        };
        _refreshTokens[handle] = refreshToken;
        return await Task.FromResult(handle);
    }

    /// <summary>
    /// 修改刷新token
    /// </summary>
    /// <param name="handle"></param>
    /// <param name="refreshToken"></param>
    /// <param name="client"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    public async Task<string> UpdateRefreshTokenAsync(string handle, RefreshToken refreshToken, Client client)
    {
        if (!_refreshTokens.ContainsKey(handle))
        {
            throw new ArgumentException("Invalid refresh token handle");
        }

        _refreshTokens[handle] = refreshToken;
        return await Task.FromResult(handle);
    }

    /// <summary>
    /// 验证刷新token
    /// </summary>
    /// <param name="token"></param>
    /// <param name="client"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    public async Task<TokenValidationResult> ValidateRefreshTokenAsync(string token, Client client)
    {
        if (!_refreshTokens.TryGetValue(token, out var refreshToken))
        {
            return await Task.FromResult(new TokenValidationResult
            {
                IsError = true,
                Error = "Invalid refresh token"
            });
        }

        if (refreshToken.AccessToken.ClientId != client.ClientId)
        {
            return await Task.FromResult(new TokenValidationResult
            {
                IsError = true,
                Error = "Refresh token does not belong to the client"
            });
        }

        if (DateTime.UtcNow > refreshToken.CreationTime.AddSeconds(refreshToken.Lifetime))
        {
            return await Task.FromResult(new TokenValidationResult
            {
                IsError = true,
                Error = "Refresh token has expired"
            });
        }

        return await Task.FromResult(new TokenValidationResult
        {
            IsError = false,
            RefreshToken = refreshToken
        });
    }
}

Program注入

builder.Services.AddTransient<IRefreshTokenService, RefreshTokenService>();
builder.Services.AddIdentityServer()
    .AddDeveloperSigningCredential()
    .AddInMemoryApiScopes(Config.ApiScopes)
    .AddInMemoryClients(Config.Clients)
    .AddInMemoryIdentityResources(Config.IdentityResources)
    .AddProfileService<ProfileService>()
    .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>();
	
	
	app.UseIdentityServer();

项目结构
image

至此,基于IdentityServer4的认证授权服务就搭建完成了
使用postman请求获取AccessToken
image

使用RefreshToken刷新AccessToken
image

解析后的Token
image

源码地址:https://gitee.com/nzyGetHub/Microservice2.git