.NET Core OAuth IdentityServer4 Token

身份验证和 ASP.NET Web API 中的授权 
IdentityServer4 Reference Token
IdentityServer4 1.0.0 documentation(官网)    

业务逻辑
● 搭建 授权服务器 和 资源服务器
● 给App客户端发放 AppId 和 AppSecret
● 用户向App客户端提供自己的 账号 和 密码
● App客户端将AppId、AppSecret、账号、密码提交到 授权服务器
● 授权服务器通过授权,发放token和refresh_token
● 客户端通过 token 与 资源服务器 进行对接,并对 token 进行管理,防止失效

定义

Authentication:认证,身份验证,确定用户是谁;想要说明白的是 你是谁(你的身份是什么)

Authorization:授权,确定用户能做什么,不能做什么;想要说明白的是 你能做什么(得到了什么权限)

授权

授权筛选器(Authorization filter)在 action 之前运行。若请求未授权,返回错误,action 不运行。
在 action 内部,可以用 ApiController.User 属性获取主体对象,做进一步的控制。
[Authorize] 属性
AuthorizeAttribute 是内置的授权筛选器。用户未通过身份验证时,它返回 HTTP 401 状态码。可以在全局,控制和 action 三个级别应用它。
参考网址:ASP.NET MVC 筛选器

自定义授权筛选器

AuthorizeAttribute:基于用户和角色进行授权。
AuthorizationFilterAttribute:不基于用户和角色的同步授权。
IAuthorizationFilter:实现此接口执行异步授权逻辑。例如,授权逻辑中有对 IO 或网络的异步调用。(CPU-bound的授权逻辑更适合从 AuthorizationFilterAttribute 派生,这样不必写异步方法)。

全局级别应用:【\App_Start\WebApiConfig.cs】应用

public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}

控制器级别应用

[Authorize]
public class ValuesController : ApiController
{
    public IEnumerable Get()
    {
        return new string[] { "value1", "value2" };
    }
}

Action 级别应用

public class ValuesController : ApiController
{
    [Authorize]
    public string Get(int id)
    {
        return "value";
    }
}

控制器应用 [Authorize] 时,在 Action 应用 [AllowAnonymous] 取消某个 Action 授权

[Authorize]
public class ValuesController : ApiController
{
    [AllowAnonymous]
    public string Get(int id)
    {
        return "value";
    }
}

指定角色和用户进行限制

[Authorize(Roles = "Administrators", Users = "Alice,Bob")]
public class ValuesController : ApiController
{
    public string Get(int id)
    {
        return "value";
    }
}

代码参考

1、NuGet 引用
   Microsoft.AspNetCore.Authentication.OAuth
   IdentityServer4

2、资源配置(Config.cs)

using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Test;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace OAuth2IdentityServer.OAuth2
{
    public class Config
    {
        public static IEnumerable GetIdentityResourceResources()
        {
            return new List
            {
                new IdentityResources.OpenId(), //必须要添加,否则报无效的scope错误
                new IdentityResources.Profile(),
                new IdentityResources.Email(),
                new IdentityResource
                {
                    Name = "role",
                    UserClaims = new List {"role"}
                }
            };
        }

        public static IEnumerable GetScopes()
        {
            return new List
            {
                new Scope
                {
                    Name = "api1",
                    Description = "My API",
                },
                //如果想带有RefreshToken,那么必须设置:StandardScopes.OfflineAccess
                //StandardScopes.OfflineAccess,
            };
        }

        public static IEnumerable GetApiResources()
        {
            return new List
            {
                new ApiResource("clientservice", "CAS Client Service"),
                new ApiResource("agentservice", "CAS Agent Service"),
                new ApiResource("productservice", "CAS Product Service")
                {
                    ApiSecrets = { new Secret("api1pwd".Sha256()) }
                },
                new ApiResource
                {
                    Name = "customAPI",
                    DisplayName = "Custom API",
                    Description = "Custom API Access",
                    UserClaims = new List {"role"},
                    ApiSecrets = new List {new Secret("scopeSecret".Sha256())},
                    Scopes = new List
                    {
                        new Scope("customAPI.read"),
                        new Scope("customAPI.write")
                    }
                }
            };
        }

        public static IEnumerable GetResources()
        {
            return new List
            {
                new ApiResource { Name = "ImageResource", Scopes ={ new Scope ("ImageResource") }},//Scopes必须配置,否则获取token时返回 invalid_scope                
                new ApiResource { Name = "FileResourse" },
                new ApiResource { Name ="Api", Scopes = { new Scope ("Api") }}
            };
        }

        /// 
        /// Define which uses will use this IdentityServer
        /// 
        /// 
        public static IEnumerable GetUsers()
        {
            return new[]
            {
                new TestUser
                {
                    SubjectId = "10001",
                    Username = "[email protected]",
                    Password = "edisonpassword"
                },
                new TestUser
                {
                    SubjectId = "10002",
                    Username = "[email protected]",
                    Password = "andypassword"
                },
                new TestUser
                {
                    SubjectId = "10003",
                    Username = "[email protected]",
                    Password = "leopassword"
                },
                new TestUser
                {
                    SubjectId = "5BE86359-073C-434B-AD2D-A3932222DABE",
                    Username = "scott",
                    Password = "password",
                    Claims = new List
                    {
                        new Claim(JwtClaimTypes.Email, "[email protected]"),
                        new Claim(JwtClaimTypes.Role, "admin")
                    }
                }
            };
        }
    }
}

