操作系统权限实现

2017-01-17 13:26:54 le31ei 阅读数 19084

本文记录使用django自带的认证系统实现自定义的权限管理系统,包含组权限、用户权限等实现。

0x01. django认证系统

django自带的认证系统能够很好的实现如登录、登出、创建用户、创建超级用户、修改密码等复杂操作,并且实现了用户组、组权限、用户权限等复杂结构,使用自带的认证系统就能帮助我们实现自定义的权限系统达到权限控制的目的。

0x02. 认证系统User对象

User对象顾名思义即为表示用户的对象,里面的属性包括:

username
password
email
first_name
last_name
is_superuser
is_active

创建好对象后,django会自动生成表,表名为auth_user,包含以上字段。具体的api文档如下所示:

class models.User

User 对象具有如下字段:

username
必选。少于等于30个字符。 用户名可以包含字母、数字、_、@、+、.和- 字符。

first_name
可选。 少于等于30个字符。

last_name
可选。少于30个字符。

email
可选。邮箱地址。

password
必选。 密码的哈希及元数据。(Django 不保存原始密码)。原始密码可以无限长而且可以包含任意字符。参见密码相关的文档。

groups
与Group 之间的多对多关系。

user_permissions
与Permission 之间的多对多关系。

is_staff
布尔值。指示用户是否可以访问Admin 站点。

is_active
布尔值。指示用户的账号是否激活。

is_superuser
布尔值。只是这个用户拥有所有的权限而不需要给他们分配明确的权限。

last_login
用户最后一次登录的时间。

date_joined
账户创建的时间。当账号创建时,默认设置为当前的date/time。

一般在注册操作中会用到该方法,实现注册一个用户,用到的函数是User.objects.create_user(),在新建用户的时候需要判断用户是否存在,我的实现方式是,User.objects.get(username=xxx)去获取一个用户User对象,用try except实现,如果用户不存在则抛出User.DoesNotExist异常,在这个异常中进行创建用户的操作。具体代码如下:

# 注册操作
from django.contrib.auth.models import User

try:
  User.objects.get(username=username)
  data = {'code': '-7', 'info': u'用户已存在'}
except User.DoesNotExist:
  user = User.objects.create_user(username, email, password)
  if user is not None:
    user.is_active = False
    user.save()

该过程中密码字段会自动加密存储。无需关注过多细节。

0x03. 登录登出用户

创建好用户后,就是登录及登出了,django认证系统提供了login()logout()函数,能够自动登录登出,并且修改session值,非常方便。验证用户身份使用authenticate函数能自动进行password字段的hash比对。
具体实现代码如下:

from django.contrib.auth import authenticate, login, logout

# 认证操作
ca = Captcha(request)
if ca.validate(captcha_code):
  user = authenticate(username=username, password=password)
  if user is not None:
    if user.is_active:
      # 登录成功
      login(request, user)  # 登录用户
      data = {'code': '1', 'info': u'登录成功', 'url': 'index'}
    else:
      data = {'code': '-5', 'info': u'用户未激活'}
  else:
      data = {'code': '-4', 'info': u'用户名或密码错误'}
else:
  data = {'code': '-6', 'info': u'验证码错误'}

登出操作如下:

from django.contrib.auth import authenticate, login, logout

def logout_system(request):
    """
    退出登录
    :param request:
    :return:
    """
    logout(request)
    return HttpResponseRedirect('/')

0x04. login_required装饰器

通过该装饰器能够使视图函数首先判断用户是否登录,如果未登录会跳到默认在settings.py设置的LOGIN_URL参数对应的url,如:LOGIN_URL = '/'。使用方法如下所示:

from django.contrib.auth.decorators import login_required

@login_required
def user_index(request):
    """
    用户管理首页
    :param request:
    :return:
    """
    if request.method == "GET":
        # 用户视图实现

0x05. 用户组及权限分配

组对象包含的字段只有name,但是外键了几张表,能够与userpermissions,产生多对多的关系,我在自定义权限实现中,采用的是权限写死的方法,添加用户组权限。

创建组的函数采用Group.objects.create(name=xxx),就能实现了。当然也跟创建用户一样,需要先判断是否组名已经存在。

创建好组名后,下一步就需要为每个组分配权限了,从前端提交过来的权限列表,然后后端采用groups.permissions.add(permission)的方式依次将权限添加进组。

添加完组权限后,最后一步是将组名添加进用户属性,区分用户属于哪个组。
具体实现代码如下:

# 创建组
try:
    Group.objects.get(name=role_name)
    data = {'code': -7, 'info': u'组名已存在'}
except Group.DoesNotExist:
    groups = Group.objects.create(name=role_name)
    if log_manage == 'true':
        permission = Permission.objects.get(codename='access_log')
        groups.permissions.add(permission)
    if role_manage == 'true':
        permission = Permission.objects.get(codename='access_role_manage')
        groups.permissions.add(permission)
    if user_manage == 'true':
        permission = Permission.objects.get(codename='access_user_manage')
        groups.permissions.add(permission)
    if get_users is not None:
        for user in get_users:
            # 每个user添加组属性
            db_user = get_object_or_404(User, username=user)
            db_user.groups.add(groups)
            data = {'code': 1, 'info': u'添加成功'}
    return HttpResponse(json.dumps(data))

0x06. 权限模型及权限控制

在上一点中用到的Permission.objects.get(codename='access_user_manage')是通过权限模型创建,需要在models中创建一个权限类,然后在meta中进行定义codename

class AccessControl(models.Model):
    """
    自定义权限控制
    """
    class Meta:
        permissions = (
            ('access_dashboard', u'控制面板'),
            ('access_log', u'日志管理'),
            ('access_role_manage', u'角色管理'),
            ('access_user_manage', u'用户管理'),
        )

运行后,会自动在数据库中创建相应的表,并且插入数据。

在创建好权限之后,下一步就是在各个视图中插入权限控制代码了。permission_required(),参数为当前应用名.codename。这样就能控制用户访问,如果用户非法访问则会清空session退出登录。

@permission_required('webcenter.access_role_manage')
@login_required
def role_index(request):
    """
    角色管理首页
    :param request:
    :return:
    """

同时在前端模板页面中也需要进行权限控制,前端要获取request对象的话,后端返回就需要使用render函数,render(request,xxx,xxx),具体代码就如下:

{% if request.user.is_superuser or 'webcenter.access_user_manage' in request.user.get_group_permissions or 'webcenter.access_role_manage' in request.user.get_group_permissions or 'webcenter.access_log' in request.user.get_group_permissions  %}
<li class="treeview">
  <a href="#">
      <i class="fa fa-fw fa-skyatlas"></i>
      <span>站点管理</span> <i class="fa fa-angle-left pull-right"></i>
  </a>
  <ul class="treeview-menu">
  {% if request.user.is_superuser or 'webcenter.access_log' in request.user.get_group_permissions %}
      <li><a href="#" id="log_view">日志管理</a></li>
  {% endif %}
  {% if request.user.is_superuser or 'webcenter.access_role_manage' in request.user.get_group_permissions %}
      <li><a href="/role/index/">角色管理</a></li>
  {% endif %}
  {% if request.user.is_superuser or 'webcenter.access_user_manage' in request.user.get_group_permissions %}
      <li><a href="/user/index/">用户管理</a></li>
  {% endif %}
</ul>
</li>
{% endif %}
2014-07-11 16:39:19 chexlong 阅读数 258444

关于权限菜单的设计


http://blog.csdn.net/bearyb1982/article/details/2448301


权限设计(初稿)   
  1. 前言:   
  权限管理往往是一个极其复杂的问题,但也可简单表述为这样的逻辑表达式:判断“Who对What(Which)进行How的操作”的逻辑表达式是否为真。针对不同的应用,需要根据项目的实际情况和具体架构,在维护性、灵活性、完整性等N多个方案之间比较权衡,选择符合的方案。   
  2. 目标:   
  直观,因为系统最终会由最终用户来维护,权限分配的直观和容易理解,显得比较重要简单,包括概念数量上的简单和意义上的简单还有功能上的简单。想用一个权限系统解决所有的权限问题是不现实的。设计中将常常变化的“定制”特点比较强的部分判断为业务逻辑,而将常常相同的“通用”特点比较强的部分判断为权限逻辑就是基于这样的思路。   
  3. 现状:   
  对于在企业环境中的访问控制方法,一般有三种:   
  1.自主型访问控制方法:目前在我国的大多数的信息系统中的访问控制模块中基本是借助于自主型访问控制方法中的访问控制列表(ACLs)。   
  2.强制型访问控制方法:用于多层次安全级别的军事应用。   
  3.基于角色的访问控制方法(RBAC):是目前公认的解决大型企业的统一资源访问控制的有效方法。其显著的两大特征是:1.减小授权管理的复杂性,降低管理开销。2.灵活地支持企业的安全策略,并对企业的变化有很大的伸缩性。   
  4. 名词:   
  粗粒度:表示类别级,即仅考虑对象的类别(the   type   of   object),不考虑对象的某个特定实例。比如,用户管理中,创建、删除,对所有的用户都一视同仁,并不区分操作的具体对象实例。   
  细粒度:表示实例级,即需要考虑具体对象的实例(the   instance   of   object),当然,细粒度是在考虑粗粒度的对象类别之后才再考虑特定实例。比如,合同管理中,列表、删除,需要区分该合同实例是否为当前用户所创建。   
  5. 原则:   
  权限逻辑配合业务逻辑。即权限系统以为业务逻辑提供服务为目标。相当多细粒度的权限问题因其极其独特而不具通用意义,它们也能被理解为是“业务逻辑”的一部分。比如,要求:“合同资源只能被它的创建者删除,与创建者同组的用户可以修改,所有的用户能够浏览”。这既可以认为是一个细粒度的权限问题,也可以认为是一个业务逻辑问题。在这里它是业务逻辑问题,在整个权限系统的架构设计之中不予过多考虑。当然,权限系统的架构也必须要能支持这样的控制判断。或者说,系统提供足够多但不是完全的控制能力。即,设计原则归结为:“系统只提供粗粒度的权限,细粒度的权限被认为是业务逻辑的职责”。   
  权限公式:Who+What(Which)+How   的问题,在这里我们实现What和部分Which的权限问题(粗粒度和细粒度相合,到一定的程度),其他的权限问题留给业务逻辑解决   
  6. 概念:   
  Who:权限的拥用者或主体(Principal(负责人)、User、Group、Role、Actor等等)   
  What:权限针对的对象或资源(Resource、Class)。   
  How:具体的权限(Privilege,   正向授权与负向授权)。   
  Role:是角色,拥有一定数量的权限。   
  Operator:操作。表明对What的How   操作。   
  7. 解释:   
  User:与   Role   相关,用户仅仅是纯粹的用户,权限是被分离出去了的。User是不能与   Privilege   直接相关的,User   要拥有对某种资源的权限,必须通过Role去关联。解决   Who   的问题。   
  Resource:就是系统的资源,比如部门新闻,文档等各种可以被提供给用户访问的对象。   
  Privilege:是Resource   Related的权限。就是指,这个权限是绑定在特定的资源实例上的。比如说部门新闻的发布权限,叫做"部门新闻发布权限"。这就表明,该Privilege是一个发布权限,而且是针对部门新闻这种资源的一种发布权限。Privilege是由Creator在做开发时就确定的。Privilege   如"删除"   是一个抽象的名词,当它不与任何具体的   Object   或   Resource   绑定在一起时是没有任何意义的。拿新闻发布来说,发布是一种权限,但是只说发布它是毫无意义的。因为不知道发布可以操作的对象是什么。只有当发布与新闻结合在一起时,才会产生真正的   Privilege。这就是   Privilege   Instance。   
  Role:是粗粒度和细粒度(业务逻辑)的接口,一个基于粗粒度控制的权限框架软件,对外的接口应该是Role,具体业务实现可以直接继承或拓展丰富Role的内容,Role不是如同User或Group的具体实体,它是接口概念,抽象的通称。   
  Operator的定义包括了Resource   Type和Method概念。即,What和How的概念。之所以将What和How绑定在一起作为一个Operator概念而不是分开建模再建立关联,这是因为很多的How对于某What才有意义。比如,发布操作对新闻对象才有意义,对用户对象则没有意义。   
  8. 思想:   
  权限系统的核心由以下三部分构成:1.创造权限,2.分配权限,3.使用权限,然后,系统各部分的主要参与者对照如下:1.创造权限   -   Creator创造,2.分配权限   -   Administrator   分配,3.使用权限   -   User:   
  1.   Creator   创造   Privilege,   Creator   在设计和实现系统时会划分,一个子系统或称为模块,应该有哪些权限。这里完成的是   Privilege   与   Resource   的对象声明,并没有真正将   Privilege   与具体Resource   实例联系在一起,形成Operator。   
  2.   Administrator   指定   Privilege   与   Resource   Instance   的关联。在这一步,   权限真正与资源实例联系到了一起,   产生了Operator(Privilege   Instance)。Administrator利用Operator这个基本元素,来创造他理想中的权限模型。如,创建角色,创建用户组,给用户组分配用户,将用户组与角色关联等等...这些操作都是由   Administrator   来完成的。   
  3.   User   使用   Administrator   分配给的权限去使用各个子系统。Administrator   是用户,在他的心目中有一个比较适合他管理和维护的权限模型。于是,程序员只要回答一个问题,就是什么权限可以访问什么资源,也就是前面说的   Operator。程序员提供   Operator   就意味着给系统穿上了盔甲。Administrator   就可以按照他的意愿来建立他所希望的权限框架可以自行增加,删除,管理Resource和Privilege之间关系。可以自行设定用户User和角色Role的对应关系。(如果将   Creator看作是   Basic   的发明者,   Administrator   就是   Basic   的使用者,他可以做一些脚本式的编程)   Operator是这个系统中最关键的部分,它是一个纽带,一个系在Programmer,Administrator,User之间的纽带。

