基于IdentityServer4的单点登录——IdentityServer

1.新建项目并添加引用

新建一个asp .net core 2.0的项目
引用IdentityServer4.AspNetIdentity

2.定义资源

新建Config.cs文件,定义Api资源与Identity资源
资源分为身份资源(Identity resources)和API资源(API resources)

(1)身份资源(Identity resources)

身份资源是用户的用户名,姓名或电子邮件地址等数据。身份资源具有唯一的名称,可以为其分配任意声明类型。然后,这些声明将被包含在用户的身份令牌中。客户端将使用scope参数来请求访问身份资源。
身份资源可以是IdentityServer自带的资源,也可以是自定义的资源。

public static IEnumerable<IdentityResource> GetIdentityResources()
{
    //自定义身份资源
    var customProfile = new IdentityResource(
        name: "custom.profile",
        displayName: "Custom profile",
        claimTypes: new[] { "name", "email", "status" });
    return new List<IdentityResource>
    {
        //IdentityServer自带的资源
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        customProfile
    };
}

(2)API资源(API resources)

API资源是客户端访问API获得的资源

public static IEnumerable<ApiResource> GetApiResources()
{
    return new List<ApiResource>
    {
        //具有单一范围的简单API(在这种情况下,范围名称与api名称相同)
        new ApiResource("api1", "My API"),
        // 扩展版本,更多的设置选项
        new ApiResource
        {
            Name = "api2",

            // 用于内部校验的密码
            // ApiSecrets是用于内部校验的,API的名称(Name)和密码(ApiSecrets)可以用于认证
            ApiSecrets =
            {
                new Secret("secret".Sha256())
            },

            // 在访问令牌中包含以下内容(除了subject ID)
            // UserClaims表示用户声明
            UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.Email },

            // // 该API定义了两个范围
            Scopes =
            {
                new Scope()
                {
                    Name = "api2.full_access",
                    DisplayName = "Full access to API 2",
                },
                new Scope
                {
                    Name = "api2.read_only",
                    DisplayName = "Read only access to API 2"
                }
            }
        }
    };
}

3.定义客户端Client

客户端指想要访问资源的Client

public static IEnumerable<Client> GetClients()
{
    return new List<Client>
    {
        //不一定要配置得这么复杂,根据需求来
        new Client
        {
            ClientId = "mvc",
            ClientName = "MVC Client",
            AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,

            RequireConsent = true,

            ClientSecrets = 
            {
                new Secret("secret".Sha256())
            },
            //这里配置客户端的网址,多个客户端则是多个网址
            RedirectUris = { "http://localhost:5002/signin-oidc" },
            PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
            //允许的范围
            AllowedScopes =
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                "api1"
            },
            AllowOfflineAccess = true
        }
    };
}

其他配置请参阅:
https://identityserver4.readthedocs.io/en/release/topics/clients.html

4.配置

把资源信息和客户端信息交给IdentityServer

public void ConfigureServices(IServiceCollection services)
{
    //配置服务
    services.AddIdentityServer()
        //读取客户端列表
        .AddInMemoryClients(Clients.Get())
        //读取身份资源列表
        .AddInMemoryIdentityResources(Resources.GetIdentityResources())
        //读取API资源列表
        .AddInMemoryApiResources(Resources.GetApiResources())
        //设置临时签名凭据
        .AddDeveloperSigningCredential()
        //添加测试用户
        .AddTestUsers(TestUsers.Users);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    //配置管道:注意配置管道要写在路由的前面...
    app.UseIdentityServer();
}

这里需要注意的是,以上的资源信息和客户端信息是存在内存中的,可以设置为读取数据库
参阅:https://identityserver4.readthedocs.io/en/release/quickstarts/8_entity_framework.html

5.多客户端的统一登录

(1)用户表

//ApplicationUser继承于IdentityUser,可以在此为用户表添加属性,字段
public class ApplicationUser : IdentityUser
{
}

(2)页面

<div class="col-md-4">
    <section>
        <form asp-route-returnurl="@ViewData["ReturnUrl"]" method="post">
            <h4>Use a local account to log in.</h4>
            <hr />
            <div asp-validation-summary="All" class="text-danger"></div>
            <div class="form-group">
                <!--整个流程是根据邮箱进行注册和登录的-->
                <label asp-for="Email"></label>
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Password"></label>
                <input asp-for="Password" class="form-control" />
                <span asp-validation-for="Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <div class="checkbox">
                    <label asp-for="RememberMe">
                        <input asp-for="RememberMe" />
                        @Html.DisplayNameFor(m => m.RememberMe)
                    </label>
                </div>
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-default">Log in</button>
            </div>
            <div class="form-group">
                <p>
                    <a asp-action="ForgotPassword">Forgot your password?</a>
                </p>
                <p>
                    <a asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]">Register as a new user?</a>
                </p>
            </div>
        </form>
    </section>
</div>

(3)登录方法

这里是AspNet Identity的内容

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
    ViewData["ReturnUrl"] = returnUrl;
    if (ModelState.IsValid)
    {
        // 尝试登录
        // 需要注意的是,这里没有对登录失败进行计数
        // 为了在登录失败多次之后触发锁定用户,把lockoutOnFailure设置为true
        var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            _logger.LogInformation("User logged in.");
            return RedirectToLocal(returnUrl);
        }
        if (result.RequiresTwoFactor)
        {
            return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
        }
        if (result.IsLockedOut)
        {
            _logger.LogWarning("User account locked out.");
            return RedirectToAction(nameof(Lockout));
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return View(model);
        }
    }
    return View(model);
}

6.设置IdentityServer项目的端口号为5000

7.其他

整个流程还需要包括注册,注销,忘记密码,第三方登录,修改用户信息等,这些不属于IdentityServer的讨论范畴,在此不做讨论

posted @ 2017-12-05 14:06  Lulus  阅读(5759)  评论(2编辑  收藏  举报