3、资源配置(Clients.cs)

using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Stores;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace OAuth2IdentityServer.OAuth2
{
    public class Clients
    {
        /// 
        /// 授权模式:客户端模式 GrantTypes.ClientCredentials
        /// 
        /// 
        public static IEnumerable GetClients()
        {
            return new List
            {
                //client credentials client(OK)
                new Client
                {
                    ClientId = "ClientIdOK",
                    AllowedGrantTypes = GrantTypes.ClientCredentials,//授权模式:客户端模式
                    AllowedScopes = { "ImageResource","Api" }, //允许访问的资源 GetResources()中配置的
                    ClientSecrets = { new Secret { Value= "ClientSecret".Sha256(), Expiration = DateTime.Now.AddMinutes(5)} }
                },
                //client credentials client(OK)
                new Client
                {
                    ClientId = "client1",
                    ClientName = "Example Client Credentials Client Application",
                    AllowedGrantTypes = GrantTypes.ClientCredentials,//授权模式:客户端模式
                    AllowedScopes = //对应的是 “OAuth2.Config.GetApiResources()” 或 “OAuth2.Config.GetResources()”中的资源
                    {
                        "ImageResource", "Api",
                        //IdentityServerConstants.StandardScopes.OpenId, //加上此参数报错
                        //IdentityServerConstants.StandardScopes.Profile,//加上此参数报错
                        //IdentityServerConstants.StandardScopes.OfflineAccess,//加上此参数报错
                    },
                    ClientSecrets = { new Secret("181a7853053b4be9b0ac9d9c709f3ecd".Sha256()) },
                    AllowedCorsOrigins = new List { "http://localhost:44389" },
                    RedirectUris = { "http://localhost:6321/Home/AuthCode" },//登录成功重定向地址
                    PostLogoutRedirectUris = { "http://localhost:44389/" },//退出重定向地址
                    AccessTokenLifetime = 3600, //AccessToken过期时间, in seconds (defaults to 3600 seconds / 1 hour)
                    AuthorizationCodeLifetime = 300,  //设置AuthorizationCode的有效时间,in seconds (defaults to 300 seconds / 5 minutes)
                    AbsoluteRefreshTokenLifetime = 2592000,  //RefreshToken的最大过期时间,in seconds. Defaults to 2592000 seconds / 30 day
                },
                // resource owner password grant client
                new Client
                {
                    ClientId = "client2",
                    ClientName = "Example Client Credentials Client Application",
                    AccessTokenType = AccessTokenType.Jwt,
                    //AccessTokenType = AccessTokenType.Reference,
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                    ClientSecrets = { new Secret("ClientSecret".Sha256()) },
                    AllowedCorsOrigins = new List { "http://localhost:44389" },
                    RefreshTokenExpiration = TokenExpiration.Sliding,
                    SlidingRefreshTokenLifetime = 5*60,
                    UpdateAccessTokenClaimsOnRefresh = true,
                    AllowOfflineAccess = true, //刷新令牌来进行长时间的API访问
                    AllowedScopes = new List 
                    { 
                        "ImageResource", 
                        "Api",
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.OfflineAccess, 
                    },
                    RedirectUris = { "http://localhost:6321/Home/AuthCode" },
                    PostLogoutRedirectUris = { "http://localhost:6321/" },
                    AccessTokenLifetime = 3600, //AccessToken过期时间, in seconds (defaults to 3600 seconds / 1 hour)
                    AuthorizationCodeLifetime = 300,  //设置AuthorizationCode的有效时间,in seconds (defaults to 300 seconds / 5 minutes)
                    AbsoluteRefreshTokenLifetime = 2592000,  //RefreshToken的最大过期时间,in seconds. Defaults to 2592000 seconds / 30 day
                },
                //Implicit
                new Client
                {
                    ClientId = "openIdConnectClient",
                    ClientName = "Example Implicit Client Application",
                    AllowedGrantTypes = GrantTypes.Implicit,
                    AllowedScopes = new List
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.Email,
                        "role",
                        "customAPI.write"
                    },
                    RedirectUris = new List {"https://localhost:44330/signin-oidc"},
                    PostLogoutRedirectUris = new List {"https://localhost:44330"},
                    AccessTokenLifetime = 3600, //AccessToken过期时间, in seconds (defaults to 3600 seconds / 1 hour)
                    AuthorizationCodeLifetime = 300,  //设置AuthorizationCode的有效时间,in seconds (defaults to 300 seconds / 5 minutes)
                    AbsoluteRefreshTokenLifetime = 2592000,  //RefreshToken的最大过期时间,in seconds. Defaults to 2592000 seconds / 30 day
                }
            };
        }

        public static IEnumerable GetClientsTest()
        {
            return new List
            {
                new Client
                {
                    ClientId = "ClientId",
                    AllowedGrantTypes = GrantTypes.ClientCredentials,//授权模式:客户端模式
                    AllowedScopes ={ "ImageResource","Api" }, //允许访问的资源 GetResources()中配置的
                    ClientSecrets ={ new Secret { Value= "ClientSecret".Sha256(), Expiration=DateTime.Now.AddMinutes(5)} }
                }
            };
        }
    }
}