但凡涉及多用户不同权限的网络或者单机程序,都会有权限管理的问题,比较突出的是MIS系统。     
    
  下面我要说的是MIS系统权限管理的数据库设计及实现,当然,这些思路也可以推广开来应用,比如说在BBS中用来管理不同级别的用户权限。     
    
  权限设计通常包括数据库设计、应用程序接口(API)设计、程序实现三个部分。     
    
  这三个部分相互依存,密不可分,要实现完善的权限管理体系,必须考虑到每一个环节可行性与复杂程度甚至执行效率。     
    
  我们将权限分类,首先是针对数据存取的权限,通常有录入、浏览、修改、删除四种,其次是功能,它可以包括例如统计等所有非直接数据存取操作,另外,我们还可能对一些关键数据表某些字段的存取进行限制。除此,我想不出还有另外种类的权限类别。     
    
  完善的权限设计应该具有充分的可扩展性,也就是说,系统增加了新的其它功能不应该对整个权限管理体系带来较大的变化,要达到这个目的,首先是数据库设计合理,其次是应用程序接口规范。     
    
  我们先讨论数据库设计。通常我们使用关系数据库,这里不讨论基于Lotus产品的权限管理。     
    
  权限表及相关内容大体可以用六个表来描述,如下:     
  1   角色(即用户组)表:包括三个字段,ID,角色名,对该角色的描述;     
  2   用户表:包括三个或以上字段,ID,用户名,对该用户的描述,其它(如地址、电话等信息);     
  3   角色-用户对应表:该表记录用户与角色之间的对应关系,一个用户可以隶属于多个角色,一个角色组也可拥有多个用户。包括三个字段,ID,角色ID,用户ID;     
  4   限制内容列表:该表记录所有需要加以权限区分限制的数据表、功能和字段等内容及其描述,包括三个字段,ID,名称,描述;     
  5   权限列表:该表记录所有要加以控制的权限,如录入、修改、删除、执行等,也包括三个字段,ID,名称,描述;     
  6   权限-角色-用户对应表:一般情况下,我们对角色/用户所拥有的权限做如下规定,角色拥有明令允许的权限,其它一律禁止,用户继承所属角色的全部权限,在此范围内的权限除明令禁止外全部允许,范围外权限除明令允许外全部禁止。该表的设计是权限管理的重点,设计的思路也很多,可以说各有千秋,不能生搬硬套说某种方法好。对此,我的看法是就个人情况,找自己觉得合适能解决问题的用。     
    
  先说第一种也是最容易理解的方法,设计五个字段:ID,限制内容ID,权限ID,角色/用户类型(布尔型字段,用来描述一条记录记录的是角色权限还是用户权限),角色/用户ID,权限类型(布尔型字段,用来描述一条记录表示允许还是禁止)     
    
  好了,有这六个表,根据表六,我们就可以知道某个角色/用户到底拥有/禁止某种权限。     
    
  或者说,这么设计已经足够了,我们完全实现了所需要的功能:可以对角色和用户分别进行权限定制,也具有相当的可扩展性,比如说增加了新功能,我们只需要添加一条或者几条记录就可以,同时应用程序接口也无须改动,具有相当的可行性。但是,在程序实现的过程中,我们发现,使用这种方法并不是十分科学,例如浏览某个用户所拥有的权限时,需要对数据库进行多次(甚至是递归)查询,极不方便。于是我们需要想其它的办法。使用过Unix系统的人们都知道,Unix文件系统将对文件的操作权限分为三种:读、写和执行,分别用1、2、4三个代码标识,对用户同时具有读写权限的文件被记录为3,即1+2。我们也可以用类似的办法来解决这个问题。初步的想法是修改权限列表,加入一个字段:标识码,例如,我们可以将录入权限标识为1,浏览权限标识为2,修改权限标识为4,删除权限标识为8,执行权限标识为16,这样,我们通过权限累加的办法就可以轻易的将原本要分为几条记录描述的权限放在一起了,例如,假定某用户ID为1,库存表对应的限制内容ID为2,同时规定角色类型为0、用户类型为1,我们就可以将该用户具有录入、浏览、修改、删除库存表的权限描述为:2,15,1,1。     
    
  确实很简单,不是吗?甚至还有更过激的办法,将限制内容列表也加上一列,定义好标识码,这样,我们甚至可以用简单的一条记录描述某个用户具有的对全部内容所具有的全部权限了。当然,这样做的前提是限制内容数量比较小,不然,呵呵,2的n次方递增起来可是数量惊人,不容易解析的。     
    
  从表面上看,上述方法足以达到实现功能、简化数据库设计及实现的复杂度这个目的,但这样做有个弊端,我们所涉及的权限列表不是相互独立而是互相依赖的,比如说修改权限,其实是包含浏览权限的,例如,我们可能只是简单的设置用户对库存表存取的权限值为录入+修改+删除(1+4+8=13),但事实上,该用户具有(1+2+4+8=15)的权限,也就是说,在这种方案中,13=15。于是当我们调用API询问某用户是否具有浏览权限时,就必须判断该用户是否具有对该数据表的修改权限,因此,如果不能在程序中固化权限之间的包含关系,就不能利用应用程序接口简单的做出判断。但这与我们的目的“充分的可扩展性”矛盾。     
    
  这个问题如何解决?我想到了另外一种设置标识码的方法,那就是利用素数。我们不妨将录入、浏览、修改、删除、执行的基本标志码定为2,3,5,7,11,当遇到权限互相包含的时候,我们将它的标识码设定为两个(或多个)基本标志码的乘积,例如,可以将“修改”功能的标志码定为3*5=15,然后将所有的权限相乘,就得到了我们需要的最终权限标识值。这样,我们在询问用户是否具有某项权限的时候,只需要将最终的值分解成质因子,例如,我们可以定义一个用户具有录入+修改+删除库存表的权限为   2*15*7=2*3*5*7,即表示,该用户具有了对库存表录入+浏览+修改+删除权限。     
    
  当然,对权限列表我们使用上述方法的前提是权限列表记录条数不会太多并且关系不是十分复杂,否则,光是解析权限代码就要机器忽悠半宿:)  

通用权限管理设计篇  


http://sxiaomais.blog.163.com/blog/static/31741203200811102630406/

一.引言

       因为做过的一些系统的权限管理的功能虽然在逐步完善,但总有些不尽人意的地方,总想抽个时间来更好的思考一下权限系统的设计。

       权限系统一直以来是我们应用系统不可缺少的一个部分,若每个应用系统都重新对系统的权限进行设计,以满足不同系统用户的需求,将会浪费我们不少宝贵时间,所以花时间来设计一个相对通用的权限系统是很有意义的。

二.设计目标

       设计一个灵活、通用、方便的权限管理系统。

       在这个系统中,我们需要对系统的所有资源进行权限控制,那么系统中的资源包括哪些呢?我们可以把这些资源简单概括为静态资源(功能操作、数据列)和动态资源(数据),也分别称为对象资源和数据资源,后者是我们在系统设计与实现中的叫法。

系统的目标就是对应用系统的所有对象资源和数据资源进行权限控制,比如应用系统的功能菜单、各个界面的按钮、数据显示的列以及各种行级数据进行权限的操控。

三.相关对象及其关系

       大概理清了一下权限系统的相关概念,如下所示:

1.       权限

系统的所有权限信息。权限具有上下级关系,是一个树状的结构。下面来看一个例子

系统管理

        用户管理

               查看用户

                新增用户

                     修改用户

                     删除用户

       对于上面的每个权限,又存在两种情况,一个是只是可访问,另一种是可授权,例如对于“查看用户”这个权限,如果用户只被授予“可访问”,那么他就不能将他所具有的这个权限分配给其他人。

2.       用户

