欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.NET Core IdentityServer4实战 第Ⅴ章-单点登录

程序员文章站 2022-09-07 10:35:48
OiDc可以说是OAuth的改造版,在最初的OAuth中,我们需要先请求一下认证服务器获取下Access_token,然后根据Access_token去Get资源服务器, 况且OAuth1 和 2 完全不兼容,易用性差,而OIDC可以在登陆的时候就把信息返回给你,不需要你在请求一下资源服务器。下面我 ......

  oidc可以说是oauth的改造版,在最初的oauth中,我们需要先请求一下认证服务器获取下access_token,然后根据access_token去get资源服务器, 况且oauth1 和 2 完全不兼容,易用性差,而oidc可以在登陆的时候就把信息返回给你,不需要你在请求一下资源服务器。下面我们根据oidc来做一个单点登录。

  新建三个项目(.net core mvc)两个client(端口5001,5002),一个server(5000),首先在server中添加identityserver4的引用。

  在server中config.cs用于模拟配置。

    public class config
    {
        public static ienumerable<apiresource> getapiresource()
        {
            return new list<apiresource>
            {
                new apiresource("api","my api app")
            };
        }
        public static ienumerable<client> getclients()
        {
            return new list<client>
            {
                new client()
                {
                    clientid = "mvc",
                    allowedgranttypes = granttypes.implicit,
                    clientsecrets ={
                        new secret("secret".sha256())
                    },
                    requireconsent = false,
                    redirecturis = {"http://localhost:5001/signin-oidc",
                        "http://localhost:5002/signin-oidc" } ,
                    postlogoutredirecturis = {"http://localhost:5001/signout-callback-oidc" ,
                        "http://localhost:5002/signout-callback-oidc" },
                    allowedscopes = {
                        identityserverconstants.standardscopes.profile,
                        identityserverconstants.standardscopes.openid
                    }
                }
            };
        }
        public static list<testuser> gettestusers()
        {
            return new list<testuser>
            {
                new testuser()
                {
                    subjectid = "10000",
                    username = "zara",
                    password = "112233"
                }
            };
        }
        public static ienumerable<identityresource> getidentityresources()
        {
            return new list<identityresource>
            {
                new identityresources.openid(),
                new identityresources.profile(),
                new identityresources.email()
            };
        }
    }

getclient方法中字段为redirecturis是登陆成功返回的地址,并且我们采用隐式模式(因为只是传统web中传递access_token),requireconsent是否出现同意授权页面,这个我们后续再细说.写完config.cs后,我们需要依赖注入到identityserver中。

public void configureservices(iservicecollection services)
        {
            services.configure<cookiepolicyoptions>(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;
            });
        //config to identityserver services services.addidentityserver() .adddevelopersigningcredential() .addinmemoryclients(config.getclients()) .addtestusers(config.gettestusers()) .addinmemoryidentityresources(config.getidentityresources()) .addinmemoryapiresources(config.getapiresource()); services.addmvc().setcompatibilityversion(compatibilityversion.version_2_1); }

 在configure中添加代码 app.useidentityserver(); .我们还需要添加一个登陆页面,名为account.cshtml.

@{
    viewdata["title"] = "index";
}

<h2>index</h2>

@using mvcwebfirstsolucation.models;
@model loginvm;

<div class="row">
    <div class="col-md-4">
        <section>
            <form method="post" asp-controller="account" asp-action="login" asp-route-returnurl="@viewdata["returnurl"]">
                <h4>use a local to log in .</h4>
                <hr />
                <div class="from-group">
                    <label asp-for="username"></label>
                    <input asp-for="username" class="form-control">
                    <span asp-validation-for="username" class="text-danger"></span>
                </div>
                <div class="from-group">
                    <label asp-for="password"></label>
                    <input asp-for="password" type="password" class="form-control">
                    <span asp-validation-for="username" class="text-danger"></span>
                </div>
                <div class="from-group">
                    <button type="submit" class="btn btn-default">log in </button>
                </div>
            </form>
        </section>
    </div>
</div>
@section scripts
{
    @await html.partialasync("_validationscriptspartial")
}

在控制器中我们写一个构造函数,用于将identityserver.text给我们封装好的对象传过来,这个对象是我们在config.cs中添加的用户信息,也就是getclients的返回值,全都在 testuserstore 中。其中还有一个提供好的方法,来给我们用,如果验证通过我们直接跳转到了传递过来的returnurl.

    public class accountcontroller : controller
    {
        private readonly testuserstore _users;
        public accountcontroller(testuserstore ussotre)
        {
            _users = ussotre;
        }
        [httpget]
        [route("/account/login")]
        public iactionresult index(string returnurl = null)
        
{
            viewdata["returnurl"] = returnurl;
            return view();
        }
        private iactionresult redirettolocal(string returnurl)
        {
            if (url.islocalurl(returnurl))
            {
                return redirect(returnurl);
            }
            return redirecttoaction(nameof(homecontroller.index),"home");
        }
        [httppost]
        public async task<iactionresult> login(loginvm vm,string returnurl = null)
        {
            if (modelstate.isvalid)
            {
                viewdata["returnurl"] = returnurl;
                var user =  _users.findbyusername(vm.username);
                if (user==null)
                {
                    modelstate.addmodelerror(nameof(loginvm.username), "username is exists");
                }
                else
                {
                    if(_users.validatecredentials(vm.username, vm.password))
                    {
                        var props = new authenticationproperties
                        {
                            ispersistent = true,
                            expiresutc = datetimeoffset.utcnow.add(timespan.fromminutes(30))
                        };
                        await microsoft.aspnetcore.http
                            .authenticationmanagerextensions
                                .signinasync( httpcontext, user.subjectid, user.username, props );

                        return redirettolocal(returnurl);
                    }

                    modelstate.addmodelerror(nameof(loginvm.password), "wrong password");
                }
            }
            return view();
        }
    }

这个时候最基本的服务端已经配置成功了,我们开始配置受保护的客户端吧。

在客户端中我们不需要引入identityserver,因为我们只是去请求服务端然后看看cookies有没有在而已,所以我们只需要给受保护的客户端的api做好安全判断就好.

在受保护的控制器中添加 [authorize] 标识。然后再startup.cs中添加安全验证。并且在configure中use下 app.useauthentication(); 

public void configureservices(iservicecollection services)
        {
            services.addauthentication(options =>
            {
                options.defaultscheme = "cookies";
                options.defaultchallengescheme = "oidc";
            }).addcookie("cookies").addopenidconnect("oidc", options => {
                options.signinscheme = "cookies";
                options.authority = "http://localhost:5000";
                options.requirehttpsmetadata = false;
                options.clientid = "mvc";
                options.clientsecret = "secret";
                options.savetokens = true;
            });

            services.addmvc().setcompatibilityversion(compatibilityversion.version_2_1);
        }

在首页中最好遍历下claims对象,这个是通过oidc直接给我们返回回来的.(最后另一个客户端也这么干!)

<div>
    @foreach (var claim in user.claims)
    {
        <dl>
            <dt>@claim.type</dt>
            <dd>@claim.value</dd>
        </dl>
    }
</div>

现在我们启动项目看一下效果吧。

 .NET Core IdentityServer4实战 第Ⅴ章-单点登录