4、ResourceOwnerPasswordValidator.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Validation;

namespace OAuth2IdentityServer.OAuth2
{
    public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
    {
        private readonly IUserRepository _userRepo;//数据库仓储类
        private readonly IReferenceTokenStore _reference;
        private readonly IPersistedGrantStore _persisted;

        public ResourceOwnerPasswordValidator(IUserRepository userRepo, IReferenceTokenStore reference, IPersistedGrantStore persisted)
        {
            _userRepo = userRepo;
            _reference = reference;
            _persisted = persisted;
        }

        public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
        {
            if (context.Request.GrantType == GrantType.AuthorizationCode)
            {
                string a = "";
            }
            else if (context.Request.GrantType == GrantType.ResourceOwnerPassword)
            {
                #region 获取所有请求参数:grant_type、client_Id、client_secret、username、password、eid、device_id
                var validatedRequestDictionary = context.Request.Raw.AllKeys.ToDictionary(s => s, s => context.Request.Raw[s]);
                string eid = validatedRequestDictionary["eid"];
                string device_id = validatedRequestDictionary["device_id"];
                #endregion
            }

            //根据context.UserName和context.Password与数据库的数据做校验,判断是否合法
            if (context.UserName == "wjk" && context.Password == "123")
            {
                _reference.RemoveReferenceTokensAsync(context.UserName, clientId);
                _persisted.RemoveAllAsync(context.UserName, clientId);

                context.Result = new GrantValidationResult(
                 subject: context.UserName,
                 authenticationMethod: "custom",
                 claims: GetUserClaims());
            }
            else
            {
                //验证失败
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential");
            }
        }

        //可以根据需要设置相应的Claim
        private Claim[] GetUserClaims()
        {
            return new Claim[]
            {
                new Claim("UserId", 1.ToString()),
                new Claim(JwtClaimTypes.Name,"wjk"),
                new Claim(JwtClaimTypes.GivenName, "jaycewu"),
                new Claim(JwtClaimTypes.FamilyName, "yyy"),
                new Claim(JwtClaimTypes.Email, "[email protected]"),
                new Claim(JwtClaimTypes.Role,"admin")
            };
        }
    }
}

5、ProfileService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityServer4.Services;

namespace OAuth2IdentityServer.OAuth2
{
    public class ProfileService : IProfileService
    {
        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            try
            {
                //depending on the scope accessing the user data.
                var claims = context.Subject.Claims.ToList();

                //set issued claims to return
                context.IssuedClaims = claims.ToList();
            }
            catch (Exception ex)
            {
                //log your error
            }
        }

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

6、Startup.cs(“认证服务器”配置 IdentityServer,颁发 token)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using OAuth2IdentityServer.OAuth2;

namespace OAuth2IdentityServer
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            #region 对应 grant_type = Client_Credentials 模式(测试通过 - OK)

            //services.AddIdentityServer()
            //    .AddDeveloperSigningCredential()
            //    .AddInMemoryApiResources(Config.GetResources())
            //    .AddInMemoryClients(Clients.GetClients());

            #endregion

            #region 对应 grant_type = Resource_Owner_Password 模式(测试通过 - OK)

            //services.AddIdentityServer()
            //    .AddDeveloperSigningCredential()
            //    .AddInMemoryApiResources(Config.GetResources())
            //    .AddInMemoryClients(Clients.GetClients())
            //    .AddResourceOwnerValidator()
            //    .AddProfileService();

            #endregion