应用系统的具体操作者,用户可以自己拥有权限信息,可以归属于0~n个角色,可属于0~n个组。他的权限集是自身具有的权限、所属的各角色具有的权限、所属的各组具有的权限的合集。它与权限、角色、组之间的关系都是n对n的关系。

3.       角色

为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念,例如系统管理员、管理员、用户、访客等角色。角色具有上下级关系,可以形成树状视图,父级角色的权限是自身及它的所有子角色的权限的综合。父级角色的用户、父级角色的组同理可推。

4.       组

为了更好地管理用户,对用户进行分组归类,简称为用户分组。组也具有上下级关系,可以形成树状视图。在实际情况中,我们知道,组也可以具有自己的角色信息、权限信息。这让我想到我们的QQ用户群,一个群可以有多个用户,一个用户也可以加入多个群。每个群具有自己的权限信息。例如查看群共享。QQ群也可以具有自己的角色信息,例如普通群、高级群等。

针对上面提出的四种类型的对象,让我们通过图来看看他们之间的关系。

通用权限管理设计篇 - sxiaomais - 小麦

 

    有上图中可以看出,这四者的关系很复杂,而实际的情况比这个图还要复杂,权限、角色、组都具有上下级关系,权限管理是应用系统中比较棘手的问题,要设计一个通用的权限管理系统,工作量也着实不小。

当然对于有些项目,权限问题并不是那么复杂。有的只需要牵涉到权限和用户两种类型的对象,只需要给用户分配权限即可。

在另一些情况中,引入了角色对象,例如基于角色的权限系统, 只需要给角色分配权限,用户都隶属于角色,不需要单独为用户分配角色信息。

通用权限管理设计篇(二)——数据库设计

   国庆前整的通用权限设计的数据库初步设计部分,现在贴上来。

理清了对象关系之后,让我们接着来进行数据库的设计。在数据库建模时,对于N对N的

关系,一般需要加入一个关联表来表示关联的两者的关系。初步估计一下,本系统至少需要十张表,分别为:权限表、用户表、角色表、组表、用户权限关联表、用

户角色关联表、角色权限关联表、组权限关联表、组角色关联表、用户属组关联表。当然还可能引出一些相关的表。下面让我们在PowerDesigner中画出各表吧。

       各表及其关系如下:

通用权限管理设计篇 - sxiaomais - 小麦

      

1.       用户表

用户表(TUser)

字段名称

字段

类型

备注

记录标识

tu_id

bigint

pk, not null

所属组织

to_id

bigint

fk, not null

登录帐号

login_name

varchar(64)

not null

用户密码

password

varchar(64)

not null

用户姓名

vsername

varchar(64)

not null

手机号

mobile

varchar(20)

电子邮箱

email

varchar(64)

创建时间

gen_time

datetime

not null

登录时间

login_time

datetime

上次登录时间

last_login_time

datetime

登录次数

count

bigint

not null

2.       角色表

角色表(TRole)

字段名称

字段

类型

备注

角色ID

tr_id

bigint

pk, not null

父级角色ID

parent_tr_id

bigint

not null

角色名称

role_name

varchar(64)

not null

创建时间

gen_time

datetime

not null

角色描述

description

varchar(200)

3.       权限表

权限表(TRight)

字段名称

字段

类型

备注

权限ID

tr_id

bigint

pk, not null

父权限

parent_tr_id

bigint

not null

权限名称

right_name

varchar(64)

not null

权限描述

description

varchar(200)

4.       组表

组表(TGroup)

字段名称

字段

类型

备注

组ID

tg_id

bigint

pk, not null

组名称

group_name

varchar(64)

not null

父组

parent_tg_id

bigint

not null

创建时间

gen_time

datetime

not null

组描述

description

varchar(200)

5.       角色权限表

角色权限表(TRoleRightRelation)

字段名称

字段

类型

备注

记录标识

trr_id

bigint

pk, not null

角色

Role_id

bigint

fk, not null

权限

right_id

bigint

fk, not null

权限类型

right_type

int

not null(0:可访问,1:可授权)

6.       组权限表

组权限表(TGroupRightRelation)

字段名称

字段

类型

备注

记录标识

tgr_id

bigint

pk, not null

tg_id

bigint

fk, not null

权限

tr_id

bigint

fk, not null

权限类型

right_type

int

not null(0:可访问,1:可授权)

7.       组角色表

组角色表(TGroupRoleRelation)

字段名称

字段

类型

备注

记录标识

tgr_id

bigint

pk, not null

tg_id

bigint

fk, not null

角色

tr_id

bigint

pk, not null

8.       用户权限表

用户权限表(TUserRightRelation)

字段名称

字段

类型

备注

记录标识

tur_id

bigint

pk, not null

用户

tu_id

bigint

fk, not null

权限

tr_id

bigint

fk, not null

权限类型

right_type

int

not null(0:可访问,1:可授权)

9.       用户角色表

用户角色表(TUserRoleRelation)

字段名称

字段

类型

备注

记录标识

tur_id

bigint

pk, not null

用户

tu_id

bigint

fk, not null

角色

tr_id

bigint

fk, not null

10.   用户组表

用户组表(TUserGroupRelation)

字段名称

字段

类型

备注

记录标识

tug_id

bigint

pk, not null

用户

tu_id

bigint

fk, not null

tg_id

bigint

fk, not null

11.   组织表

组织表(TOrganization)

字段名称

字段

类型

备注

组织id

to_id

bigint

pk, not null

父组

parent_to_id

bigint

not null

组织名称

org_name

varchar(64)

not null

创建时间

gen_time

datetime

not null

组织描述

description

varchar(200)

12.   操作日志表

操作日志表(TLog)

字段名称

字段

类型

备注

日志ID

log_id

bigint

pk, not null

操作类型

op_type

int

not null

操作内容

content

varchar(200)

not null

操作人

tu_id

bigint

fk, not null

操作时间

gen_time

datetime

not null

通用权限管理系统设计篇(三)——概要设计说明书

 在前两篇文章中,不少朋友对我的设计提出了异议,认为过于复杂,当然在实际的各种系统的权限管理模块中,并不像这里设计得那么复杂,我以前所做的系统中,

由只有用户和权限的,有只有用户、权限和角色的,还有一个系统用到了用户、权限、角色、组概念,这个系统是我在思考以前所做系统的权限管理部分中找到的一

些共性而想到的一个设计方案,当然还会有不少设计不到位的地方,在设计开发过程中会慢慢改进,这个系统权当学习只用,各位朋友的好的建议我都会考虑到设计

中,感谢各位朋友的支持。

    今天抽时间整了一份概念设计出来,还有一些地方尚未考虑清楚,贴出1.0版,希望各位朋友提出宝贵建议。

    大家也可以点击此处《通用权限管理概要设计说明书》自行下载,这是1.0版本,有些地方可能还会进行部分修改,有兴趣的朋友请关注我的blog。

     

1.      引言

1.1 编写目的

本文档对通用权限管理系统的总体设计、接口设计、界面总体设计、数据结构设计、系统出错处理设计以及系统安全数据进行了说明。

1.2 背景

a、 软件系统的名称:通用权限管理系统;

b、 任务提出者、开发者:谢星星;

c、 在J2EE的web系统中需要使用权限管理的系统。

1.3 术语

本系统:通用权限管理系统;

SSH:英文全称是Secure Shell。

1.4 预期读者与阅读建议

预期读者

阅读重点

开发人员

总体设计、接口设计、数据结构设计、界面总体设计、系统出错处理设计

设计人员

总体设计、接口设计、数据结构设计、系统安全设计

1.5 参考资料

《通用权限管理系统需求规格说明书》

《通用权限管理系统数据库设计说明书》

2.      总体设计

2.1 设计目标

权限系统一直以来是我们应用系统不可缺少的一个部分,若每个应用系统都重新对系统的权限进行设计,以满足不同系统用户的需求,将会浪费我们不少宝贵时间,所以花时间来设计一个相对通用的权限系统是很有意义的。

本系统的设计目标是对应用系统的所有资源进行权限控制,比如应用系统的功能菜单、各个界面的按钮控件等进行权限的操控。

2.2 运行环境

操作系统:Windows系统操作系统和Linux系列操作系统。

2.3 网络结构

 通用权限管理系统可采用Java Swing实现,可以在桌面应用和Web应用系统中进行调用。如果需要要适应所有开发语言,可以将其API发布到WEB Service上。暂时用Java Swing实现。

2.4 总体设计思路和处理流程

在说明总体设计思路前,我们先说明本系统的相关概念:

1. 权限资源

系统的所有权限信息。权限具有上下级关系,是一个树状的结构。下面来看一个例子

系统管理

        用户管理

               查看用户

               新增用户

               修改用户

               删除用户

对于上面的每个权限,又存在两种情况,一个是只是可访问,另一种是可授权,例如对于“查看用户”这个权限,如果用户只被授予“可访问”,那么他就不能将他所具有的这个权限分配给其他人。

2. 用户

应用系统的具体操作者,用户可以自己拥有权限信息,可以归属于0~n个角色,可属于0~n个组。他的权限集是自身具有的权限、所属的各角色具有的权限、所属的各组具有的权限的合集。它与权限、角色、组之间的关系都是n对n的关系。

3. 角色

为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念,例如系统管理员、管理员、用户、访客等角色。角色具有上下级关系,可以形成树状视图,父级角色的权限是自身及它的所有子角色的权限的综合。父级角色的用户、父级角色的组同理可推。

4. 组

了更好地管理用户,对用户进行分组归类,简称为用户分组。组也具有上下级关系,可以形成树状视图。在实际情况中,我们知道,组也可以具有自己的角色信息、

权限信息。这让我想到我们的QQ用户群,一个群可以有多个用户,一个用户也可以加入多个群。每个群具有自己的权限信息。例如查看群共享。QQ群也可以具有

自己的角色信息,例如普通群、高级群等。

针对如上提出的四种对象,我们可以整理得出它们之间的关系图,如下所示:

通用权限管理设计篇 - sxiaomais - 小麦

总体设计思路是将系统分为组权限管理、角色权限管理、用户权限管理、组织管理和操作日志管理五部分。

其中组权限管理包括包含用户、所属角色、组权限资源和组总权限资源四部分,某个组的权限信息可用公式表示:组权限 = 所属角色的权限合集 + 组自身的权限。

角色权限管理包括包含用户、包含组和角色权限三部分,某个角色的权限的计算公式为:角色权限 = 角色自身权限。

用户权限管理包括所属角色、所属组、用户权限、用户总权限资源和组织管理五部分。某个用户总的权限信息存在如下计算公式:用户权限 = 所属角色权限合集 + 所属组权限合集 + 用户自身权限。

组织管理即对用户所属的组织进行管理,组织以树形结构展示,组织管理具有组织的增、删、改、查功能。

操作日志管理用于管理本系统的操作日志。

