Fork me on GitHub

IdentityServer4 实现 OAuth 2.0(密码模式 - HTTP Post 方式)

之前写了一篇文章:《IdentityServer4 实现 OpenID Connect 和 OAuth 2.0

上面这篇文章虽然详细,但都是点到为止的介绍,并没有实际应用的示例,所以,后面在真正去实现的时候,踩到了自己之前种下的很多坑。

业务场景:前后端分离项目,前端调用后端业务服务需要授权访问(提供access_token),access_token在用户登录的时候(用户名和密码登录),由授权中心生成access_token并返回给前端,这样前端就可以拿到access_token,去调用后端业务服务了。

一开始,我使用的GrantTypes.Implicit模式,登录页面在授权中心,登录成功之后会跳到callback.htm#access_token=*页面,前端调用使用oidc-client组件,然后获取access_token,当时使用还没什么,现在觉得真是一团乱麻,前后端分离的项目,在授权中心居然把登录页面放在服务中了,但我后面还是没有意识到GrantTypes.Implicit的问题,而是尝试在这种模式下,写HTTP Post请求授权中心(提供用户名和密码),然后没然后,一团糟。。。

使用 IdentityServer4 实现上面的业务场景,其实很简单,只要使用GrantTypes.GrantTypes.ResourceOwnerPassword模式,就可以了。

Startup.ConfigureServices配置代码:

var builder = services.AddIdentityServer();
builder.AddTemporarySigningCredential()
        //.AddInMemoryIdentityResources(Config.GetIdentityResources())
        .AddInMemoryApiResources(Config.GetApiResources())
        .AddInMemoryClients(new List<Client>
        {
            new Client
            {
                ClientId = "client_id_1",
                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                AllowOfflineAccess = true,
                AccessTokenLifetime = 3600 * 6, //6小时
                SlidingRefreshTokenLifetime = 1296000, //15天
                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },
                AllowedScopes =
                {
                    IdentityServerConstants.StandardScopes.OfflineAccess, 
                    "api1"
                }
            }});
builder.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>();

ResourceOwnerPasswordValidator示例代码:

public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
    private readonly IUserService _userService;

    public ResourceOwnerPasswordValidator(IUserService userService)
    {
        _userService = userService;
    }

    public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        var userId = await _userService.Login(context.UserName, context.Password);
        if (userId != 0)
        {
            context.Result = new GrantValidationResult(userId.ToString(), OidcConstants.AuthenticationMethods.Password);
        }
    }
}

使用ResourceOwnerPasswordValidator的作用,就是自定义用户登录的用户名密码判断,而不是使用 IdentityServer4 的TestUser

请求示例:IdentityServer4 Token Endpoint

获取access_token请求示例:

刷新access_token请求示例:

也可以服务端进行请求,示例代码:

private async Task<TokenResponse> GetToken(string clientId, string clientSecret, string grantType, string userName, string password, string scope)
{
    var client = new DiscoveryClient($"http://localhost:5001");
    client.Policy.RequireHttps = false;
    var disco = await client.GetAsync();
    var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, clientSecret);
    return await tokenClient.RequestResourceOwnerPasswordAsync(userName, password, scope);
}

private async Task<TokenResponse> GetRefreshToken(string clientId, string clientSecret, string grantType, string refreshToken)
{
    var client = new DiscoveryClient($"http://localhost:5001");
    client.Policy.RequireHttps = false;
    var disco = await client.GetAsync();
    var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, clientSecret);
    return await tokenClient.RequestRefreshTokenAsync(refreshToken);
}

参考资料:

posted @ 2017-05-30 21:47  田园里的蟋蟀  阅读(3615)  评论(6编辑  收藏  举报