sqlite[转].net中的认证(authentication)与授权(authorization)

本文转自:http://www.cnblogs.com/yjmyzz/archive/2010/08/29/1812038.html

 

横流:这首文章主要被新手看之,老手们或会见觉得没啥营养,就请绕了吧。

 

“认证”与“授权”是几拥有系统被还见面涉嫌的概念,通俗点讲:

 

说明(authentication) 就是
“判断用户产生没有发生记名?”,好比windows系统,没登录就无法运用(不管您是故Administrator或Guest用户,总之要优先对登录后,才能够进来系统).

授权(authorization)
就是”用户登录后底身价/角色识别”,好比”管理员用户”登录windows后,能设置软件、修改windows设置等有着操作,而Guest用户登录后,只有做片的操作(比如设置软件就被取缔了).

 

 .net中同”认证”对应之是IIdentity接口,而与”授权”对应之尽管是IPrincipal接口,这第二单接口的概念均以命名空间System.Security.Principal中: 

+ View
Code?

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
using System.Runtime.InteropServices;
 
namespace System.Security.Principal
{
    [ComVisible(true)]
    public interface IIdentity
    {
           string AuthenticationType { get; }
           bool IsAuthenticated { get; }
           string Name { get; }
    }
}

 

+ View
Code?

1
2
3
4
5
6
7
8
9
10
11
12
using System;
using System.Runtime.InteropServices;
 
namespace System.Security.Principal
{
    [ComVisible(true)]
    public interface IPrincipal
    {
          IIdentity Identity { get; }
          bool IsInRole(string role);
    }
}

该专注到:IPrincipal接口中蕴藏着一个止读的IIdentity,这也跟最开始波及的定义一样:识别身份的前提是先期登录,只有登录成功后能更认可身份。

据此Membership/Role做过asp.net开发之意中人等,看到就第二只接口的概念,应该会看大熟知,想想咱们以Asp.Net页面被凡是怎样判定用户是否登录以及角色的?

+ View
Code?

1
2
3
4
5
6
7
8
9
10
11
12
protected void Page_Load(object sender, EventArgs e)
        {
            HttpContext ctx = HttpContext.Current;
            if (ctx.User.Identity.IsAuthenticated && ctx.User.IsInRole("管理员"))
            {
                //管理员该做的事,就写在这里
            }
            else
            {
                //Hi,您不是管理员,别胡来!
            }
        }

马上段代码再熟悉而了,没错!membership/role的规律就是是基于这第二单接口的,如果又针对HttpContext.Current.User刨根问底,能发现下面的定义:

就算:HttpContext.Current.User本身就是一个IPrincipal接口的实例。有了方的备选知识,可以直奔主题了,先来一个Console控制台程序测试一下用法:

+ View
Code?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using System.Security.Principal;
using System.Threading;
 
namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
 
            GenericIdentity _identity = new GenericIdentity("菩提树下的杨过");
            GenericPrincipal _principal = new GenericPrincipal(_identity, new string[] {"管理员","网站会员" });
 
            Thread.CurrentPrincipal = _principal;//并非必需,但在winform程序中有很用(后面会提到)
 
            string loginName = _principal.Identity.Name;
            bool isLogin = _principal.Identity.IsAuthenticated;
            bool isAdmin = _principal.IsInRole("管理员");
            bool isWebUser = _principal.IsInRole("网站会员");
 
            Console.WriteLine("当前用户: {0}", loginName);
            Console.WriteLine("是否已经登录? {0}", isLogin);
            Console.WriteLine("是否管理员? {0}", isAdmin);
            Console.WriteLine("是否网站会员? {0}", isWebUser);
 
            Console.Read();           
        }
    }
}

输出如下:

即用户: 菩提树下的杨过 是否曾报到? True 是不是管理员? True 是否网站会员? True

一切正常,没什么特别莫了,但Console默认只是一个单线程的次,也绝非长的GUI界面,所以…这个只不过是热身,看下接口定义的几个办法是否中而已。

立刻第二单接口同样也能用当Winform先后中,下面将创一个WinForm应用,里面有第二单窗口:Form1以及Form2,可以将Form1当成登录界面,而Form2则是次主窗口,在群管理软件中,主窗口都要求登录后才能够看,我们虽来拟一下:

Form1的界面:

Form2更简约:(就一个仅仅读的TextBox)

自我想做的作业:在Form1上登录后,看看当Form2中,能否判断出用户已报到,以及识别出位置。

Form1 中的代码:

+ View
Code?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Security.Principal;
using System.Threading;
using System.Windows.Forms;
 
namespace WinformTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void btnLogin_Click(object sender, EventArgs e)
        {
            if (txtUserName.Text.Trim() == "") {
                MessageBox.Show("请输入用户名!");
                txtUserName.Focus();
                return;
            }
 
            IIdentity _identity = new GenericIdentity(txtUserName.Text.Trim());
            IPrincipal _principal = new GenericPrincipal(_identity, new string[] { "管理员" });
 
            Thread.CurrentPrincipal = _principal;//将其附加到当前线程的CurrentPrincipal
 
            MessageBox.Show("登录成功!");
        }
 
        private void btnShow_Click(object sender, EventArgs e)
        {
            (new Form2()).ShowDialog();
        }
 
        private void btnLogOut_Click(object sender, EventArgs e)
        {
            Thread.CurrentPrincipal = null;
            MessageBox.Show("已经退出!");
        }
    }
}

Form2中之代码:

+ View
Code?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using System.Security.Principal;
using System.Threading;
using System.Windows.Forms;
 
namespace WinformTest
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }
 
        private void Form2_Load(object sender, EventArgs e)
        {
            IPrincipal _principal = Thread.CurrentPrincipal;
 
            if (_principal.Identity.IsAuthenticated)
            {
                this.textBox1.Text = "您已经登录,当前用户:" + _principal.Identity.Name;
                this.textBox1.Text += Environment.NewLine + "当前角色:" + (_principal.IsInRole("管理员") ? "管理员" : "非管理员");
            }
            else
            {
                this.textBox1.Text = "您还没有登录";
            }
        }
    }
}

测试一下:如果以匪登录的景况下,直接点击”Show窗体2″,结果如下

万一输入用户称,并点击”登录”后,再点击”Show窗体2″,结果如下:

生不错!Form2中一直就可知看清用户是否登录,以及时登录用户之角色。这里发出一个首要之底细:

?

1
Thread.CurrentPrincipal = _principal;//将其附加到当前线程的CurrentPrincipal

于Form1中,将登录后底_principal附加到眼前线程的CurrentPrincipal,我们了解:每个程序不管它是无是多线程,总归是发出一个默认的主线程的。所以要是将主线程的CurrentPrincipal与登录后底_principal关联起来后,其它任何窗体,都得以一直用它来做判定,如果判断通过,则好这么或那样(包括创造多线程进行和谐的拍卖),如果判断不经过,则好拒绝继续操作。

 

Winform的题目化解了,再来考虑一下Webform,当然,你可以直接以由Asp.Net2.0虽支持之membership/role机制,但membership/role默认只支持sqlserver数据库(通过membership
provider for
oracle也得以支撑oracle,但终究起局部数据库不深受支持,比如access、mysql、sqlite、db2等),假如你免思将用户名/密码这看似消息保存于sqlserver中(甚至无思保留在数据库被,比如:xml),这时候就得开动脑筋了。

 

实际…就到底不用membership/role,上面提到的当即第二独接口仍然是得用的,但生一个问题:winform中,IPrincipal接口的实例可以直接囤在内存中(直到程序退出),所以任何窗口就能够继承走访它,以便做越的论断,但是以webform中,页面本身是无论状态的,一旦服务器输出html到客户端浏览器后,客户端的页面就跟服务器再无关系了(你甚至足以离线浏览,前提是免刷新),那么最终之证明信息保存在啊地方呢?

 

答案就是客户端的浏览器Cookie!所以于WebForm中之做法有些有例外:

 

始建一个webApplication,里面新建4单页面:login.aspx,logout.aspx,default.aspx,gotoUrl.aspx,这四独页面的企图如下:

login.aspx : 登录页面

logout.aspx: 用来拍卖用户注销
(非必需,但建议将取消逻辑在这里,以便任何索要注销的地方更使用)

default.aspx: 登录完成后的来得页面

gotoUrl.aspx : 登录完成后,用来赞助做页面跳转的页面(非必需,但建议加上)

 