注意:因为组和角色都具有上下级关系,所以下级的组或角色的权限只能在自己的直属上级的权限中选择,下级的组或者角色的总的权限都不能大于直属上级的总权限。

2.5 模块结构设计

本系统的具有的功能模块结构如下图所示:

通用权限管理设计篇 - sxiaomais - 小麦

2.6 尚未解决的问题

无。

3.      接口设计(暂略)

3.1 用户接口(暂略)

3.2 外部接口(暂略)

3.3 内部接口(暂略)

4.      界面总体设计

本节将阐述用户界面的实现,在此之前对页面元素做如下约定:

序号

页面元素

约定

1

按钮

未选中时:[按钮名称]

选中时:[按钮名称]

2

单选框

○ 选项

3

复选框

□ 选项

4

下拉框

 [选项,…,] ▽

5

文本框

 |________|

6

TextArea

 |…………|

7

页签

未选中时:选项名称

 选中时:选项名称

8

未选中链接

链接文字

9

选中链接

链接文字

10

说明信息

说明信息

 

4.1 组权限管理

4.1.1包含用户

组信息

   组1

       组11

       组12

       组…

   组2

       组21

       组22

       组…

 

所选择组:组1

[包含用户] [所属角色] [组权限] [总权限]

[修改]

用户名   姓名     手机号   最近登录时间 登录次数

阿蜜果 谢星星 13666666666 2007-10-8    66

sterning xxx    13555555555 2007-10-8    10 

……

当用户选择“修改”按钮时,弹出用户列表,操作人可以通过勾选或取消勾选来修改该组所包含的用户。

4.1.2所属角色

组信息

   组1

       组11

       组12

       组…

   组2

       组21

       组22

       组…

 

所选择组:组1

[包含用户] [所属角色] [组权限] [总权限]

[修改]

角色ID   角色名称   角色描述

1          访客       --

   2         初级用户    --

  

当用户选择“修改”按钮时,弹出角色树形结构,操作人可以通过勾选或取消勾选来修改该组所属的角色。

4.1.3组权限

组信息

   组1

       组11

       组12

       组…

   组2

       组21

       组22

       组…

 

所选择组:组1

[包含用户] [所属角色] [组权限] [总权限]

通用权限管理设计篇 - sxiaomais - 小麦

                [保存] [取消]

4.1.4总权限

组信息

   组1

       组11

       组12

       组…

   组2

       组21

       组22

       组…

 

所选择组:组1

[包含用户] [所属角色] [组权限] [总权限]

通用权限管理设计篇 - sxiaomais - 小麦

                [保存] [取消]

通过对已具有的权限取消勾选,或为某权限添加勾选,来修改组的权限信息,点击“保存”按钮保存修改信息。

4.1.5组管理

       在下图中,选中组1的时候,右键点击可弹出组的操作列表,包括添加、删除和修改按钮,从而完成在该组下添加子组,删除该组以及修改该组的功能。

组信息

   组1

       组11

       组12

       组…

   组2

       组21

       组22

       组…

 

所选择组:组1

[包含用户] [所属角色] [组权限] [总权限]

[修改]

用户名   姓名     手机号   最近登录时间 登录次数

阿蜜果 谢星星 13666666666 2007-10-8    66

sterning xxx    13555555555 2007-10-8    10 

……

4.2 角色权限管理

4.2.1包含用户

角色信息

   角色1

       角色11

       角色12

       角色…

   角色2

       角色21

       角色22

       角色…

 

所选择角色:角色1

[包含用户] [包含组] [角色权限]

[修改]

用户名   姓名     手机号   最近登录时间 登录次数

阿蜜果 谢星星 13666666666 2007-10-8    66

sterning xxx    13555555555 2007-10-8    10 

……

当用户选择“修改”按钮时,弹出用户列表,操作人可以通过勾选或取消勾选来修改该角色所包含的用户。

4.2.2包含组

角色信息

   角色1

       角色11

       角色12

       角色…

   角色2

       角色21

       角色22

       角色…

 

所选择角色:角色1

[包含用户] [包含组] [角色权限]

[修改]

组ID   组名称     组描述

1      xxx1       --

2       xxx2        -- 

……

当用户选择“修改”按钮时,弹出用户列表,操作人可以通过勾选或取消勾选来修改该角色所包含的组。

4.2.3角色权限

角色信息

   角色1

       角色11

       角色12

       角色…

   角色2

       角色21

       角色22

       角色…

 

所选择角色:角色1

[包含用户] [包含组] [角色权限]

                 通用权限管理设计篇 - sxiaomais - 小麦

               [保存] [取消]

通过对已具有的权限取消勾选,或为某权限添加勾选,来修改角色的权限信息,点击“保存”按钮保存修改信息。

4.2.4管理角色

       在下图中,选中组1的时候,右键点击可弹出组的操作列表,包括添加、删除和修改按钮,从而完成在该组下添加子组,删除该组以及修改该组的功能。

角色信息

   角色1

       角色11

       角色12

       角色…

   角色2

       角色21

       角色22

       角色…

 

所选择角色:角色1

[包含用户] [包含组] [角色权限]

[修改]

用户名   姓名     手机号   最近登录时间 登录次数

阿蜜果 谢星星 13666666666 2007-10-8    66

sterning xxx    13555555555 2007-10-8    10 

……

4.3 用户权限管理

4.3.1所属角色

用户权限信息

xx公司

   广州分公司

       阿蜜果

       肖xx

       yy…

   北京分公司

       zz1

       zz2

       zz3…

 

所选择用户:阿蜜果

[所属角色] [所属组] [用户权限] [总权限]

[修改]

角色ID   角色名称   角色描述

1          访客       --

   2         初级用户    --

当用户选择“修改”按钮时,弹出角色树形结构,操作人可以通过勾选或取消勾选来修改该用户所属的角色。

4.3.2所属组

用户信息

xx公司

   广州分公司

       阿蜜果

       肖xx

       yy…

   北京分公司

       zz1

       zz2

       zz3…

 

所选择用户:阿蜜果

[所属角色] [所属组] [用户权限] [总权限]

[修改]

组ID   组名称     组描述

1       组1         --

   2       组2         --

当用户选择“修改”按钮时,弹出组的树形结构,操作人可以通过勾选或取消勾选来修改该用户所属的组。

4.3.3用户权限

用户信息

xx公司

   广州分公司

       阿蜜果

       肖xx

       yy…

   北京分公司

       zz1

       zz2

       zz3…

 

所选择用户:阿蜜果

[所属角色] [所属组] [用户权限] [总权限]

                 通用权限管理设计篇 - sxiaomais - 小麦

                [保存] [取消]

通过对已具有的权限取消勾选,或为某权限添加勾选,来修改用户的权限信息,点击“保存”按钮保存修改信息。

4.3.4总权限

用户信息

xx公司

   广州分公司

       阿蜜果

       肖xx

       yy…

   北京分公司

       zz1

       zz2

       zz3…

 

所选择用户:阿蜜果

[所属角色] [所属组] [用户权限] [总权限]

                 通用权限管理设计篇 - sxiaomais - 小麦

                [保存] [取消]

通过对已具有的权限取消勾选,或为某权限添加勾选,来修改用户的权限信息,点击“保存”按钮保存修改信息。

4.3.5用户管理

       当选择了某用户时,点击右键,弹出菜单列表:修改、删除、取消,点击修改和删除按钮可以实现用户的删除和修改功能。

       选择某个组织,例如下表中的“广州分公司”,弹出菜单列表:添加子组织、删除组织、修改组织、添加用户、取消,点击添加用户按钮可以实现用户的添加功能。

用户权限信息

xx公司

   广州分公司

       阿蜜果

       肖xx

       yy…

   北京分公司

       zz1

       zz2

       zz3…

 

所选择用户:阿蜜果

[所属角色] [所属组] [用户权限] [总权限]

[修改]

角色ID   角色名称   角色描述

1          访客       --

   2         初级用户    --

4.3.6组织管理

       选择某个组织,例如下表中的“广州分公司”,弹出菜单列表:添加子组织、删除组织、修改组织、添加用户、取消,点击添加子组织、删除组织、修改组织按钮可以实现组织的添加、删除和修改功能。

用户权限信息

xx公司

   广州分公司

       阿蜜果

       肖xx

       yy…

   北京分公司

       zz1

       zz2

       zz3…

 

所选择用户:阿蜜果

[所属角色] [所属组] [用户权限] [总权限]

[修改]

角色ID   角色名称   角色描述

1          访客       --

   2         初级用户    --

4.4 操作日志管理

4.4.1查询操作日志

操作名称:|________|  操作人:|________|

操作时间从 |________| 到 |________| [查询] [重置] [删除]

编号    操作名称    操作内容    操作人    操作时间

1        xx1         --        Amigo    2007-10-8

2        xx2         --        xxyy     2007-10-8

输入上图表单中的查询信息后,点击“查询”按钮,可查询出符合条件的信息。

4.4.2删除操作日志

操作名称:|________| 操作人:|________|

操作时间从 |________| 到 |________| [查询] [重置] [删除]

编号    操作名称    操作内容    操作人    操作时间

1        xx1       --           Amigo      2007-10-8

2        xx2       --           xxyy       2007-10-8

输入上图表单中的查询信息后,点击“查询”按钮,可查询出符合条件的信息。而后点击“删除”按钮,可删除符合查询条件的操作日志。

5.      数据结构设计

数据库设计的模型请参见《通用权限管理系统_数据库模型.pdm》。表的说明请参见《通用权限管理系统数据库设计说明书》。

5.1 设计原则

5.1.1命名的规范

数据库中表、主键、外键、索引的命名都以统一的规则,采用大小写敏感的形式,各种对象命名长度不要超过30个字符,这样便于应用系统适应不同的数据库平台。

5.1.2数据的一致性和完整性

为了保证数据库的一致性和完整性,往往通过表间关联的方式来尽可能的降低数据的冗余。表间关联是一种强制性措施,建立后,对父表(Parent Table)和子表(Child Table)的插入、更新、删除操作均要占用系统的开销。如果数据冗余低,数据的完整性容易得到保证,但增加了表间连接查询的操作,为了提高系统的响应时间,合理的数据冗余也是必要的。使用规则(Rule)和约束(Check)来防止系统操作人员误输入造成数据的错误是设计人员的另一种常用手段,但是,不必要的规则和约束也会占用系统的不必要开销,需要注意的是,约束对数据的有效性验证要比规则快。所有这些,需要在设计阶段应根据系统操作的类型、频度加以均衡考虑。

5.2 数据库环境说明

数据库:MySql5.0

设计库建模工具:PowerDesigner12.0

5.3 数据库命名规则

表名以T开头,外键以FK开头,索引以INDEX开头。

5.4 逻辑结构

pdm文件的名称为:《通用权限管理系统_数据库模型》。

5.5 物理存储

