zoukankan      html  css  js  c++  java
  • IdentityServer4实现单点登录统一认证

    什么是单点登录同一认证?
    多个应用共用一套登录系统,用户只需要在其中一个应用登录就获得了ids4授权,无需多次登录其它应用。

    下面介绍简单的demo实现:
    1.数据库
    新建database为blogcore,然后在下面新建Admin表,表结构如下图所示

    2.搭建IdentityServer4服务端
    准备工具: VS2017以上、.net core2.1
    新建项目
    名字为IdentityS4

    依赖项-管理NuGet程序包,找到IdentityServer4包,选择版本为2.2.0

    设置该项目地址为:http://localhost:5000
    新建一个cs文件为Config代码如下:

    public class Config
        {
            // scopes define the resources in your system
            public static IEnumerable<IdentityResource> GetIdentityResources()
            {
                return new List<IdentityResource>
                {
                    new IdentityResources.OpenId(),
                    new IdentityResources.Profile(),
                };
            }
    
            // clients want to access resources (aka scopes)
            public static IEnumerable<Client> GetClients()
            {
                return new List<Client>
                {
                    // OpenID Connect隐式流客户端(MVC)
                    new Client
                    {
                        ClientId = "mvc",
                        ClientName = "MVC Client",
                        AllowedGrantTypes = GrantTypes.Implicit,//隐式方式
                        RequireConsent=false,//如果不需要显示否同意授权 页面 这里就设置为false
                        RedirectUris = { "http://localhost:5002/signin-oidc" },//登录成功后返回的客户端地址
                        PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },//注销登录后返回的客户端地址
    
                        AllowedScopes =//下面这两个必须要加吧 不太明白啥意思
                        {
                            IdentityServerConstants.StandardScopes.OpenId,
                            IdentityServerConstants.StandardScopes.Profile
                        }
                    }
                };
            }
        }
    

    在Startup.cs的ConfigureServices方法中注入Ids4服务,如下面部分代码:

    public void ConfigureServices(IServiceCollection services)
            {
                services.AddDbContext<EFContext>(options=>options.UseSqlServer(Configuration.GetConnectionString("conn")));//注入DbContext
                services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    
                services.AddIdentityServer()//Ids4服务
                    .AddDeveloperSigningCredential()
                    .AddInMemoryIdentityResources(Config.GetIdentityResources())
                    .AddInMemoryClients(Config.GetClients());//把配置文件的Client配置资源放到内存
    
                services.AddTransient<IAdminService,AdminService>();//service注入
            }
    

    在Startup.cs的Configure方法中添加ids4服务中间件(注意要放在UseMvc之前就可以):

    app.UseIdentityServer();
    

    至此ids4配置好,接下来是数据库连接,添加DbContext类名字为:EFContext.cs

    public class EFContext : DbContext
        {
            public EFContext(DbContextOptions<EFContext> options) : base(options)
            {
    
            }
    
            #region 实体集
    
            public DbSet<Admin> Admin { get; set; }//注意 这里这个Blog不能写成Blogs否则会报错找不到Blogs 因为我们现在数据库和表是现成的 这里就相当于实体对应的数据库是Blog
    
            #endregion
        }
    

    添加一个Admin.cs的实体类,对应数据库里面的用户表Admin

    public class Admin
        {
            public int Id { get; set; }
            public DateTime CreateDate { get; set; }
            public string UserName { get; set; }
            public string Password { get; set; }
            public string Remark { get; set; }
        }
    

    接下来写service层,.net core为依赖注入,所以要以接口的形式在service层
    新建一个接口:IAdminService.cs 代码如下:

    public interface IAdminService : IBaseService<Admin>
        {
            Task<Admin> GetByStr(string username, string pwd);
        }
    

    新建实现该接口的类AdminService.cs 代码如下:

    public class AdminService: BaseService<Admin>, IAdminService
        {
            public AdminService(EFContext _efContext)
            {
                db = _efContext;
            }
    
            public async Task<Admin> GetByStr(string username, string pwd)
            {
                Admin m=await db.Admin.Where(a => a.UserName == username && a.Password == pwd).SingleOrDefaultAsync();
                if (m!=null)
                {
                    return m;
                }
                else
                {
                    return null;
                }
            }
        }
    

    BaseService代码如下:

    public class BaseService<T> where T : class, new()
        {
            public EFContext db;
    
            /// <summary>
            /// 增
            /// </summary>
            /// <param name="entity"></param>
            /// <returns></returns>
            public async Task<bool> Add(T entity)
            {
                await db.Set<T>().AddAsync(entity);
                return db.SaveChanges() > 0;
            }
            /// <summary>
            /// 删
            /// </summary>
            /// <param name="entity"></param>
            /// <returns></returns>
            public async Task<bool> Del(T entity)
            {
                db.Entry(entity).State = EntityState.Deleted;
                int i = await db.SaveChangesAsync();
                return i > 0;
            }
            /// <summary>
            /// 改
            /// </summary>
            /// <param name="entity"></param>
            /// <returns></returns>
            public async Task<bool> Edit(T entity)
            {
                db.Entry(entity).State = EntityState.Modified;
                int i = await db.SaveChangesAsync();
                return i > 0;
            }
            /// <summary>
            /// 查-根据传进来的lambda表达式查询一条数据
            /// </summary>
            /// <param name="whereLambda"></param>
            /// <returns></returns>
            public async Task<T> Get(System.Linq.Expressions.Expression<Func<T, bool>> whereLambda)
            {
                T t = await db.Set<T>().Where(whereLambda).SingleOrDefaultAsync();
                return t;
            }
    
            /// <summary>
            /// 查询多条数据-根据传进来的lambda和排序的lambda查询
            /// </summary>
            /// <typeparam name="s"></typeparam>
            /// <param name="pageIndex"></param>
            /// <param name="pageSize"></param>
            /// <param name="whereLambda"></param>
            /// <param name="orderbyLambda"></param>
            /// <param name="isAsc"></param>
            /// <returns></returns>
            public async Task<List<T>> GetList<s>(int pageIndex, int pageSize,
                System.Linq.Expressions.Expression<Func<T, bool>> whereLambda,
                System.Linq.Expressions.Expression<Func<T, s>> orderbyLambda, bool isAsc)
            {
                var temp = db.Set<T>().Where(whereLambda);
                List<T> list = null;
                if (isAsc)//升序
                {
                    list = await temp.OrderBy(orderbyLambda).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
                }
                else//降序
                {
                    list = await temp.OrderByDescending(orderbyLambda).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
                }
                return list;
            }
    
            /// <summary>
            /// 获取总条数
            /// </summary>
            /// <param name="whereLambda"></param>
            /// <returns></returns>
            public async Task<int> GetTotalCount(Expression<Func<T, bool>> whereLambda)
            {
                return await db.Set<T>().Where(whereLambda).CountAsync();
            }
        }
    

    IBaseService代码如下:

    public interface IBaseService<T> where T : class, new()
        {
            //增
            Task<bool> Add(T entity);
    
    
            //删
            Task<bool> Del(T entity);
    
            //改
            Task<bool> Edit(T entity);
    
            //查
            Task<T> Get(System.Linq.Expressions.Expression<Func<T, bool>> whereLambda);
    
    
            //查询分页
            Task<List<T>> GetList<s>(int pageIndex, int pageSize, System.Linq.Expressions.Expression<Func<T, bool>> whereLambda, System.Linq.Expressions.Expression<Func<T, s>> orderbyLambda, bool isAsc);
    
            //获取总条数
            Task<int> GetTotalCount(Expression<Func<T, bool>> whereLambda);
        }
    

    在Startup.cs的ConfigureServices方法中注入 service层 上面已经注入过...
    在配置文件appsettings.json中添加数据库连接字符串如下代码:

    {
      "ConnectionStrings": { "conn": "server=.;database=blogcore;uid=sa;pwd=123456" }, 
      "Logging": {
        "LogLevel": {
          "Default": "Warning"
        }
      },
      "AllowedHosts": "*"
    }
    

    接下来做统一认证服务的登录页,新增一个控制器AccountController,代码如下:

    public class AccountController : Controller
        {
            private IAdminService _adminService;//自己写的操作数据库Admin表的service
            private readonly IIdentityServerInteractionService _interaction;
            private readonly IClientStore _clientStore;
            private readonly IAuthenticationSchemeProvider _schemeProvider;
            private readonly IEventService _events;
            public AccountController(IIdentityServerInteractionService interaction,
                IClientStore clientStore,
                IAuthenticationSchemeProvider schemeProvider,
                IEventService events,
                IAdminService adminService)
            {
                _interaction = interaction;
                _clientStore = clientStore;
                _schemeProvider = schemeProvider;
                _events = events;
                _adminService = adminService;
            }
    
            /// <summary>
            /// 登录页面
            /// </summary>
            [HttpGet]
            public async Task<IActionResult> Login(string returnUrl=null)
            {
                ViewData["returnUrl"] = returnUrl;
                return View();
            }
    
            /// <summary>
            /// 登录post回发处理
            /// </summary>
            [HttpPost]
            public async Task<IActionResult> Login(string userName, string password,string returnUrl=null)
            {
                ViewData["returnUrl"] = returnUrl;
                Admin user = await _adminService.GetByStr(userName, password);
                if (user!=null)
                {
                    AuthenticationProperties props= new AuthenticationProperties
                    {
                        IsPersistent = true,
                        ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromDays(1))
                    };
                    await HttpContext.SignInAsync(user.Id.ToString(), user.UserName, props);
                    if (returnUrl!=null)
                    {
                        return Redirect(returnUrl);
                    }
    
                    return View();
                }
                else
                {
                    return View();
                }
            }
        }
    

    Login视图代码如下:

    
    @{
        Layout = null;
    }
    
    <!DOCTYPE html>
    
    <html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>Login</title>
    </head>
    <body>
    
        <div align="center">
            <h1>统一认证登录中心</h1>
            <form action="/Account/Login" method="post">
                用户名:<input type="text" name="userName" /><br />
                密 码:<input type="password" name="password" /><input type="hidden" name="returnUrl" value="@ViewData["returnUrl"]" /> <br />
                <input type="submit" value="登录" />
            </form>
        </div>
    </body>
    </html>
    
    

    至此,IdentityServer4服务端的工作完成,接下来我们要开始建客户端了,也就是需要保护的MVC网站

    3.搭建客户端
    新建一个名为 MvcClient 的ASP.Net Core Web应用程序

    把地址设置为:http://localhost:5002

    在Startup.cs的ConfigureServices方法中添加如下部分代码(主要用来配置认证中心ids4的及自己作为客户端的认证信息):

    public void ConfigureServices(IServiceCollection services)
            {
                JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
    
                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.SaveTokens = true;
                    });
    
    
                services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            }
    

    在 Configure方法中添加这行代码(需要在UseMvc之前就可以):

    app.UseAuthentication();
    

    到此,客户端跟统一认证的信息就配置完了。接下来我们把Home控制器上面加上需要验证的标志:[Authorize]

    我们把默认的Index视图页面html代码去掉,改成如下(主要用来显示下授权后拿到的用户信息):

    @{
        ViewData["Title"] = "Home Page";
    }
    
    <div align="center"><h1>这里是受保护的客户端首页</h1></div>
    <h3>User claims</h3>
    
    <dl>
        @foreach (var claim in User.Claims)
        {
            <dt>@claim.Type</dt>
            <dd>@claim.Value</dd>
    
        }
    </dl>
    

    到此,客户端的工作也做完了,下面我们要开始启动项目了,设置项目为多项目启动:解决方案上右键-属性

    现在我们启动项目:服务器项目和 客户端都运行了,但是客户端会直接跳转到服务端登录页面
    比如我们如果访问受保护的5002端口资源,如果用户没有登录会直接跳到5000端口的统一认证服务登录页


  • 相关阅读:
    Wicket框架使用小结
    【YII是个什么鬼】YII入门——URL Manager配置
    【碎碎念】你要做重要的工作
    【科普】五分钟分清网页钟各种长度单位px、em、rem
    【CSS】最简单的css3实现的水平导航栏的手风琴效果
    PYTHON__关于Socket中的Select使用理解
    PIG__Failed to create DataStorage解决方案
    MYSQL__Mysql免安装版的使用(Windows7)
    PYTHON__生成器和普通函数的区别
    vim status 颜色配置
  • 原文地址:https://www.cnblogs.com/ButterflyEffect/p/12321763.html
Copyright © 2011-2022 走看看