            #region 同时兼容 Client_Credentials 和 Resource_Owner_Password 模式(测试通过 - OK)
            services.AddIdentityServer()
                .AddDeveloperSigningCredential()
                .AddInMemoryApiResources(Config.GetResources())
                .AddInMemoryClients(Clients.GetClients())
                .AddResourceOwnerValidator()
                .AddProfileService();
            #endregion

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseIdentityServer();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

7、Program.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace OAuth2IdentityServer
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup();
    }
}

8、启动项目之后,浏览器输入 https://localhost:44305/.well-known/openid-configuration 查看文档(端口号以项目运行的真实地址为准)

9、获取token(Client Credentials 模式)

.NET Core OAuth IdentityServer4 Token_第1张图片

10、获取token(Resource Owner Password 模式)

.NET Core OAuth IdentityServer4 Token_第2张图片

扩展参数、扩展参数、扩展参数
注意、注意、注意、除了必要参数还可以添加扩展参数,比如:eid,device_id
.NET Core OAuth IdentityServer4 Token_第3张图片

刷新令牌:获取 token 并且包含刷新令牌 refresh_token(Resource Owner Password 模式)
refresh_token 的作用就是,在 access_token 过期的时候,不需要再通过一些凭证申请 access_token,而是直接通过 refresh_token 就可以重新申请 access_token。如果标记了offline_access,则生成 refreshToken,否则不生成。
.NET Core OAuth IdentityServer4 Token_第4张图片
*
利用refresh_token(刷新令牌),更新access_token(token值)
refresh_token 的作用就是,在 access_token 过期的时候,不需要再通过一些凭证申请 access_token,而是直接通过 refresh_token 就可以重新申请 access_token。
如果 Clients 的 AllowedScopes 包含 IdentityServerConstants.StandardScopes.OpenId 或 IdentityServerConstants.StandardScopes.Profile 响应信息Response中会包含 id_token
.NET Core OAuth IdentityServer4 Token_第5张图片

.NET Core OAuth IdentityServer4 Token_第6张图片
*
利用 token 获取当前登录信息(connect/userinfo)
.NET Core OAuth IdentityServer4 Token_第7张图片

*、资源服务器,Startup.cs 配置如下:

using IdentityModel;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace ImageResourceApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(config =>
            {
                config.DefaultScheme = "Bearer";
            }).AddIdentityServerAuthentication(option =>
            {
                option.Authority = "https://localhost:44308"; //认证服务的url
                option.ApiName = "ImageResource";
                option.ApiSecret = "ClientSecret".ToSha256();// 访问的secret
                option.SaveToken = true;
                //options.EnableCaching = true;
                //options.CacheDuration = TimeSpan.FromMinutes(10);
                option.RequireHttpsMetadata = false;//如果授权认证服务器访问地址是https开头,设置成true,如果授权认证服务器访问地址是http开头,设置成false【注意注意】
            });

            方式2(待验证)
            services.AddAuthentication("Bearer")
            .AddJwtBearer("Bearer", options =>
            {
                options.Authority = Configuration["IdentityAuthentication:Authority"];
                options.RequireHttpsMetadata = true;
                options.Audience = Configuration["IdentityAuthentication:ApiName"];
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateLifetime = true,//是否验证失效时间
                    ClockSkew = TimeSpan.FromSeconds(30),
                };
                options.Events = new JwtBearerEvents
                {
                    OnAuthenticationFailed = context =>
                    {
                        //Token expired
                        if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                        {
                            context.Response.Headers.Add("Token-Expired", "true");
                        }
                        context.Response.ContentType = "application/json";
                        context.Response.WriteAsync(JsonConvert.SerializeObject(new { code = "401", msg = "Unauthorized" }));
                        return Task.CompletedTask;
                    }
                };
            });
            方式2(待验证)

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();

            //使用认证
            app.UseAuthentication();

            app.UseMvc();
        }
    }
}

*、资源服务器:在需要进行授权验证的资源接口(controller控制器或方法)上设置AuthorizeAttribute:

using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace ImageResourceApi.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        // GET api/values
        [HttpGet]
        public ActionResult> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public ActionResult Get(int id)
        {
            return "value";
        }

        // POST api/values
        [HttpPost]
        public void Post([FromBody] string value)
        {
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody] string value)
        {
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}

*、客户端 发送web请求 资源服务器,PostMan方式如下:

.NET Core OAuth IdentityServer4 Token_第8张图片

.NET Core OAuth IdentityServer4 Token_第9张图片

【注意:】“认证服务器”中\OAuth2\Clients.cs =>AllowedScopes  要和 “资源服务器” Startup.cs => option.ApiName 的值相匹配

*、

*、

*、

 

你可能感兴趣的:(.NET,Core)