通过数据库建模工具PowerDesigner12可以将pdm导出为文本文件,将数据库脚本放入文本文件中保存。

5.6 数据备份和恢复

数据库需定期备份(每天备份一次),备份文件格式为backup_yyyyMMdd,数据库被破坏时,利用最新的备份文件进行恢复。

6.      系统出错处理设计

6.1 出错信息

错误分类

子项及其编码

错误名称

错误代码

备注

数据库错误

连接

连接超时

100001001

连接断开

100001002

数据库本身错误代码

数据库本身错误代码

100002+数据库错误代码

TCP连接错误

连接

连接超时

101001001

连接断开

101001002

其它TCP连接错误(socket自身错误代码)

101002+ socket错误代码

配置信息错误

未配置输入参数

102001

未配置输出参数

102002

组管理部分自定义错误

103001——103999

角色管理部分自定义错误

104001——104999

用户管理部分自定义错误

105001——105999

操作日志管理

106001——106999

6.2 补救措施

为了当某些故障发生时,对系统进行及时的补救,提供如下补救措施:

a.后备技术   定期对数据库信息进行备份(每天一次),当数据库因某种原因被破坏时,以最新的数据库脚本进行恢复;。

7.      系统安全设计

7.1 数据传输安全性设计

SSH可以通过将联机的封包加密的技术进行资料的传递; 使用SSH可以把传输的所有数据进行加密,即使有人截获到数据也无法得到有用的信息。同时数据经过压缩,大大地加快了传输的速度。通过SSH的使用,可以确保资料传输比较安全并且传输效率较高。

7.2 应用系统安全性设计

操作人的操作信息需要提供操作记录。对系统的异常信息需进行记录,已备以后查看。只有授权用户才能登录系统,对于某个操作,需要具有相应权限才能进行操作。

7.3 数据存储安全性设计           

对于用户的密码等敏感信息采用MD5进行加密。



OA之权限管理

权限管理自己做完了,但是很多的研究和总结,现在就来总结一下权限管理。

第一、数据库中主要类:

主要负责类:用户(User),角色(Role)、资源(module)和操作(Permission)

衍生类:用户角色(UserRole)和对某个资源的某个操作(ACL)

第二、ACL的具体理解:

一条acl授权记录中主要记录了以下信息: 角色、资源和授权 

授权作为一个int, 每一位是一个操作的权限. 

假设从右向左, 分别代表CRUD 

那么, 我们CRUD的代码就应该是0123(也就是移位时要移的位数), 因为我们要进行移位进行认证。

先看授权与取消授权的代码:

[java] view plaincopy
  1. public void setPermission(int permission,boolean yes){  
  2.         int temp = 1;  
  3.         temp = temp << permission;  
  4.         if(yes){  
  5.             aclState |= temp;  
  6.         }else{  
  7.             aclState &= ~temp;  
  8.         }  
  9.     }  

首先, 一个int temp = 1的临时变量, aclState为原始授权状态

tmp的二进制表示是: 00000000 00000000 00000000 00000001 


U对应的代码是U, 对应的是2. 


将tmp左移2位, temp = tmp < < 2; temp变成:00000000 00000000 00000000 00000100 


假设原始授权是aclState=00000000 00000000 00000000 00001010 


当变量yes=true时,为授权,将temp与aclState求|运算,因为temp现在只有他要授权的位为1,求或运算后,


aclState=00000000 00000000 00000000 00001110,这样就授权成功


当变量yes=false时,为取消授权,先将temp取反,即为11111111 11111111 11111111 11111011,


现在只有要取消权限的位为0,其余全为1,然后与aclState求&运算,则除了要取消权限的位变0,其余的都不变,


即aclState=00000000 00000000 00000000 00001010


再来看认证:

[java] view plaincopy
  1. public int getPermission(int permission){  
  2.                  int temp = 1;  
  3.     temp = temp << permission;  
  4.     temp &= aclState;  
  5.     if(temp != 0){  
  6.         return ACL_YES;  
  7.     }  
  8.     return ACL_NO;  
  9. }  

认证更简单,直接将temp变量与aclState求&,temp为1的位为要验证的权限,其余全为0,如果aclState的这一位为1,则结果不为零,即有该权限;若aclState这一位为0,即没权限,则结果为0,没有该操作权限

总结:权限管理最难理解的就是CRUD中的数据得来,至于别的我们可以关系,我们还是可以清晰的理解,还有一个概念就是集成的概念:

a)        针对某个资源的所有操作,我们可以设置这些权限对用户来说是“继承”或“不继承”

                        i.             继承:意思是这些权限将使用其(即用户)所拥有的角色的权限,而不使用其(即用户)单独设置的权限

                      ii.             不继承:意思是这些权限将使用其单独设置的权限,而不使用其所拥有的角色的权限




2014-12-07 16:12:50 kang_940516 阅读数 5336

CRM系统操作权限的实现

操作权限功能:

     (一)粗粒度权限控制:(即防止用户没有登录手动输入地址)

     (二)细粒度权限控制:(即防止没有权限的操作)

               1.菜单的操作的权限

               2.功能的操作权限

PO实体的设计:

      


实现:

(一)粗粒度权限控制:(即防止用户没有登录手动输入地址)

思路:使用过滤器,

      1.过滤未登陆用户的操作,将其跳转到登陆界面,

      2.放行已登录用户的操作

      3.放行跟登陆相关的操作

<span style="font-size:18px;color:#FF0000;">SessionCheckUserFilter过滤器:</span>

package cn.kang.crm.filter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.kang.crm.domain.SysUser;
import cn.kang.crm.util.SessionUtils;

public class SessionCheckUserFilter implements Filter {

	private List<String> list;
	
	public void init(FilterConfig filterConfig) throws ServletException {
		//TODO 自己处理放置在资源文件,利用流读入
		list=new ArrayList<String>();
		list.add("/image.jsp");
		list.add("/index.jsp");
		list.add("/WEB-INF/page/login.jsp");
		list.add("/sys/sysUserAction_isLogin.do");
	}
	
	public void doFilter(ServletRequest req, ServletResponse res,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request=(HttpServletRequest)req;
		HttpServletResponse response=(HttpServletResponse)res;
		String path=request.getServletPath();
		
		//对 /image.jsp  /index.jsp  /login.jsp   /sys/sysUserAction_isLogin路径过滤器要放行
		if(list!=null&&list.contains(path)){
			chain.doFilter(request, response);
			return;
		}
		
		//获取当前的登陆用户
		SysUser sysUser=SessionUtils.getSysUserFormSession(request);
		
		//如果用户!=null 表示用户已经登陆
		if(sysUser!=null){
			//放行
			chain.doFilter(request, response);
		}else{  //如果用户==null 表示用户没有登陆
			//重定向到login.jsp(index.jsp)
			response.sendRedirect(request.getContextPath());
		}
	}

	public void destroy() {

	}

}
<span style="font-size:18px;color:#FF6600;">获取Session操作的Util帮助类:</span>
<span style="font-size:12px;">SessionUtilspackage cn.kang.crm.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;

import cn.kang.crm.domain.SysUser;

public class SessionUtils {

    /**
     * 处理验证码
     * @param request
     * @return
     */
    public static boolean isCheckNum(HttpServletRequest request) {
        //获取已经存在的session
        HttpSession session=request.getSession(false);
        
        if(session==null){
            return false;
        }
        String check_number_key=(String)session.getAttribute("CHECK_NUMBER_KEY");
        if(StringUtils.isBlank(check_number_key)){
            return false;
        }
        
        //获取jsp页面文本框中输入的值
        //<input name="checkNum" type="text" value="" id="checkNum" style="width: 80">
        String saved=request.getParameter("checkNum");
        if(StringUtils.isBlank(saved)){
            return false;
        }
        
        //比对session中存放的值和页面文本框输入的验证码的值
        return check_number_key.equalsIgnoreCase(saved);
        
    }

    /**
     * 保存当前登录用户的信息到session中
     * @param request
     * @param sysUser
     */
    public static void setSysUserToSession(HttpServletRequest request,SysUser sysUser) {
        HttpSession session=request.getSession();
        if(sysUser==null){
            return;
        }
        session.setAttribute("sysUserKey", sysUser);
    }

    /**
     * 从session中获取当前登陆用户的信息
     * @param request
     * @return
     */
    public static SysUser getSysUserFormSession(HttpServletRequest request) {
          HttpSession session=request.getSession(false);
          if(session==null){
              return null;
          }
          SysUser SysUser=(SysUser)session.getAttribute("sysUserKey");
          return SysUser;
    }

}
</span>

在web.xml中配置拦截器:

<!-- 配置自定义过滤器用于进行粗粒度权限控制,防止用户未登录手动输入 -->
	<filter>
	   <filter-name>SessionCheckUserFilter</filter-name>
	   <filter-class>cn.kang.crm.filter.SessionCheckUserFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>SessionCheckUserFilter</filter-name>
		<url-pattern>*.jsp</url-pattern>
		<dispatcher>REQUEST</dispatcher>
		<dispatcher>FORWARD</dispatcher>
	</filter-mapping>
	
	<filter-mapping>
		<filter-name>SessionCheckUserFilter</filter-name>
		<url-pattern>*.do</url-pattern>
		<dispatcher>REQUEST</dispatcher>
		<dispatcher>FORWARD</dispatcher>
	</filter-mapping>
配置错误跳转页面: 
<!-- 配置全局的转发,用于转向没有权限的提示页面 -->
	    <global-results>
	        <result name="popmsg_popedom">/WEB-INF/page/popmsg_popedom.jsp</result>
	    </global-results>

(二)细粒度权限控制:(即防止没有权限的操作)

               1.菜单的操作的权限

       思路:

                 1.建立监听器监听servletContext,将所有的菜单权限放入application域中

                 2.自定义标签,在页面进行权限判断

SysMenuListener

<pre name="code" class="java"><span style="font-size:12px;">package cn.kang.crm.listener;

import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import cn.kang.crm.container.ServiceProvinder;
import cn.kang.crm.domain.SysMenu;
import cn.kang.crm.service.ISysMenuService;

public class SysMenuListener implements ServletContextListener{
	//获取Application作用域
	private ServletContext application;
	//获取菜单功能的业务层
	private ISysMenuService sysMenuService=
		    (ISysMenuService)ServiceProvinder.getService(ISysMenuService.SERVICE_NAME);
	/***
	 * 服务器启动,创建容器时
	 */
	public void contextInitialized(ServletContextEvent sce) {
		<span style="color:#33CCFF;">application=sce.getServletContext();
		//获取所有的权限信息
		List<SysMenu> sysMenus=sysMenuService.findAllSysPopedoms();
		application.setAttribute("sysMenus",sysMenus);
		System.out.println("正在启动服务器,设置菜单权限值成功");</span>
	}

