【C#】【WinForm】【DevExpress】XAF自定义登录参数和身份验证

  在使用XAF框架是,默认身份验证是使用用户名和密码的。Win和Web页面如下所示。

  现在要(公司、员工、密码)登录来替代(用户名、密码)登录。效果如下:

  原理

  是如何实现自定义传参验证的呢?最关键的步骤在于登录使用自定义的认证标准(能够实现登录)而不是默认的Authentication Standard。

  我们可以先了解在生成项目时,有无登录页面的区别。分别打开生成简单的无自动登录和有自动登录的默认Win应用配置(Web应用配置类似)文件WinApplication.cs。

 

  因此,默认登录需要两个XAF控件Security Strategy Complex 和 Authentication Standard,字面意思是复杂安全策略和认证标准。

  Security Strategy Complex,复杂安全策略。对其功能简单的理解就是定义登录的角色类型、用户类型等信息。查看Security Strategy Complex的属性。实现登录控件是角色类型(RoleType)和用户类型(UserType)来实现自定义的用户登录。

   Authentication Standard, 认证标准。简单的理解,就是根据复杂安全策略提供的参数,有了这个控件,就能实现登录。它的方法本身定义了登录页面需要登录的参数。要想实现登录页面,就需要自己写一个控件,然后替换Authentication Standard控件

   注:记得将自定义的控件替换为Authentication Standard(开始时依据官方步骤,不理解原理,遗漏此步骤,因此百思不得其解)。

  如何自定义控件呢?Authentication Standard控件对应的类为AuthenticationStandard,我们可以自定义类,官方的方法是继承AuthenticationBase、或者IAuthenticationStandard接口。下图是登录类的继承关系图,蓝色部分为登录控件,可以查看官方文件(https://documentation.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.Security.AuthenticationStandard.class)根据需求,实现和集成。接下来的方法会按照官方的方法来做。

步骤

要实现自定义登录(公司、员工、密码)登录。依据以下步骤:

实现员工公司类,并生成演示数据

  • 创建参数类
  • 创建自定义登录认证类
  • 让登录窗口下拉能显示数据
    • 为登录窗口提供对象空间
    • 让登录窗口能够访问外部数据
  • 将自定义类传递给安全系统

实现员工公司类,并生成演示数据

  为了实现登录,员工(Employee)需要继承PermissionPolicyUser类。公司(Company)与员工需要创建关联(association)。

  员工(Employee)具体代码如下:

 1 using DevExpress.Xpo;
 2 using DevExpress.ExpressApp.DC;
 3 using DevExpress.Persistent.Base;
 4 using DevExpress.Persistent.BaseImpl.PermissionPolicy;
 5 //……
 6     [DefaultClassOptions]
 7     [XafDefaultProperty("UserName"),XafDisplayName("员工")]
 8     public class Employee : PermissionPolicyUser
 9     {
10         public Employee(Session session) : base(session) { }
11         private Company company;
12         [Association("Company-Employees")]
13         [XafDisplayName("公司")]
14         public Company Company
15         {
16             get { return company; }
17             set { SetPropertyValue("Company", ref company, value); }
18         }
19 }
20 //……

  公司(Company)类具体代码:

using DevExpress.Xpo;
using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl;
//……
    [DefaultClassOptions]
    [XafDisplayName("公司"),XafDefaultProperty("Name")]
    public class Company : BaseObject
    {
        public Company(Session session) : base(session) { }
        private string name;
        
        [XafDisplayName("名称")]
        public string Name
        {
            get { return name; }
            set { SetPropertyValue("Name", ref name, value); }
        }
        [Association("Company-Employees")]
        public XPCollection<Employee> Employees
        {
            get { return GetCollection<Employee>("Employees"); }
        }
    }
//……

  接下来调用项目的模型编辑器,展开Views | CustomLogonParametersExample.Module.BusinessObjects节点,并找到员工(Employee)和公司(Company)的列表视图(ListView视图),将属性AllowNew设置为false。为了防止在这两个视图中创建新对象。

   为了方便验证,打开 BatabaseUpdate→Update.cs文件,添加验证数据。

 1 using DevExpress.ExpressApp.Security;
 2 using DevExpress.ExpressApp.Security.Strategy;
 3 // ... 
 4 public class Updater : ModuleUpdater {
 5     public Updater(IObjectSpace objectSpace, Version currentDBVersion) : base(objectSpace, currentDBVersion) { }
 6     public override void UpdateDatabaseAfterUpdateSchema() {
 7         base.UpdateDatabaseAfterUpdateSchema();
 8         PermissionPolicyRole administrativeRole = ObjectSpace.FindObject<PermissionPolicyRole>(
 9             new BinaryOperator("Name", SecurityStrategy.AdministratorRoleName));
10         if (administrativeRole == null) {
11             administrativeRole = ObjectSpace.CreateObject<PermissionPolicyRole>();
12             administrativeRole.Name = SecurityStrategy.AdministratorRoleName;
13             administrativeRole.IsAdministrative = true;
14         }
15         const string adminName = "Administrator";
16         Employee administratorUser = ObjectSpace.FindObject<Employee>(
17             new BinaryOperator("UserName", adminName));
18         if (administratorUser == null) {
19             administratorUser = ObjectSpace.CreateObject<Employee>();
20             administratorUser.UserName = adminName;
21             administratorUser.IsActive = true;
22             administratorUser.SetPassword("");
23             administratorUser.Roles.Add(administrativeRole);
24         }
25         PermissionPolicyRole userRole = ObjectSpace.FindObject<PermissionPolicyRole>(
26             new BinaryOperator("Name", "User"));
27         if (userRole == null) {
28             userRole = ObjectSpace.CreateObject<PermissionPolicyRole>();
29             userRole.Name = "User";
30             userRole.AddTypePermission<Employee>(
31                 SecurityOperations.ReadOnlyAccess, SecurityPermissionState.Allow);
32             userRole.AddTypePermission<Company>(
33                 SecurityOperations.ReadOnlyAccess, SecurityPermissionState.Allow);
34         }
35         if (ObjectSpace.FindObject<Company>(null) == null) {
36             Company company1 = ObjectSpace.CreateObject<Company>();
37             company1.Name = "Company 1";
38             company1.Employees.Add(administratorUser);
39             Employee user1 = ObjectSpace.CreateObject<Employee>();
40             user1.UserName = "Sam";
41             user1.SetPassword("");
42             user1.Roles.Add(userRole);
43             Employee user2 = ObjectSpace.CreateObject<Employee>();
44             user2.UserName = "John";
45             user2.SetPassword("");
46             user2.Roles.Add(userRole);
47             Company company2 = ObjectSpace.CreateObject<Company>();
48             company2.Name = "Company 2";
49             company2.Employees.Add(user1);
50             company2.Employees.Add(user2);
51         }
52         ObjectSpace.CommitChanges();
53     }
54 }

 创建参数类

  在项目*.Modeule下添加参数类CustomLogonParameters。代码如下。

 1 using System;
 2 using System.ComponentModel;
 3 using System.Runtime.Serialization;
 4 using DevExpress.ExpressApp.DC;
 5 using DevExpress.Persistent.Base;
 6 using UserTest.Module.BusinessObjects;
 7 
 8 //……
 9    [DomainComponent, Serializable]
10     [System.ComponentModel.DisplayName("登录")]                       //登录标题
11     public class CustomLogonParameters : INotifyPropertyChanged, ISerializable
12     {
13         #region 构造方法
14         public CustomLogonParameters() { }
15 
16         //ISerializable,序列化
17         public CustomLogonParameters(SerializationInfo info, StreamingContext context)
18         {
19             if (info.MemberCount > 0)
20             {
21                 UserName = info.GetString("UserName");
22                 Password = info.GetString("Password");
23             }
24         }
25         #endregion
26         
27         private Company company;                //公司
28         private Employee employee;              //员工
29         private string password;                //密码
30 
31         [ImmediatePostData]   //值应该尽快传递给绑定对象,但用户更改值时,允许强制更新
32         [XafDisplayName("公司")]
33         public Company Company
34         {
35             get { return company; }
36             set
37             {
38                 if (value == company) return;
39                 company = value;
40                 Employee = null;
41                 OnPropertyChanged("Company");
42             }
43         }
44         [DataSourceProperty("Company.Employees")]
45         [ImmediatePostData] //值应该尽快传递给绑定对象,但用户更改值时,允许强制更新
46         [XafDisplayName("员工")]
47         public Employee Employee
48         {
49             get { return employee; }
50             set
51             {
52                 if (Company == null) return;
53                 employee = value;
54                 if (employee != null)
55                 {
56                     UserName = employee.UserName;
57                 }
58                 OnPropertyChanged("Employee");
59             }
60         }
61         [Browsable(false)]                  //用户名在登录页面不显示    
62         [XafDisplayName("用户名")]                 
63         public String UserName { get; set; }
64         [PasswordPropertyText(true)]  
65         [XafDisplayName("密码")]
66         public string Password
67         {
68             get { return password; }
69             set
70             {
71                 if (password == value){ return; }
72                 password = value;
73             }
74         }
75 
76         public event PropertyChangedEventHandler PropertyChanged;
77         //属性更改时引发事件页面上能快速更改事件)
78         private void OnPropertyChanged(string propertyName)
79         {
80             if (PropertyChanged != null)
81             {
82                 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
83             }
84         }       
85 
86         [System.Security.SecurityCritical]
87         public void GetObjectData(SerializationInfo info, StreamingContext context)
88         {
89             info.AddValue("UserName", UserName);
90             info.AddValue("Password", Password);
91         }
92 }
93 //……

  注:在登录验证的时候,不能使用引用属性验证。因此,在方法中,根据公司、员工获取用户名(UserName),登录验证的时候使用用户名+密码验证。这就是为什么参数中需要加一个因此用户名的原因。

创建自定义登录认证类

  创建类CustomAuthentication。为了实现自定义身份认证,需要继承AuthenticationBase。具体代码如下:

 1 using System;
 2 using DevExpress.ExpressApp;
 3 using DevExpress.Data.Filtering;
 4 using System.Collections.Generic;
 5 using DevExpress.ExpressApp.Security;
 6 using UserTest.Module.BusinessObjects;
 7 //……
 8 
 9 /// <summary>
10     /// 自定义登录参数类
11     /// </summary>
12     public class CustomAuthentication : AuthenticationBase, IAuthenticationStandard
13     {
14         private CustomLogonParameters customLogonParameters;            //登录参数
15         public CustomAuthentication()
16         {
17             customLogonParameters = new CustomLogonParameters();
18         }
19         /// <summary>
20         /// 注销
21         /// </summary>
22         public override void Logoff()
23         {
24             base.Logoff();
25             customLogonParameters = new CustomLogonParameters();
26         }
27         /// <summary>
28         /// 重置登录参数
29         /// </summary>
30         public override void ClearSecuredLogonParameters()
31         {
32             customLogonParameters.Password = "";
33             base.ClearSecuredLogonParameters();
34         }
35         /// <summary>
36         /// 通过比较登录参数与数据库中用户对象的值,进行验证。
37         /// 
38         /// </summary>
39         /// <param name="objectSpace">用户数据操作对象空间</param>
40         /// <returns>经身份验证的用户</returns>
41         public override object Authenticate(IObjectSpace objectSpace)
42         {
43 
44             Employee employee = objectSpace.FindObject<Employee>(new BinaryOperator("UserName", customLogonParameters.UserName));
45             if (employee == null)
46             {
47                 throw new ArgumentNullException("Employee");
48             }
49             if (!employee.ComparePassword(customLogonParameters.Password))
50             {
51                 throw new AuthenticationException(employee.UserName, "密码错误.");
52             }
53             return employee;
54         }
55 
56         /// <summary>
57         /// 初始化登录参数
58         /// </summary>
59         /// <param name="logonParameters">登录参数</param>
60         public override void SetLogonParameters(object logonParameters)
61         {
62             this.customLogonParameters = (CustomLogonParameters)logonParameters;
63         }
64 
65         /// <summary>
66         /// 返回到要添加程序的应用列表
67         /// </summary>
68         /// <returns></returns>
69         public override IList<Type> GetBusinessClasses()
70         {
71             return new Type[] { typeof(CustomLogonParameters) };
72         }
73         /// <summary>
74         /// 指示登录过程是否是交互式的(通过登录对话框请求登录参数)
75         /// </summary>
76         public override bool AskLogonParametersViaUI
77         {
78             get { return true; }
79         }
80 
81         /// <summary>
82         /// 返回登录参数
83         /// </summary>
84         public override object LogonParameters
85         {
86             get { return customLogonParameters; }
87         }
88         /// <summary>
89         /// 是否启用注销方法,继承AuthenticationBase
90         /// </summary>
91         public override bool IsLogoffEnabled
92         {
93             get { return true; }
94         }
95 }
96 
97 //……

   注:记得初始化方法LogonParameters。

让登录窗口下拉能显示数据

  完成参数类和自定义登录认证类后,直接将自定义类传递给安全系统(后一步会介绍)。会发现下拉框无法获取到数据。

  要使得下拉框能够获取到这些外部数据。我们需要为登录窗口提供对象空间和让登录窗口访问外部数据。

  以Win版本为例,提供对象空间后,下拉时能够获取到数据,数据处于数据保护。

   让窗口访问外部数据(公司类型)以后,下拉时能够读取数据。

为登录窗口提供对象空间

Win版

  打开Program.cs ,修改代码如下:

 1 using CustomLogonParametersExample.Module;
 2 using CustomLogonParametersExample.Module.BusinessObjects;
 3 using DevExpress.ExpressApp;
 4 // ... 
 5 static class Program {
 6     // ... 
 7     static void Main() {
 8         // ... 
 9         winApplication.CreateCustomLogonWindowObjectSpace += 
10         application_CreateCustomLogonWindowObjectSpace;
11         winApplication.Setup();
12         // ... 
13     }
14     private static void application_CreateCustomLogonWindowObjectSpace(object sender, 
15     CreateCustomLogonWindowObjectSpaceEventArgs e) {
16         e.ObjectSpace = ((XafApplication)sender).CreateObjectSpace(typeof(CustomLogonParameters));
17         if (e.ObjectSpace is NonPersistentObjectSpace) {
18             IObjectSpace objectSpaceCompany = ((XafApplication)sender).CreateObjectSpace(typeof(Company));
19             ((NonPersistentObjectSpace)e.ObjectSpace).AdditionalObjectSpaces.Add(objectSpaceCompany);
20         }
21     }
22 }

   打开WinApplication.cs,修改代码如下:

 1 using DevExpress.ExpressApp;
 2 using DevExpress.ExpressApp.Win;
 3 using DevExpress.ExpressApp.Xpo;
 4 // ... 
 5 public partial class CustomLogonParametersExampleWindowsFormsApplication : WinApplication {
 6     // ... 
 7     protected override void CreateDefaultObjectSpaceProvider(CreateCustomObjectSpaceProviderEventArgs args) {
 8         args.ObjectSpaceProviders.Add(new XPObjectSpaceProvider(args.ConnectionString, args.Connection, false));
 9         args.ObjectSpaceProviders.Add(new NonPersistentObjectSpaceProvider(TypesInfo, null));
10     }
11 }

 Web版

  Web代码如下:

 1 using CustomLogonParametersExample.Module;
 2 using CustomLogonParametersExample.Module.BusinessObjects;
 3 using DevExpress.ExpressApp;
 4 using DevExpress.ExpressApp.Web;
 5 // ... 
 6 public class Global : System.Web.HttpApplication {
 7     // ... 
 8     protected void Session_Start(Object sender, EventArgs e) {
 9         // ... 
10         WebApplication.Instance.CreateCustomLogonWindowObjectSpace += 
11         application_CreateCustomLogonWindowObjectSpace;
12         WebApplication.Instance.Setup();
13         // ... 
14     }
15     private static void application_CreateCustomLogonWindowObjectSpace(object sender, 
16     CreateCustomLogonWindowObjectSpaceEventArgs e) {
17         e.ObjectSpace = ((XafApplication)sender).CreateObjectSpace(typeof(CustomLogonParameters));
18         if (e.ObjectSpace is NonPersistentObjectSpace) {
19             IObjectSpace objectSpaceCompany = ((XafApplication)sender).CreateObjectSpace(typeof(Company));
20             ((NonPersistentObjectSpace)e.ObjectSpace).AdditionalObjectSpaces.Add(objectSpaceCompany);
21         }
22     }
23 }
 1 using System;
 2 using System.Collections.Generic;
 3 using CustomLogonParametersExample.Module;
 4 using DevExpress.ExpressApp;
 5 using DevExpress.ExpressApp.DC;
 6 using DevExpress.ExpressApp.Xpo;
 7 using DevExpress.ExpressApp.Web;
 8 // ... 
 9 public partial class CustomLogonParametersExampleAspNetApplication : WebApplication {
10     private static NonPersistentTypeInfoSource nonPersistentTypeInfoSource;
11     // ... 
12     protected override void CreateDefaultObjectSpaceProvider(CreateCustomObjectSpaceProviderEventArgs args) {
13         args.ObjectSpaceProvider = 
14         new XPObjectSpaceProvider(GetDataStoreProvider(args.ConnectionString, args.Connection), true);
15         if (nonPersistentTypeInfoSource == null) {
16             nonPersistentTypeInfoSource = new NonPersistentTypeInfoSource(TypesInfo, new List<Type>() 
17                { typeof(CustomLogonParameters) });
18         }
19         args.ObjectSpaceProviders.Add(new NonPersistentObjectSpaceProvider(TypesInfo, 
20         nonPersistentTypeInfoSource));
21     }
22 }

 让登录窗口能够访问外部数据

  为了能够让下拉框读取公司、员工数据。

  Win版本,打开WinApplication.cs,代码如下。

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Win;
using DevExpress.ExpressApp.Security;
//……
public partial class UserTestWindowsFormsApplication : WinApplication {
//……
public UserTestWindowsFormsApplication() {
//……
//允许在登录窗口中访问公司、员工数据
            ((SecurityStrategy)Security).AnonymousAllowedTypes.Add(typeof(Company)); 
          ((SecurityStrategy)Security).AnonymousAllowedTypes.Add(typeof(Employee));
}
//……
}
//……

  Web版本,打开WebApplication.cs,代码如下。

 1 using DevExpress.ExpressApp;
 2 using DevExpress.ExpressApp.Security;
 3 using DevExpress.ExpressApp.Web;
 4 // ... 
 5 public partial class UserTestAspNetApplication: WebApplication {
 6     // ... 
 7     public UserTestAspNetApplication () {
 8         // ... 
 9 //允许登录串口查看公司、员工数据
10             ((SecurityStrategy)Security).AnonymousAllowedTypes.Add(typeof(Company));
11             ((SecurityStrategy)Security).AnonymousAllowedTypes.Add(typeof(Employee));
12     }
13     // ... 
14 }

 将自定义类传递给安全系统

  调用应用程序设计器。将SecurityStrategyComplex组件从工具箱拖到设计器的安全窗格上。然后,将您在与平台无关的模块中实现的CustomAuthentication组件放在同一个窗格中。

  关注SecurityStrategyComplex组件。在Properties窗口中,设置SecurityStrategy。UserType员工。

 个人源码地址

  百度网盘链接: https://pan.baidu.com/s/1atleFNZeqYqoiNtKfeErIA 提取码: 9fkf

参考网址

  [1]https://documentation.devexpress.com/eXpressAppFramework/112982/Task-Based-Help/Security/How-to-Use-Custom-Logon-Parameters-and-Authentication

 

posted @ 2019-09-02 16:09  陆陆无为而治者  阅读(1175)  评论(1编辑  收藏  举报