login.aspx代码:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="LoginTest.Login" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <table>
        <tr>
            <td>用户名:</td>
            <td>
                <asp:TextBox ID="txtUserName" runat="server" style="width:200px"></asp:TextBox></td>
        </tr>
        <tr>
            <td>密  码:</td>
            <td>
                <asp:TextBox ID="txtPassword" runat="server" TextMode="Password" style="width:200px"></asp:TextBox>
            </td>
        </tr>
        <tr>
            <td></td>
            <td>
                <asp:Button ID="Button1" runat="server" Text="登 录" onclick="Button1_Click" />
            </td>
        </tr>
    </table>
    </form>
</body>
</html>

后置代码:

using System;
using System.Web;
using System.Web.Security;

namespace LoginTest
{
    public partial class Login : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            string user = this.txtUserName.Text; //读取用户名
            string password = this.txtPassword.Text; //读取密码
            if (ValidateUser(user, password) == true) //ValidateUser方法用来验证用户合法性的
            {
                //建立表单验证票据
                FormsAuthenticationTicket Ticket = new FormsAuthenticationTicket(1, user, DateTime.Now, DateTime.Now.AddMinutes(30), true, "管理员,会员", "/");

                //使用webcongfi中定义的方式,加密序列化票据为字符串
                string HashTicket = FormsAuthentication.Encrypt(Ticket);

                //将加密后的票据转化成cookie
                HttpCookie UserCookie = new HttpCookie(FormsAuthentication.FormsCookieName, HashTicket);

                //添加到客户端cookie
                Context.Response.Cookies.Add(UserCookie);

                //登录成功后重定向
                Response.Redirect("GotoUrl.aspx?returnUrl=" + Server.UrlEncode("Default.aspx"));
            }
            else
            {
                //登录失败后的处理
            }           
        }

        /// <summary>
        /// 验证用户名/密码是否正确
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="pwd"></param>
        /// <returns></returns>
        private bool ValidateUser(string userName, string pwd) {
            return true; //当然实际开发中,您可以到数据库里查询校验,这里只是示例而已
        }
    }
}

GotoUrl.aspx:这个页面就是只的援手跳反而曾,所以aspx页面本身不用加什么代码,只需要在后置cs代码里大概处理一下

using System;

namespace LoginTest
{
    public partial class GotoUrl : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            string _returnUrl = Request["returnUrl"];

            if (string.IsNullOrEmpty(_returnUrl))
            {
                _returnUrl = "~/default.aspx";
            }


            Response.Redirect(_returnUrl);
        }
    }
}

连着下当是Default.aspx了,这里只是演示,所以没有后置代码,判断的逻辑全写在default.aspx本身:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="LoginTest.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <% if (User.Identity.IsAuthenticated)
           {
               Response.Write("" + User.Identity.Name + "已登录!");
               if (User.IsInRole("管理员"))
               {
                   Response.Write(" 当前用户角色:管理员");
               }

               if (User.IsInRole("会员"))
               {
                   Response.Write(",会员。");
               }

               Response.Write(" <a href='logout.aspx'>安全退出</a>");
           }
           else
           {
               Response.Write("请先<a href='login.aspx'>登录</a>");
           }
        %>
    </div>
    </form>
</body>
</html>

末尾一个是取消页面logout.aspx,类似的,这个页面本身才当收回cookie票据,所以界面及从来不东西,只有后置代码:

using System;
using System.Web.Security;

namespace LoginTest
{
    public partial class Logout : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            FormsAuthentication.SignOut();
            Response.Redirect("default.aspx");
        }
    }
}

要是您已经抵不着急的本下了F5纪念看下最后之结果,可能会见叫人失望:

俺们还尚无登录呢,甚至并用户称,密码都没有输入,咋会显示已报到?是未是回顾了有些沈阳底那么句经典台词:为~什么呢?

即时就是webform与winform不同的地方,asp.net默认的表单认证方法是Windows,所以程序一样运行,asp.net就把windows当前底报到用户就是已经报到了,因此我们得转asp.net的默认“傻帽”行为,修改web.config成下面这样:

<?xml version="1.0"?>

<configuration>
  <system.web>

    <compilation debug="true" targetFramework="4.0" />


    <authentication mode="Forms">
      <forms
         name=".ASPXAUTH"
         loginUrl="login.aspx"
         timeout="30"
         path="/"
         requireSSL="false"
         domain="">
      </forms>
    </authentication>


  </system.web>