	/***
	 * 服务器关闭时,移除数据
	 */
	public void contextDestroyed(ServletContextEvent sce) {
		application.removeAttribute("sysMenus");
		System.out.println("正在关闭服务器,移除菜单权限值成功");
	}
}</span>
<!-- 配置自定义过滤器用于进行粗粒度权限控制,防止用户未登录手动输入 -->
    <filter>
       <filter-name>SessionCheckUserFilter</filter-name>
       <filter-class>cn.kang.crm.filter.SessionCheckUserFilter</filter-class>
    </filter>


自定义标签CheckMenuTag

     

package cn.kang.crm.tag;

import java.io.IOException;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.JspTag;
import javax.servlet.jsp.tagext.SimpleTag;

import org.apache.commons.lang.StringUtils;

import cn.kang.crm.container.ServiceProvinder;
import cn.kang.crm.domain.SysMenuPrivilege;
import cn.kang.crm.domain.SysUser;
import cn.kang.crm.service.ISysMenuPrivilegeService;
import cn.kang.crm.util.SessionUtils;

public class CheckMenuTag implements SimpleTag{
	private PageContext pageContext;
	//标签体
	private JspFragment jspFragment;
	////<itcast:checkMemu  module="${sysMenu.id.menuModule}"   privilege="${sysMenu.id.menuPrivilege}">
	//模块名称
	private String module;
	//操作名称
	private String privilege;
	
	public void doTag() throws JspException, IOException {
		//获取request对象
		HttpServletRequest request=(HttpServletRequest) pageContext.getRequest();
		
		//获取当前登陆用户
		SysUser sysUser=SessionUtils.getSysUserFormSession(request);
		if(sysUser==null){
			return;
		}
		if(sysUser.getSysRole()==null){
			return;
		}
		//获取当前登陆用户对应的权限组id
		String roleId=sysUser.getSysRole().getId();
		
		//获取菜单操作权限的业务层sys_menu_privlige
		ISysMenuPrivilegeService sysMenuPrivilegeService = (ISysMenuPrivilegeService) ServiceProvinder
		.getService(ISysMenuPrivilegeService.SERVICE_NAME);
		
		//获取菜单操作权限表中的所有数据sys_menu_privlige返回list集合
		List<SysMenuPrivilege> list=sysMenuPrivilegeService.findAllSysMenuPrivilegesCache();
		<span style="color:#3366FF;">//遍历集合
		if(list!=null&&list.size()>0){
			for(int i=0;i<list.size();i++){
				if(list.get(i)!=null){
					SysMenuPrivilege privilege=list.get(i);
					//判断接受的值是否为空
					if(StringUtils.isNotBlank(roleId)&&StringUtils.isNotBlank(module)&&StringUtils.isNotBlank(this.privilege)){
						//比对权限组id+模块名称+操作名称
					if(roleId.equals(privilege.getId().getRoleId())
							&&module.equals(privilege.getId().getMenuModule())
							&&this.privilege.equals(privilege.getId().getMenuPrivilege())){
						//在集合中如果存在,则输出标签体
						  //System.out.println(this.privilege+"有权限");
		                  this.jspFragment.invoke(null);
		                  break;
					}
					//如果不存在
					}
				}
			}</span>
			
		}
		
		
		
	}
	public JspTag getParent() {
		
		return null;
	}
	public void setJspBody(JspFragment jspBody) {
		this.jspFragment=jspBody;
	}
	public void setJspContext(JspContext pc) {
		this.pageContext=(PageContext)pc;
	}
	public void setParent(JspTag parent) {
		
	}
	public String getModule() {
		return module;
	}
	public void setModule(String module) {
		this.module = module;
	}
	public String getPrivilege() {
		return privilege;
	}
	public void setPrivilege(String privilege) {
		this.privilege = privilege;
	}
	
  
}

写自定义标签的配置:myitcast.tld

<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">
  <description>kang 1.o core library</description>
  <display-name>Kang core</display-name>
  <tlib-version>1.0</tlib-version>
  <short-name>Kang</short-name>
  <uri>http://www.kangge.cn/jsp/jstl/core</uri>
  <tag>
    <name>checkMemu</name>
    <tag-class>cn.kang.crm.tag.CheckMenuTag</tag-class>
    <!-- 标签体不能为空 -->
    <body-content>scriptless</body-content>
    <attribute>
        <name>module</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <name>privilege</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
  </tag>
</taglib>

在页面的判断显示:
<%@ page language="java" pageEncoding="utf-8"
	contentType="text/html; charset=utf-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<span style="font-size:14px;color:#3333FF;"><%@taglib uri="http://www.kangge.cn/jsp/jstl/core" prefix="Kang"%></span>
<html>
<head>
<title>菜单</title>
		<link rel="stylesheet"
			href="${pageContext.request.contextPath}/menu/css/jquery.treeview.css" />
		<link rel="stylesheet"
			href="${pageContext.request.contextPath}/menu/css/screen.css" />
		<script
			src="${pageContext.request.contextPath}/menu/js/jquery-1.4.2.js"
			type="text/javascript">
</script>
		<script
			src="${pageContext.request.contextPath}/menu/js/jquery.treeview.js"
			type="text/javascript">
</script>
		<script type="text/javascript">
$(function() {
	$("#tree").treeview( {
		collapsed : true,
		animated : "medium",
		control : "#sidetreecontrol",
		persist : "location"
	});
})
</script>

	</head>
	<body ondblclick="ToggleNav();">
		<div id="main">
			<div id="sidetree">
				<div class="treeheader">
					操作菜单
				</div>

				<div id="sidetreecontrol">
					<a href="?#">Collapse All</a> |
					<a href="?#">Expand All</a>
				</div>
<span style="color:#3333FF;">				<ul id="tree">
					<c:if test="${! empty applicationScope.sysMenus}">
						<c:forEach items="${applicationScope.sysMenus}" var="sysMenu">
							<c:if test="${sysMenu.id.menuModule==sysMenu.id.menuPrivilege}">
							  <!-- 判斷是否有操作权限 -->
							<span style="font-size:14px;color:#FF0000;">  <Kang:checkMemu privilege="${sysMenu.id.menuPrivilege}" module="${sysMenu.id.menuModule}"></span>
								<li>
									<img
										src="${pageContext.request.contextPath}/ui/images${sysMenu.icon}"
										width="17" height="17">
									<strong>${sysMenu.menuName}</strong>
									<c:forEach items="${applicationScope.sysMenus}"
										var="sysMenusub">
										<c:if
											test="${sysMenusub.id.menuModule!=sysMenusub.id.menuPrivilege&&
									        sysMenusub.id.menuModule==sysMenu.id.menuModule
									     }">
									     <!-- 判斷是否有操作权限 -->
									     <span style="font-size:14px;color:#FF0000;"><Kang:checkMemu privilege="${sysMenusub.id.menuPrivilege}" module="${sysMenusub.id.menuModule}"></span>
											<ul>
												<li>
													<img
														src="${pageContext.request.contextPath}/ui/images${sysMenusub.icon}"
														width="17" height="17">
													<a
														href="${pageContext.request.contextPath}${sysMenusub.url}"
														target="rightFrame">${sysMenusub.menuName}</a>
												</li>
											</ul>
											<span style="font-size:14px;color:#FF0000;"></Kang:checkMemu></span>
										</c:if>
									</c:forEach>
								</li>
								<span style="font-size:14px;color:#FF0000;"></Kang:checkMemu></span>
							</c:if>


						</c:forEach>


					</c:if>


				</ul>

			</div>

		</div></span>
		<div id="divCollapsedNav" class="navTocColor"
			style="display: none; width: 100%; height: 100%;">
			<a href="javascript:ToggleNav();" title="展开导航框架" id="linkNavClosed">
				<img src="${pageContext.request.contextPath}/menu/images/toc2.gif"
					alt="展开导航框架" border="3"></img> </a>
		</div>
		<script src="${pageContext.request.contextPath}/menu/js/left.js">
</script>

	</body>
</html>

<span style="color:#333399;">
</span>

  2.功能的操作权限

         思路: 1.使用自定义注解,为每个方法,写注解
                    2.写拦截器MethodFilterInterceptor,在拦截器中:
                           1)获取action及方法名称,通过反射获取注解的值
                           2)获取session中登录用户的信息
                           3)查询所有的权限组(sys_popedom_privilege)信息,
                           4)将用户的权限组id,模块名称,操作名称与所有权限组数据比对,存在即有权限,则放行.不存在跳转提示


  定义注解:KangGe

/**
 * 自定义注解
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface KangGe{
	 String module();  //模块名称
	 String privilege(); //操作名称
}

<span style="font-size:18px;"><span style="background-color: rgb(0, 102, 0);">//使用:
@KangGe(module = "role", privilege = "list")
@KangGe(module="user",privilege="list")
@KangGe(module="group",privilege="list")</span></span>

定义拦截器KangGeInterceptor,实现struts最强的MethodFilterInterceptor接口奋斗

package cn.kang.crm.interceptor;

import java.lang.reflect.Method;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.ServletActionContext;

import cn.kang.crm.annotation.KangGe;
import cn.kang.crm.container.ServiceProvinder;
import cn.kang.crm.domain.SysPopedomPrivilege;
import cn.kang.crm.domain.SysUser;
import cn.kang.crm.service.ISysPopedomPrivilegeService;
import cn.kang.crm.util.SessionUtils;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;

@SuppressWarnings("serial")
public class KangGeInterceptor  extends MethodFilterInterceptor{

	public String doIntercept(ActionInvocation invocation) throws Exception {
<span style="color:#006600;">		//获取请求的action对象
		Object action=invocation.getAction();
		
		//获取请求的方法的名称
		String methodName=invocation.getProxy().getMethod();
		System.out.println("methodName  "+methodName);
		
		//获取action中的方法的封装类(action中的方法没有参数)
		Method method=action.getClass().getMethod(methodName, null);

		//获取request对象
		HttpServletRequest request=ServletActionContext.getRequest();
		</span>
		//检查注解
		boolean flag=isCheckLimit(request,method);
		
		
		if(!flag){
			System.out.println("没有权限");
			//没有权限
			return "popmsg_popedom";
		}
		System.out.println("有权限");
		
		//有权限,可以调用action中的方法
		String returnvalue=invocation.invoke();
		return returnvalue;
	}

	public boolean isCheckLimit(HttpServletRequest request, Method method) {
		if(method==null){
			return false;
		}
		
		//获取当前的登陆用户
		SysUser sysUser=SessionUtils.getSysUserFormSession(request);
		if(sysUser==null){
			return false;
		}
		
		if(sysUser.getSysRole()==null){
			return false;
		}
		
		//获取当前登陆用户的权限组id
		String roleId=sysUser.getSysRole().getId();
		//处理注解
		/*
		 * 	@Limit(module="group",privilege="list")
	        public String list(){
		 */
		boolean isAnnotationPresent= method.isAnnotationPresent(KangGe.class);
		
		//不存在注解
		if(!isAnnotationPresent){
			return false;
		}
		
		//存在注解
		KangGe kangge=method.getAnnotation(KangGe.class);
		
		//获取注解上的值
		String module=kangge.module();  //模块名称
		String privilege=kangge.privilege(); //操作名称
		
		<span style="color:#993399;">/**
		 * 如果登陆用户的权限组id+注解上的@Limit(module="group",privilege="list")
		 *   * 在sys_popedom_privilege表中存在   flag=true;
		 *   * 在sys_popedom_privilege表中不存在 flag=false;
		 */
		boolean flag=false;
		
		//查询sys_popedom_privilege表中的所有的数据
		ISysPopedomPrivilegeService sysPopedomPrivilegeService=
		    (ISysPopedomPrivilegeService)ServiceProvinder.getService(ISysPopedomPrivilegeService.SERVICE_NAME);
	
		List<SysPopedomPrivilege> list=sysPopedomPrivilegeService.findAllSysPopedomPrivilegesCache();
		if(list!=null&&list.size()>0){
		  for(int i=0;i<list.size();i++){
			  SysPopedomPrivilege s=list.get(i);
			  if(s!=null){
				   if(roleId.equals(s.getId().getRoleId())&&module.equals(s.getId().getPopedomModule())
						   &&privilege.equals(s.getId().getPopedomPrivilege())){
					   flag=true;
					   break;
				   }
			  }
		  }
		}

		return flag;</span>
	}
}


//在struts.xml中配置拦截器:
<span style="color:#CC33CC;">       <!-- 配置拦截器 -->
        <interceptors>
           <!-- 自定义拦截器 -->
           <interceptor name="kangGeInterceptor" class="cn.kang.crm.interceptor.KangGeInterceptor"/>
          <!-- 自定义拦截器栈 -->
           <interceptor-stack name="kangGeStack">
               <!-- 引入默认拦截器栈 -->
              <interceptor-ref name="defaultStack"/>
               <!-- 引入自定义拦截器 -->
               <interceptor-ref name="kangGeInterceptor">
               <!-- 配置那些方法不被拦截 -->
               <param name="excludeMethods">isLogin,top,left</param>
               </interceptor-ref>
           </interceptor-stack>
        </interceptors>
        
        <!--  配置struts运行时,执行的拦截器 (在系统完成后启用)
        <default-interceptor-ref name="kangGeStack"/>
         --></span>


效果图:



自己写的第一篇技术博客,不足之处很多,还请指出。                                      
                                          2014.12.07

2015-11-10 23:36:19 leftfist 阅读数 3449

一、文件系统结构
为了提供对磁盘的高效便捷的访问,操作系统通过文件系统来轻松地存储、定位、提取数据。文件系统有两个不同的设计问题:1、如何定义文件系统对用户的接口,涉及到文件及其属性、文件允许的操作、组织文件的目录结构;2、创建数据结构和算法将逻辑文件系统映射到物理外存设备上。

这里写图片描述

目前有许多文件系统在使用。绝大多数操作系统都支持多个文件系统。

二、文件系统实现

实现文件系统需要应对多种磁盘和内存结构。
磁盘上,文件系统包括操作系统,总块数,空闲块数及位置,目录结构,具体文件等;
内存则用于进行文件系统管理、缓存以提高性能。打开一个文件,内存中的打开文件表则相应增加一个条目,反之剔除。调用open()返回该文件的指针,windows称之为 文件句柄(file handle)。

磁盘分区分为根分区和其他分区。根分区有操作系统内核或其他系统文件,在开机引导时装入内存。其他分区可以在引导时装入或之后手动装入。引导信息本身有自己的格式,但它并不能解释文件系统。系统的启动装载器首先装入引导信息,而后可以引导磁盘上的操作系统,由操作系统对文件系统进行解释、使用。

也有的分区没有文件系统,即尚未格式化的,此谓生分区(或原始的,raw)。如果有文件系统,那就是熟的(cooked)。生磁盘用于没有合适的文件系统的地方,可以被操作系统作特殊用途;也有的数据库绕过操作系统自己格式化后直接使用,也可以用于RAID。

操作系统同时支持多个文件系统类型。绝大多数操作系统都采取了面向对象技术,对文件系统接口加以不同的实现来达到这个目的。这也包括网络文件系统类型,如NFS。
这里写图片描述

三、目录实现
目录分配和目录管理算法对文件系统的效率、性能和可靠性有很大影响。

1、线性列表
存储文件名和数据块指针的线性列表。简单,但性能不高。主要是新增或删除该目录下的文件时,需要线性搜索以确定无重名或找到相应文件。当然有许多办法改善,比如缓存、排序,或将条目标记、转移等。

2、哈希表
大大减少目录搜索时间,插入和删除也比较简单。哈希算法其实就是为了解决冲突的。

四、分配方法
一个磁盘可存储多个文件,那么如何为它们分配空间,才能有效使用磁盘空间和快速访问?常用的方法有三个:连续、链接和索引。
1、连续分配
文件在磁盘上占有连续的块,如此访问之时磁头移动最少,寻道数、寻道时间也最小。
但这种分配方式容易引起外部碎片,同时文件尺寸难以扩展。

2、链接
能解决连续分配的所有问题:每个文件是块的链表,块可以分布在磁盘任何地方。目录包含每个文件的第一块和最末块指针。缺点是只能有效地用于文件的顺序访问,不能支持直接访问。同时指针需要额外的空间。另外指针丢失或损坏会导致文件内容无法访问。
应对之道是采用簇代替块,比如4块为一簇;采用文件分配表(FAT)代替指针。

3、索引分配
链接是指针和块分布于磁盘,索引分配将所有指针放在一起。每个文件都有索引块。索引块是一个数组,元素i指向文件的块i,故而凭索引i就可以直接访问文件第i块。
索引分配模式没有碎片,问题是索引块多大合适?一个文件一个索引块,因此应该尽可能小,但太小的话,大文件的指针就不够用。这里有一些机制,比如支持多个索引块链接,或多层索引,或分级索引,如linux的方案。
这里写图片描述

4、性能
采用哪种磁盘分配方法,在于如何使用系统。一个主要为顺序访问的系统不应该与一个主要为随机访问的系统采用相同的方法。

五、空闲空间管理
空闲空间包括未使用空间及已删除文件的空间。为了记录空闲磁盘空间,分配给文件创建,或文件删除时进行回收,这个空闲表有如下几种形式:

1、位向量
每个磁盘块采用0或1进行表示:0代表已分配,1代表空闲。优点是查找第一个空闲块或者连续空闲块时相对简单和高效。因为计算机有位操作指令。缺点是比较消耗内存。一个40GB,每块1K的磁盘需要超过5M的空间来存储位图。

2、链表
将所有空闲块用链表连接起来,并将指向第一块的指针保存在磁盘的特殊位置,同时也缓存在内存中。每一块指向下一块,如此继续。效率不高,要遍历整个空闲表时,要读入每一块。不过,这种机会不大,通常操作系统只是简单地将一个空闲块分配给一个文件,每次只拿第一块就行了。

3、组
链表的改进型。将整条链表分成若干组,每个组只用一块存储组中其他块的地址,然后再用一块存储下一组的地址,如此继续。这样可以很快找到大量的空闲块。

4、计数
记录第一块的地址以及紧随其后的连续空闲块数量。这样,空闲表的每个条目包含磁盘地址和数量,比原来的条目大,但表总长度会更短,因为连续块的数量很多。适用于多个连续块同时分配或释放的情况。

六、效率与性能
磁盘是计算机主要部件中最慢的部分,常常成为系统性能的瓶颈。
1、效率
磁盘空间的有效使用主要取决于所使用的磁盘分配和目录管理算法。例如预先分配索引,根据文件大小以决定选用大簇或小簇;综合考虑是否内存中的文件条目数据结构;文件指针大小;等等。

2、性能
即使选择了基本文件系统,仍然能从多方面改善性能:磁盘缓存、页面缓存(虚拟内存)、异步写、预读,等等。

七、恢复
文件和目录保存在内存和磁盘上,必须确保系统失败不会引起数据丢失和不一致性。
1、一致性检查
由于缓存和异步写等,计算机崩溃后许多对文件和目录的修改会丢失,从而可能出现不一致状态。一致性检查程序常在系统重启时运行,试图修正。修正程度与文件的分配方法有关。

2、备份和恢复
最保险的办法是备份。

八、基于日志结构的文件系统
采用类似数据库日志的机制, 应对系统崩溃、不一致性等问题。

九、NFS
NFS是一种广泛使用的网络文件系统。它是通过局域网(甚至广域网)访问远程文件的软件实现和规范。

1、概述
NFS将一组互连工作站作为独立文件系统的机器组合,目的是允许透明地共享这些文件系统。共享是client - server模式的,一台机器既可能是客户机,也可能是服务器。共享可在任何两台对等机器之间进行。
一台机器如果要访问远程文件系统,首先要将远程目录安装到本地。一旦安装完成,远程目录与本地文件系统就有机集成起来,取代了原本的本地目录。本地目录成为新安装目录的根。NFS规范分两个:一是安装协议,一是远程文件访问协议。

2、安装协议
安装协议在客户机和服务器之间建立逻辑连接。安装操作包括要安装的远程目录名称和存储服务器名称。服务器维护一个输出列表,列出哪些文件系统允许输出以便安装。其中可能包括权限。

3、NFS协议
NFS协议提供了一组RPC以供远程文件操作。包括:
搜索文件
读取目录条目
操作链接和目录
访问文件属性
读写文件

NFS服务器一个显著特点是无状态,每次请求都要提供完整的参数。还要有序列号。在服务器端,崩溃或恢复对客户机是不可见的,那么客户机传过来的写需要确保同步,因而采用非易失性缓存。
单个NFS写时原子的,但NFS不提供并发控制,NFS上层服务需要提供锁。
NFS通过VFS与操作系统集成,客户机与服务器对等。

4、路径名转换
包括把路径名解析成独立的目录条目或组成部分。解析过程中,对每个组成名称和目录虚拟节点执行独立的NFS lookup调用,一旦遇到安装点,即向服务器发送一个RPC。客户端可缓存远程目录。不过当从服务器返回的属性与缓存内的属性不匹配时,目录缓存就要加以更新。

5、远程操作
NFS坚持远程服务,但采用了缓冲和缓存技术以提高性能。

2018-09-16 23:33:07 williamgavin 阅读数 5527

前言

前面说了操作系统启动时发生的事情,最后一个文件main.c中有这样一行代码:

if(!fork()){init();}

这行代码就是启动第一个进程,对于windows来说就是启动桌面,对于linux来说就是打开shell。这一篇文章说说操作系统的接口以及实现,即上层应用是如何穿过接口进入操作系统的。

操作系统的接口

什么是操作系统的接口