</configuration>

哦,忘了喻大家,我之所以之凡asp.net 4.0,所以web.config显示大简清爽。

ok,再来飞一下:

马上回对了,点击“登录”,转至login.aspx,然后在用户名里输入点什么(比如:”菩提树下的杨过”),然后会博得下面的结果:

证已经成功了!但是好象还稍问题:并没有辨别出位!(即login.aspx.cs中代码指定的”管理员,会员”角色)

 

静下心来想想问题发生在哪?

在winform中,我们用

IPrincipal _principal = new GenericPrincipal(_identity, new string[] { "管理员" });
Thread.CurrentPrincipal = _principal;//将其附加到当前线程的CurrentPrincipal

给_principal授权为”管理员”(当然还能够让其重多之角色),然后将那个赋值为线程的CurrentPrincipal,所以就ok了,但是webform中连从未Thread.CurrentPrincipal,而且http本身还要是无论状态的,下一样不好http请求,根本无法记得上次恳求时的状态(就好象每次http请求都是再投胎一样,前世忘记得千篇一律干二清一色),幸好:微软也asp.net搞来一个前后文Context的定义,一个webApplication中,虽然http协议本身是任状态的,但是每个aspx页面被求时,总会有意无意一个HttpContext上下文,可以为此其来搜寻回有前生的记忆,而且文章最开始提到了
HttpContext.Current.User本身就是IPrincipal,这不就是Thread.CurrentPrincipal的更换种么?

顺便又回首一下Asp.Net的页面生命周期,每个AspXsqlite页面在恳求认证时,都见面触发Application_AuthenticateRequest事件,而这个波是概念在Global.ascx中的,所以可以打这个入手:

新建一个Global.ascx,打开后置代码,内容如下:

using System;
using System.Security.Principal;
using System.Web;
using System.Web.Security;

namespace LoginTest
{
    public class Global : System.Web.HttpApplication
    {

        protected void Application_Start(object sender, EventArgs e)
        {

        }

        protected void Session_Start(object sender, EventArgs e)
        {

        }

        protected void Application_BeginRequest(object sender, EventArgs e)
        {

        }

        /// <summary>
        /// 每个aspx页面要求认证时被触发
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Application_AuthenticateRequest(object sender, EventArgs e)
        {
            HttpContext _ctx = HttpContext.Current;
            if (_ctx.User != null)
            {
                if (_ctx.User.Identity.IsAuthenticated == true) //认证成功的用户,才进行授权处理
                {
                    FormsIdentity _Identity = (FormsIdentity)_ctx.User.Identity;
                    string[] Roles = _Identity.Ticket.UserData.Split(','); //将角色字符串,即login.aspx.cs中的“管理员,会员”,变成数组
                    _ctx.User = new GenericPrincipal(_Identity, Roles); //将带有角色的信息,重新生成一个GenericPrincipal赋值给User,相当于winform中的Thread.CurrentPrincipal = _principal
                }
            }

        }

        protected void Application_Error(object sender, EventArgs e)
        {

        }

        protected void Session_End(object sender, EventArgs e)
        {

        }

        protected void Application_End(object sender, EventArgs e)
        {

        }
    }
}

又测试一下,结果算正常了:

 

终极重复帮.Net做点广告:.Net是一个平台,其中的诸多术是全平台通用的(不管是winform还是webform),强烈建议大家尽量为微软由带的规范模型靠拢,这样于多种不同类型的下成时,将很有利,而且兼容性好,容易升级。

常见有人winform中刊登录用一种做法(比如设置一个大局的静态变量,判断用户是否已报到),然后webform中又动不少心力想同一种植做法(比如自己打用户表,搞加密算法,然后据此session做判定),假如下就第二栽采取要结起来,估计要费不少劲(当然,也来计划得挺好,一开始便考虑到后来之扩展的,但是这种毕竟少数,而且相对而言,对程序员的渴求于高),但是如果大家还用文中所提取的标准模型(IIdentity,IPrincipal),要做就第二栽使是死有利之。

作者:菩提下的杨过
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意要保留这个段子声明,且以文章页面明显位置被起原文连接,否则保留追究法律责任的权。

网站地图xml地图