接口其实是一种抽象,比如插排,它将内部的电路全部封装起来,只提供两个插口,用电设备插上就能用;不用管插座内部是如何实现的。操作系统的接口也是如此,操作系统的接口其实就是一个个函数,知道它的功能然后直接调用就行,而不用管它内核里面是怎么实现的,因为这个函数是系统调用的,所以也称为系统调用。比如:write()、read()等等

POSIX标准

POSIX(Portable Operating System Interface of Unix),POSIX标准定义了操作系统应该为应用程序提供的接口标准,目的是为了增强程序的可移植性。

系统调用的实现

前面说的是操作系统的接口,说白了就是一个个函数,调用它们就可以使用相应的功能。那这些系统调用到底是如何实现的呢?下面就来解解密。解决三个问题:

  • 1,用户程序能不能直接调用系统内核
  • 2,如果不能直接调用,为什么?如何实现的
  • 3,用户程序如何才能调用系统内核

1,用户程序能不能直接调用系统内核

不能

2,如果不能直接调用,为什么?如何实现的

如果能的话,那么你从网上下载一段程序就可能进入系统内核获取你的root密码,那么还有什么安全感呢?

但是操作系统和用户程序都是在内存里面,在内存里面是可以交换数据的呀?那为什么就不能直接使用jmp、mov或者函数调用直接进入操作系统内核呢?怎么实现的呢?

实现方法:利用硬件设计将内核程序与用户程序进行隔离,内核程序的所在的那段内存程称为核心态,用户程序所在的那段内存叫用户态。用户态的程序不能直接访问核心态的数据。

实现手段:利用CS的低两位CPL和DS的低两位DPL来实现隔离。首先在head.s里面建立gdt表的时候就将内核段DPL置为0,而CPL是当前指令的特权级,如果是在用户态,那么CPL就为3(如果是核心态就是0);在访问某个地址的时候,要看有没有权限访问,0的特权级是高于3,如果CPL的特权级小于等于DPL的特权级,那么就不能访问;注意:如果CPL=DPL是可以访问的;比如CPL=0(说明是内核态),DPL=3(说明是用户态),CPL的特权级大于DPL的特权级,所以能访问。也就是说内核态能访问内存的任意区域。这个隔离对于跳转指令(jmp、mov)同样有效。

3,用户程序如何才能调用系统内核

用户态不能直接访问内核态,那么有什么方法可以访问呢?方法肯定是有的,不然系统调用就实现不了了啊;用户态访问内核态只能通过一种途径,那就是中断,int指令将使CS中的CPL从3变为0,这样就可以访问了(即进入内核),这是用户程序发起的调用内核代码的唯一方式。并且这个中断号只能是0x80.

系统调用的核心:

1,用户程序中包含一段包含int指令的代码
2,操作系统中有中断函数表,从中可以获取中断服务函数入口地址
3,操作系统执行中断服务函数

具体实现:以printf为例

首先c代码里面的printf是这样的,printf(“%d”,a);在printf()内部其实是调用了系统函数write,而write函数的函数头其实是这样的:

ssize_t write(int fd, const void *buf, size_t count);

fd:要进行写操作的文件描述词。
buf:需要输出的缓冲区
count:最大输出字节计数

可以看到,printf()函数的形参和write()的形参是不一样的,因此如果printf(“%d”,a)能调用write函数的话,肯定要对printf的形参进行处理,使其符合write函数的格式,或者说换一种方式调用。在printf()函数里面调用write()如下所示:

# include <unisted.h>
_syscall3(int, write, int, fd, const char* buf, off_t, count)

可以看到其实利用的是_syscall3这个宏,这个宏的定义如下:


#define _syscall3(type,name,atype,a,btype,b,ctype,c)\
type name(atype a, btype b, ctype c) \
{ long __res;\
__asm__ volatile(“int 0x80”:”=a”(__res):””(__NR_##name),
”b”((long)(a)),”c”((long)(b)),“d”((long)(c)))); if(__res>=0) return
(type)__res; errno=-__res; return -1;}

_syscall3这个宏调用之后就是展开成上面的一段汇编代码,比如write调用:

_syscall3(int, write, int, fd, const char* buf, off_t, count)

就是将宏展开的代码中的

type=int,name=write,atype=int,a=fd,btype=const char * ,b=buf,ctype=off_t,c=count;

用这些来替换;因此

type name(atype a, btype b, ctype c)

就变成了

int write(int fd,const char * buf, off_t count)

这样,展开的汇编代码一样跟着变。这里需要注意的是int0x80这个中断;前面已经说过在head.s里面会重新建立idt表,之后中断就是表示根据中断号查那个表,然后获取中断服务函数的入口地址,int0x80这个中断就是进入操作系统内核,这是上层应用进入操作系统的唯一手段,int 0x80相当于是操作系统的一个门户,接着看_syscall3宏定义下面的代码:

long __res;\
__asm__ volatile(“int 0x80”:”=a”(__res):””(__NR_##name),
”b”((long)(a)),”c”((long)(b)),“d”((long)(c)))); if(__res>=0) return
(type)__res; errno=-__res; return -1;

这是一段内嵌汇编,冒号左边为输入,右边为输入,,上面代码最右边一个冒号右边是:”“表示与前面的a一样,即eax这个寄存器,所以”“(_NR##name)的意思就是将__NR_write赋值给eax这个寄存器,__NR_write称为系统调用号,后面有大用。

在linux/inlcude/unistd.h中
# define __NR_write 4   

什么是系统调用号呢?所有的系统调用都是通过int 0x80这个中断来
调用的,那么如何区分是write调用还是read调用或者是其他调用呢?就是根据这个系统调用号来区分的,__NR_write表示write调用,会接着执行write对应的内核代码,__NR_read表示read调用,同理,其他的系统调用号也是如此。后面的

”b”((long)(a)),”c”((long)(b)),“d”((long)(c))

就是把形参的a、b、c依次赋值给ebx、ecx、edx三个寄存器;输入完成之后就通过int 0x80这个中断号进入操作系统,int 0x80这条指令执行完之后,eax中就会存放int 0x80的返回值,然后将这个返回值赋值给__res,__res就是int write()这个系统调用的返回值。write这个系统调用也就结束了。

总结一下_syscall3这个宏的用法:
调用这个宏可以理解为调用一个函数,宏的定义:

#define _syscall3(type,name,atype,a,btype,b,ctype,c)

type 表示函数返回值,name表示函数名,后面分别是三个形参的类型和行参名。
name不同,系统调用号不同,所以调用_syscall3之后执行的代码不同,在宏里面通过
int 0x80进入系统内核并将指条指令的结果存在eax寄存器中,然后返回到宏的调用处。

具体再扒一下:

前面说的int 0x80都是用“这条指令“来表示了,那么int 0x80到底
是什么呢?int 0x80是进入中断服务函数的一条指令。
int 指令首先要查idt表转去哪里执行。

void sched_init(void)
{ set_system_gate(0x80,&system_call); }

int 0x80对应的中断处理程序就是system_call,从这个init就知道这是一个初始化,0x80这个中断就是用后面这个system_call来处理,那么系统是怎么设置的呢?通过set_system_gate这个宏。

在linux/include/asm/system.h中
#define set_system_gate(n, addr) \
_set_gate(&idt[n],15,3,addr); //idt是中断向量表基址

set_system_gate这个宏又调用了_set_gate这个宏,

在linux/include/asm/system.h中
#define _set_gate(gate_addr, type, dpl, addr)\
__asm__(“movw %%dx,%%ax\n\t” “movw %0,%%dx\n\t”\
“movl %%eax,%1\n\t” “movl %%edx,%2:\
:”i”((short)(0x8000+(dpl<<13)+type<<8))),“o”(*(( \
char*)(gate_addr))),”o”(*(4+(char*)(gate_addr))),\
“d”((char*)(addr),”a”(0x00080000))

这里我也看不懂,但是我知道_set_gate这个宏的作用就是建立一个类似这样的下图表,处理函数入口点偏移=system_call,DPL就是3,段选择符就是0x0008,即CS是8。
这里写图片描述
用户态的程序如果要进入内核,必须使用0x80号中断,那么就必须先要进入idt表。用户态的CPL=3,且idt表的DPL故意设置成3,因此能够跳到idt表,跳到idt表中之后就能找到之后程序跳转的地方,也就是中断服务函数的起始地址,CS就是段选择符(8),ip就是”处理函数入口点偏移“。记不记得setup.s里面有一行

jmpi 0,8

这条指令表示根据gdt表跳转到内核代码的地址0处。CS=8,ip=system_call就是跳到内核的system_call这个函数;另外如果CS=8,那么CPL=0,因为CPL是CS最低两位。也就是说当前程序的特权级变了,变成内核态的了。完整流程:初始化的时候0x80号中断的DPL设成3,让用户态的代码能跳进来,跳进来之后根据CS=8将CPL设为0,到了内核态,到了内核态就什么都能干了,将来int 0x80返回的之后,CS最后两位肯定变成3,变成用户态。

中断处理函数system_call到底做了什么呢?


在linux/kernel/system_call.s中
nr_system_calls=72
.globl _system_call
_system_call: cmpl $nr_system_calls-1,%eax
ja bad_sys_call
push %ds push %es push %fs
pushl %edx pushl %ecx pushl %ebx //调用的参数
movl $0x10,%edx mov %dx,%ds mov %dx,%es //内核数据
movl $0x17,%edx mov %dx,%fs //fs可以找到用户数据
call _sys_call_table(,%eax,4) //a(,%eax,4)=a+4*eax
pushl %eax //返回值压栈,留着ret_from_sys_call时用
... //其他代码
ret_from_sys_call: popl %eax, 其他pop, iret

前面都是压栈和赋值,接着调用了_sys_call_table(,%eax,4)。
a(,%eax,4)=a+4*eax_sys_call_table(,%eax,4)=_sys_call_table+4*%eax;这是一种寻址方式。eax是系统调用号,那_sys_call_table是什么?

在include/linux/sys.h中
fn_ptr sys_call_table[]=
{sys_setup, sys_exit, sys_fork, sys_read, sys_write,
...};

在include/linux/sched.h中
typedef int (fn_ptr*)();

sys_call_table是一个fn_ptr类型的全局函数表,fn_ptr是一个函数指针,4个字节,这就是_sys_call_table+4*%eax;这里为什么要*4的原因,sys_call_table的每一项都是4个字节,然后就可以根据eax来知道要调用的真正中断服务函数的入口地址了,对于write系统函数来说,就是sys_write。

总结一下系统调用的实现:

printf ->_syscall3 ->write -> int 0x80 -> system_call -> sys_call_table -> sys_write
printf通用_syscall3这个宏调用write函数,在write函数里面用system_call来处理int 0x80,在system_call中会调用system_call_table这个表,根据eax中存储的系统调用号就可以找到真正的sys_write了。

参考资料

哈工大李志军操作系统