精华内容
下载资源
问答
  • 大数据安全管理框架 Ranger 原理介绍

    千次阅读 热门讨论 2019-01-12 14:52:15
    文章目录一、Ranger是什么二、Ranger的权限模型三、Ranger架构Ranger-adminPluginHive Plugin 授权流程四...ranger大数据领域的一个集中式安全管理框架,它可以对诸如hdfs、hive、kafka、storm等组件进行细粒度的权...

    一、Ranger是什么

    ranger大数据领域的一个集中式安全管理框架,它可以对诸如hdfs、hive、kafka、storm等组件进行细粒度的权限控制。比如它可以控制用户读取hdfs文件的权限,甚至可以控制某个用户对hive某个列访问的权限。

    ranger目前支持的组件:

    二、Ranger的权限模型

    Ranger的权限模型一条条的权限策略组成的,权限策略主要由3个方面组成,即用户、资源、权限。

    用户:ranger可以对用户进行分组,一个用户可以属于多个分组。Ranger支持对用户或者用户组配置某资源的相关权限。

    资源:对于各个不同的组件,资源的表述都不相同。比如在HDFS中,文件路径就是资源,而在Hive中,资源可以指Database、Table甚至Column。

    权限:ranger可以对各个资源的读、写、访问进行限制,具体可以配置白名单、白名单排除、黑名单、黑名单排除等条目。

    ranger管理员配置好组件的相关策略后,并且将ranger插件安装到具体的大数据组件中,ranger就可以开始生效了。这时用户访问ranger就会进行权限的校验过程了,校验过程如下:

    当用户要请求某个资源时,会先获取和这个资源有关联的所有配置的策略,之后遍历这些策略,然后根据黑白名单判断该用户是否有权限访问该资源。从上面的流程图可以看出,黑名单、黑名单排除、白名单、白名单排除匹配的优先级如下:

    1. 黑名单优先级高于白名单
    2. 黑名单排除的优先级高于黑名单
    3. 白名单排除的优先级高于白名单

    决策下放

    如果没有policy能决策访问,一般情况是认为没有权限拒绝访问,然而Ranger还可以选择将决策下放给系统自身的访问控制层。

    三、Ranger架构

    Ranger-admin

    ranger-admin是基于Jersey+Spring+EclipseLink框框开发的Web服务,对外提供了Restful风格的http服务。Ranger-Admin模块同样内嵌了jsp界面,用于管理员管理用户、资源、权限等信息。同样,我们可以基于它的Restful Api来编写自己的权限管理sdk。

    虽然Jersey和SpringMVC同样实现了JAX-RS(javaee6提供Java API for RESTful Web Services)规范,但是jersey更加适合构建restful风格的服务,因为它天生就是为restful而生的。

    EclipseLink 是 JPA(Java Persistence Api)的一种实现,是java的一个ORM框架。

    Plugin

    那么ranger是如何实现对大数据组件的权限控制访问呢?这就和ranger实现的一个个plugin有关系了。

    因为几乎所有的大数据组件都有提供一个抽象的验证接口,ranger就是根据这些接口为各个大数据组件实现了对应的plugin,plugin的工作主要是从Ranger-Admin处拉取该组件配置的所有策略,然后缓存到本地,然后当有用户来请求时提供鉴权服务。

    Hive Plugin 授权流程

    举个Hive Plugin的授权流程的例子。

    对于权限模块,Hive提供了几个接口来给开发者来实现自己的授权策略。他们分别是

    • org.apache.hadoop.hive.ql.security.authorization.plugin.HiveAuthorizerFactory
    • org.apache.hadoop.hive.ql.security.authorization.plugin.HiveAuthorizer

    其中HiveAuthorizerFactory用来生成HiveAuthorizer的相关实例,之后Hive在进行编译的时候会调用HiveAuthorizer#checkPrivileges()方法进行权限校验

    我们要实现自己的授权策略,只需实现这两个接口,然后配置一下相关的配置文件即可。Ranger就是通过实现这两个接口来定制自己的授权流程。Ranger对这两个的实现分别是

    • org.apache.ranger.authorization.hive.authorizer.RangerHiveAuthorizerFactory
    • org.apache.ranger.authorization.hive.authorizer.RangerHiveAuthorizer

    之后配置hiveserver2-site.xml文件

    <property>
         <name>hive.security.authorization.enabled</name>
         <value>true</value>
    </property>
    <property>
         <name>hive.security.authorization.manager</name>
       <value>org.apache.ranger.authorization.hive.authorizer.RangerHiveAuthorizerFactory</value>
    </property>
    

    重启hiveserver2后生效。

    RangerHiveAuthorizer在初始化时会启动一个PolicyRefresher线程定时的从Ranger-admin拉取所有的策略,然后缓存到本地,之后当需要授权时直接根据这些策略进行授权。PolicyRefresher默认是每隔30s拉取一次

    四、一些思考

    Ranger的架构其实很简单,主要就是Ranger-Admin和各个组件的Plugin,其代码也大多都是偏业务逻辑的。不过所谓魔鬼在细节,下面探讨一些Ranger在实现上比较有趣或者可以借鉴的地方。

    1. 关于组件策略的缓存

    由于组件的多样性以及策略的复杂性,在ranger的数据库中,和策略相关的表就多达10来张。也就是说,一个组件要来Ranger-Admin拉取其相关的所有策略时,需要查询这十多张表,然后将其组织成组件需要的数据结构,再转成json返回。这是一个相对耗时较长的操作,另外,不止组件会获取这些策略,web界面或者sdk也可能调用相关接口获取所有策略。

    为了提高获取策略的性能,ranger以Service为单位,来缓存这些已经组织好的数据。当数据被缓存起来后,后续的请求只需从内存中获取相关数据即可。—— service可以理解为一个具体的大数据组件

    使用缓存会引发另一个问题,怎么判定缓存已经失效?为了解决这个问题,Ranger在Service表中添加了一个字段:policy_version,只要和这个service的策略有关的属性发生了变化,就更新policy_version。当用户请求策略时,发现policy_version和缓存的不一致,就会重新拉取数据,然后更新缓存。

    当然,这种缓存策略也很容易引发bug,比如ranger之前有一个bug,它在更新用户组信息时,不会更新该service的policy_version,导致组件拉取到的策略数据还是旧数据

    因此,虽说做这种缓存可以很大程度提高性能,但同时也要格外关注这些缓存的更新问题。

    2. 插件类加载器的实现

    ranger实现大数据组件的授权接口时,一般需要把打包好的jar包放到对应大数据组件的classpath下供组件加载。同时,ranger在实现授权接口时难免会用到其他的一些第三方库,这时问题就来了:如果把这些第三方库的jar包也放到大数据组件的classpath下,引发的版本冲突该如何解决

    举个例子,比如Hive用到了apache-common-1.0.0,ranger编写hive插件时也用到了apache-common-2.0.0,这时候版本就发生了冲突。如果要完全去兼容hive的第三方库版本是一件很累的事情,同时给hive引入过多的第三方库也会导致一些潜在的隐患。

    为了解决这个问题,Ranger定义了自己的一个类加载器,用于加载它使用的那些第三方库。在介绍ranger的自定义类加载器之前,我们先简单回顾一下java中的类加载器的一些概念。

    首先我们需要明白一件事,在java中,一个类的唯一性并不是由包名+类名来决定的,还要根据类加载器。也就是说,比如有一个类com.Test,分别有两个不同的类加载器加载,那么它们加载出的两个类就不是相等的

    Java中的类加载器

    java中常见的类加载器有

    1. 启动(Bootstrap)类加载器:负责装载<Java_Home>/lib下面的核心类库或-Xbootclasspath选项指定的jar包。由native方法实现加载过程,程序无法直接获取到该类加载器,无法对其进行任何操作。 另外,java中有规定以java.*开头的类必须是BootstrapClassLoader来加载的。

    2. 扩展(Extension)类加载器:扩展类加载器由sun.misc.Launcher.ExtClassLoader实现的。负责加载<Java_Home>/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库。程序可以访问并使用扩展类加载器。

    3. 系统(System)类加载器:系统类加载器是由sun.misc.Launcher.AppClassLoader实现的,也叫应用程序类加载器。负责加载系统类路径-classpath或-Djava.class.path变量所指的目录下的类库。程序可以访问并使用系统类加载器。

    双亲委派类加载机制:

    在java世界中,有一个双亲委派机制,是说要加载一个类的时候,先交给父级类加载器加载,父级类加载器无法加载时,自己才能去加载这个类

    这是我们在实现自己的类加载器时,普遍要遵守的一个机制。但是Ranger实现的类加载器破坏了双亲委派机制,从而将自己使用的第三方库和组件使用的第三方库隔离。由于ranger使用的第三方插件的所有类都是自己定义的类加载器加载的,因此不用担心第三方库冲突的问题

    我们可以看一下安装完ranger插件的hive/lib目录:

    在这里插入图片描述

    只多了两个jar包和一个文件夹

    • ranger-plugin-classloader-1.0.1.jar : ranger实现的类加载器的相关代码
    • ranger-hive-plugin-shim-1.0.1.jar : 提供了RangerHiveAuthorizerFactory类,用于给hive的类加载器加载
    • ranger-hive-plugin-impl : 文件夹,ranger使用的第三方库都放在该目录下,RangerPluginClassLoader加载类时会从该目录下扫描类。

    五、总结

    虽说ranger是apache的顶级开源项目,覆盖的大数据组件也足够多,但是读过它的部分代码后,觉的ranger代码写的真的挺差的,我们在使用的时候也发现并并修复了一些bug,还针对它的sql做了一些优化。

    另外ranger的代码个人感觉业务逻辑偏多,"黑科技"比较少,毕竟只是一个web项目+一些插件实现。不过了解这个项目确实也让我学到了不少东西,还是有一定的价值的,后面打算找一些时间整理一份ranger的安装和使用文档。

    展开全文
  • Shiro 安全框架

    千次阅读 2016-07-03 22:05:28
    功能强大,使用简单的Java安全框架,它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案。 实际上,Shiro的主要功能是管理应用程序中与安全相关的全部,同时尽可能支持多种实现方法。Shiro是...

    Shiro 安全框架

    一、Shiro简介

    Apache Shiro是Java的一个安全框架。功能强大,使用简单的Java安全框架,它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案。 
    实际上,Shiro的主要功能是管理应用程序中与安全相关的全部,同时尽可能支持多种实现方法。Shiro是建立在完善的接口驱动设计和面向对象原则之上的,
    支持各种自定义行为。Shiro提供的默认实现,使其能完成与其他安全框架同样的功能。
    Apache Shiro相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,
    所以使用小而简单的Shiro就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。
    Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以帮助我们完成:认证、授权、加密、会话管理、
    与Web集成、缓存等。
    

    二、Shiro原理

    2.1 Shiro的API
    1、内部API
        ![这里写图片描述](https://img-blog.csdn.net/20160703220347751)
        Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
        Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。
            或者细粒度的验证某个用户对某个资源是否具有某个权限;
        Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
        Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
        Web Support:Web支持,可以非常容易的集成到Web环境;
        Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
        Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
        Testing:提供测试支持;
        Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
        Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
        记住一点,Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。
    
    2、外部API
        ![这里写图片描述](https://img-blog.csdn.net/20160703220408564)
        可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:
        Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;
        即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;
            SecurityManager才是实际的执行者;
        SecurityManager:安全管理器;可以看作为Shrio的核心控制器,即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,
            它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
        Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较
            以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
        也就是说对于我们而言,最简单的一个Shiro应用:
        1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
        2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
        从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。
    
    2.2 Shiro的具体内部功能模块
    Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;
    SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;
        所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
    Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;
        其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
    Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
    Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,
        或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
    SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;
        而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;
        这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话
        (如把数据放到Memcached服务器);
    SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;
        比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
    CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
    Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。
    

    2.3 Shiro过滤器 权限拦截器

    过滤器简称       作用                    对应的java类
    Anon         匿名可以访问             org.apache.shiro.web.filter.authc.AnonymousFilter     
    Authc        认证才可访问             org.apache.shiro.web.filter.authc.FormAuthenticationFilter
    authcBasic                          org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
    perms                               org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
    port                                org.apache.shiro.web.filter.authz.PortFilter
    rest                                org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
    roles                               org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
    ssl                                 org.apache.shiro.web.filter.authz.SslFilter
    user                                org.apache.shiro.web.filter.authc.UserFilter
    logout      登出过滤器               org.apache.shiro.web.filter.authc.LogoutFilter
    

    2.4 JSP Shiro标签

    使用时,需要先导入Shiro标准标签库
    <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
    标签名称                            标签条件(均是显示标签内容)
    <shiro:authenticated>               登录之后,才显示
    <shiro:notAuthenticated>            没有登录时才显示
    <shiro:guest>                       用户在没有RememberMe,才显示
    <shiro:user>                        用户在RememberMe时,才显示
    <shiro:hasAnyRoles name="abc,123" > 在有abc或者123角色时,才显示
    <shiro:hasRole name="abc">          拥有角色abc,才显示
    <shiro:lacksRole name="abc">        没有角色abc,才显示
    <shiro:hasPermission name="abc">    拥有权限资源abc,才显示
    <shiro:lacksPermission name="abc">  没有abc权限资源,才显示
    <shiro:principal>                   默认显示用户名称,才显示
    

    2.5 Spring security 与apache shiro 差别:

    shiro配置更加容易理解,容易上手;security配置相对比较难懂。
    在spring的环境下,security整合性更好。Shiro对很多其他的框架兼容性更好,号称是无缝集成。
    shiro 不仅仅可以使用在web中,它可以工作在任何应用环境中。
    在集群会话时Shiro最重要的一个好处或许就是它的会话是独立于容器的。
    Shiro提供的密码加密使用起来非常方便。
    

    三、Shiro实现

    3.1 导入Shrio依赖的jar包

    shiro-all-xxx.jar
    Maven工程
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-all</artifactId>
        <version>1.2.3</version>
    </dependency>
    

    3.2 在web.xml中配置相应的过滤器

    注意,该过滤器必须配置到struts过滤器之前,否则action都无法进行创建
    <!--配置Shiro核心控制filter,DelegatingFIlterProxy
    一个filter相当于10个filter;web.xml
    注意:shiro的filter必须在struts2的filter之前,否则action无法创建
    < Shiro Security filter  filter-name这个名字的值将来还会在spring中用到  
    -->
     <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    3.3 配置产生过滤器的代理方式

    在applicationContext.xml文件中,必须在声明式事务之前配置、
    表示使用cglib生成代理
      <aop:aspectj-autoproxy proxy-target-class="true" />
    

    3.3 Shiro的配置文件

    1、准备ehcache-shiro.xml缓存配置文件,放到classpath目录下
    2、配置applicationContext-shiro.xml文件
        <!--配置安全管理器  -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <!-- 注入自定义的realm类 -->
            <property name="realm" ref="authRealm"/>
            <!-- 配置二级缓存 -->
            <property name="cacheManager" ref="shiroEhcacheManager"/>
        </bean>
    
        <!-- 自定义权限控制,用户认证类 -->
        <bean id="authRealm" class="cn.lx.jk.shiro.AuthRealm">
            <!--注入userService用于查询用户  -->
             <property name="userService" ref="userService"/>
             <!-- 注入密码比较器 --> 
            <property name="credentialsMatcher" ref="passwordMatcher"/>  
        </bean>
    
        <!-- 自定义加密策略 ,密码比较器-->
        <bean id="passwordMatcher" class="cn.lx.jk.shiro.CustomCredentialsMatcher"/>
        <!--配置shiro过滤器  -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <!--注入安全管理器  -->
            <property name="securityManager" ref="securityManager"/>
            <!--登陆时跳转页面  -->
            <property name="loginUrl" value="/index.jsp"></property>
            <!-- 没有权限或者失败后跳转的页面 -->
            <property name="successUrl" value="/home.action"></property>
            <!-- 配置过滤规则 -->
            <property name="filterChainDefinitions">
                <value>
                <!--配置过滤规则 
                    anon表示无需认证就可访问
                    authc表示认证后才可访问
                 -->
                    /index.jsp* = anon
                    /home* = anon
                    /sysadmin/login/login.jsp* = anon
                    /sysadmin/login/logout.jsp* = anon
                    /login* = anon
                    /logout* = anon
                    /components/** = anon
                    /css/** = anon
                    /images/** = anon
                    /js/** = anon
                    /make/** = anon
                    /skin/** = anon
                    /stat/** = anon
                    /ufiles/** = anon
                    /validator/** = anon
                    /resource/** = anon
                    /** = authc
                    /*.* = authc
    
                </value>
            </property>
        </bean>
    
        <!-- 用户授权/认证信息Cache, 采用EhCache  缓存 -->
        <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">、
            <!-- 设置ehcache配置文件 -->
            <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
        </bean>
    
        <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
    
        <!-- 生成代理,通过代理进行控制 -->
        <bean  class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
              depends-on="lifecycleBeanPostProcessor">
            <!-- 使用cglib代理 -->
            <property name="proxyTargetClass" value="true"/>
        </bean>
    
        <!-- 认证事务-->
        <bean  class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
            <property name="securityManager" ref="securityManager"/>
        </bean>
    3、在applicatioContext.xml文件中导入shiro配置文件
           <import resource="classpath:spring/applicationContext-shiro.xml"></import> 
    

    3.4 编写密码比较器

    /**密码比较器
     * 规范,需要继承doGetAuthenticationInfo
     * 重写doCredentialsMatch密码比较方法
     * @author liuxuan
     *
     */
    public class CustomCredentialsMatcher extends SimpleCredentialsMatcher{
    
        /**
         * 密码比较方法
         * AuthenticationToken 里面存储了,用户登录时,填写的账号和密码
         * AuthenticationInfo 存储的是用户的原始密码
         * */
        @Override
        public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
            //向下转型
            UsernamePasswordToken upToken = (UsernamePasswordToken) token;
            //获取用户输入的密码
            String password = new String(upToken.getPassword());
            //对密码进行加密   
            String newPassword = Encrypt.md5(password, upToken.getUsername());
            //获取原始密码
            Object oldPassword = info.getCredentials();
            //进行比较
            return equals(newPassword, oldPassword);
        }
    
    }
    

    3.5 编写认证授权relam类

    /**realm作用域,主要实现用户认证和权限授权操作。
     * 规范,需要实现AuthorizingRealm类
     * 重写doGetAuthorizationInfo 权限授权方法
     * 重现doGetAuthenticationInfo 角色认证方法
     * @author liuxuan
     *
     */
    public class AuthRealm extends AuthorizingRealm{
        //注入userService
        private UserService userService;
    
        public void setUserService(UserService userService) {
            this.userService = userService;
        }
    
        /**
         * 当页面有shiro标签时,就会调用授权方法
         * */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            //1、获取登陆认证时,存入到shiro的realm域中的User对象
            //fromRealm是获取当前域中的PrincipalCollection的集合,进行迭代,获取第一个元素,就是我们存储的用户
            User user = (User) principals.fromRealm(this.getName()).iterator().next();
            //2、用于存储,用户模块的权限集合
            Set<String> userModule =  new HashSet<String>();
            //2、1获取当前用户的角色
            for(Role role:user.getRoles()){
                for(Module module:role.getModules()){
                    //2、2加载用户顶端菜单
                    if(module.getCtype()==0){
                        //2、3添加模块名称
                        userModule.add(module.getName());
                    }
    
                }
            }
            //2、3 创建权限授权对象
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addStringPermissions(userModule);
            return info;
        }
    
        /**
         * 当用户登陆时,自动调用该方法,即为认证方法
         * 当这个方法返回Null时,就会出现异常
         * 当认证成功后,返回的对象不为null时,就会自动进入密码比较器
         * 使用密码比较器,是在配置文件中配置的
         * AuthenticationToken 封装用户输入的用户名和密码
         * */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            UsernamePasswordToken upToken = (UsernamePasswordToken) token;
            //获取用户输入的用户名
            String userName = upToken.getUsername();
            //根据用户名查询用户是否存在
            User user = userService.findByName(userName);
            //如果用户不存在返回Null
            if(user == null){
                return null;
            }
            //如果用户存在返回AuthenticationInfo  三个 参数,第一个参数为Object principal用户,第二个参数为密码getCredentials,
            //第三个参数为当前realm域的名称
    
            AuthenticationInfo info  = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
            return info;
        }
    
    }
    

    3.6 登陆方法

        private String username;
        private String password;
    
        public String login() throws Exception {
            //如果没有填写用户名,就跳转到登陆页面
            if(username ==null){
                request.put("msg", "请先登陆");
                return "login";
            }
            try {
                //shrio认证
                //1 得到Subject
                Subject subject =SecurityUtils.getSubject();
                //2 实现登陆
                //2.1将用户名和密码进行封装,交给shiro安全框架,并实现登陆过程
                UsernamePasswordToken uptoken = new UsernamePasswordToken(username, password);
                //3该方法执行后,就会进入AuthRealm的用户认证方法doGetAuthenticationInfo,该方法执行会调用密码比较器
                subject.login(uptoken);
                //4当认证成功后,将shrio中的用户对象取出来,放到session域中
                User user = (User) subject.getPrincipal();
                //SysConstant用于存储项目常量,好处时,需要修改常量时,只需要修改一处即可。
                session.put(SysConstant.CURRENT_USER_INFO, user);
                //5实现页面跳转
                return SUCCESS;
    
            } catch (Exception e) {
                //6 当输入的用户名和密码错误,会抛出异常
                e.printStackTrace();
                request.put("msg","用户名或密码错误");
                return "login";
            }
        }
    

    3.7 退出方法

    public String logout(){
        //移除session中存储的用户
        session.remove(SysConstant.CURRENT_USER_INFO);  
    
        Subject subject =SecurityUtils.getSubject();
        //调用登出方法
        subject.logout();
        return "logout";
    }
    

    3.8 页面上进行权限认证

    1、导入shiro标签库
    <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
    2、
         <!-- 当jsp页面碰到shiro标签时就执行AuthRealm中授权方法
        <shiro:hasPermission name="系统首页"> 表示有该权限才会显示下面内容
         -->
        <shiro:hasPermission name="系统首页">
        <span id="topmenu" onclick="toModule('home');">系统首页</span><span id="tm_separator"></span>
        </shiro:hasPermission>
        <shiro:hasPermission name="流程管理">
        <span id="topmenu" onclick="toModule('activiti');">流程管理</span>
        </shiro:hasPermission>
    

    四、Shiro框架的运行流程

    1、登录之后,运行loginAction 中的login()方法
    2、将用户名和密码封装到UserpasswordToken 中,也即封装到shiro中
        UsernamePasswordtoken upToken = new UsernamePasswordtoken();
    3、因为Shiro对外提供的接口类就是Subject,所以,调用SecurityUtis.getSubject()方法获取到Subject类
        Subject subject = SecurityUtils.getSubject();
    4、调用Subkject的登陆方法subje,传入uptoken对象
        subject.login(upToken);
        4.1该方法执行后,会进入realm类中的认证方法doGetAuthenticationInfo(AuthenticationToken token)
            4.1.1 将token强转为UsernamePasswordToken 类型
                UsernamePasswordToken upToken = (UsernamePasswordToken)token;
            4.1.2 获取用户输入的用户名
                String userName = upToken.getUsername();    
            4.1.3通过用户名查询用户
                User user = userService.findByName(userName);
            4.1.4判断用户是否存在
                不存在直接返回Null
            4.1.5如果用户存在,返回AuthenticationInfo 
                //如果用户存在返回AuthenticationInfo  三个 参数,第一个参数为Object principal用户,第二个参数为密码getCredentials,
                    第三个参数为当前realm域的名称
    
                AuthenticationInfo info  = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
                return info;
        4.2之后会执行密码比较器CustomCredentialsMatcher中的密码比较方法
                    doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
            4.2.1将token强转为UsernbamePasswordtoken
                UsernamePasswordToken upToken = (UsernamePasswordToken)t
            4.2.2获取到用户输入的密码,并进行加密
                //获取用户输入的密码
                String password = new String(upToken.getPassword());
                //对密码进行加密,使用Md5Hash进行加密  
                String newPassword = Encrypt.md5(password, upToken.getUsername());
            4.2.3获取原始密码
                Object oldPassword = info.getCredentials();
            4.2.4比较密码是否相等,并将boolean型结果返回
                this.equals(newPassword, oldPassword)
    5、登陆成功之后,获取用户信息,并存储到session中
        5.1获取用户 
        User user = (User) subject.getPrincipal();
        5.2存储到session中
        session.put(SysConstant.CURRENT_USER_INFO, user);
        5.3返回结果视图
            reruen SUCCESS;
    6、当页面有shrio标签时,会自动调用realm中的权限认证方法doGetAuthorizationInfo(PrincipalCollection principals)
        6.1 获取当前登陆的用户
            //fromRealm是获取当前域中的PrincipalCollection的集合,进行迭代,获取第一个元素,就是我们存储的用户
            User user = (User) principals.fromRealm(this.getName()).iterator().next();      
        6.2获取当前用户的权限模块名称,存到集合中
            Set<String> userModule =  new HashSet<String>();
            //2、1获取当前用户的角色
            for(Role role:user.getRoles()){
                for(Module module:role.getModules()){
                    //2、2加载用户顶端菜单
                    if(module.getCtype()==0){
                        //2、3添加模块名称
                        userModule.add(module.getName());
                    }
    
                }
            }
        6.3 创建权限授权对象,并添加相应的权限集合
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addStringPermissions(userModule);
            return info
    7、登出方法
        //移除session中存储的用户
        session.remove(SysConstant.CURRENT_USER_INFO);  
    
        Subject subject =SecurityUtils.getSubject();
        //调用登出方法
        subject.logout();
    
    展开全文
  • 权限管理框架

    千次阅读 2019-04-15 14:40:52
    之前看的视频笔记, 做个...Shiro:权限管理框架 权限管理: 基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略...

    转载至:https://blog.csdn.net/d124939312/article/details/78954416

     

    之前看的视频笔记, 做个备份,以免想找的时候总找不到。 

     

    Shiro:权限管理框架

     

    权限管理:

     

             基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。

             权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。

     

    认证:

     

    就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。

     

    授权:

    所谓授权就是权限控制, 通过认证的用户我们给与它不同访问权限, 不同角色拥有不同的权限,进入到系统中看到的以及用到的资源也不一样。

     

    基本的通用权限模型:

     

    通常企业开发中将资源和权限表合并为一张权限表(也可以分开两张表,单独进行关联),如下:

     

    主体(账号、密码)

    资源(资源名称,访问地址)

    权限(权限名称、资源id)

    *(

    或者合并为:

    权限(权限名称、资源名称、资源访问地址)

    )

    角色(角色名称)

    角色和权限关系(角色id、权限id)

    主体和角色关系(主体id、角色id)

     

     

    上图常被称为权限管理的通用模型,不过企业在开发中根据系统自身的特点还会对上图进行修改,但是用户、角色、权限、用户角色关系、角色权限关系是需要去理解的。

     

     

    权限管理的解决方案2种:

    1,基于url拦截

    * 要写自定义的拦截器

     

    基于url拦截是企业中常用的权限管理方法,实现思路是:将系统操作的每个url配置在权限表中,将权限对应到角色,将角色分配给用户,用户访问系统功能通过Filter进行过虑,过虑器获取到用户访问的url,只要访问的url是用户分配角色中的url则放行继续访问。

     

    例:

    用户身份认证拦截器:
    使用springmvc拦截器对用户身份认证进行拦截,如果用户没有登陆则跳转到登陆页面,本功能也可以使用filter实现 。


    public class LoginInterceptor implements HandlerInterceptor {


    // 在进入controller方法之前执行
    // 使用场景:比如身份认证校验拦截,用户权限拦截,如果拦截不放行,controller方法不再执行
    @Override
    public boolean preHandle(HttpServletRequest request,
    HttpServletResponse response, Object handler) throws Exception {


    // 校验用户访问是否是公开资源地址(无需认证即可访问)
    List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL");


    // 用户访问的url
    String url = request.getRequestURI();
    for (String open_url : open_urls) {
    if (url.indexOf(open_url) >= 0) {
    // 如果访问的是公开 地址则放行
    return true;
    }
    }


    // 校验用户身份是否认证通过
    HttpSession session = request.getSession();
    ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
    if (activeUser != null) {
    // 用户已经登陆认证,放行
    return true;
    }
    // 跳转到登陆页面
    request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,
    response);
    return false;
    }




    用户授权拦截器:
    使用springmvc拦截器对用户访问url进行拦截,如果用户访问的url没有分配权限则跳转到无权操作提示页面(refuse.jsp),本功能也可以使用filter实现。

    public class PermissionInterceptor implements HandlerInterceptor {


    // 在进入controller方法之前执行
    // 使用场景:比如身份认证校验拦截,用户权限拦截,如果拦截不放行,controller方法不再执行
    // 进入action方法前要执行
    @Override
    public boolean preHandle(HttpServletRequest request,
    HttpServletResponse response, Object handler) throws Exception {
    // TODO Auto-generated method stub
    // 用户访问地址:
    String url = request.getRequestURI();


    // 校验用户访问是否是公开资源地址(无需认证即可访问)
    List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL");
    // 用户访问的url
    for (String open_url : open_urls) {
    if (url.indexOf(open_url) >= 0) {
    // 如果访问的是公开 地址则放行
    return true;
    }
    }
    //从 session获取用户公共访问地址(认证通过无需分配权限即可访问)
    List<String> common_urls = ResourcesUtil.gekeyList("commonURL");
    // 用户访问的url
    for (String common_url : common_urls) {
    if (url.indexOf(common_url) >= 0) {
    // 如果访问的是公共地址则放行
    return true;
    }
    }
    // 从session获取用户权限信息


    HttpSession session = request.getSession();


    ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");


    // 取出session中权限url
    // 获取用户操作权限
    List<SysPermission> permission_list = activeUser.getPermissions();
    // 校验用户访问地址是否在用户权限范围内
    for (SysPermission sysPermission : permission_list) {
    String permission_url = sysPermission.getUrl();
    if (url.contains(permission_url)) {
    return true;
    }
    }


    // 跳转到页面
    request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(
    request, response);
    return false;
    }

     

     

     

    2, 利用权限框架shiro

    shiro:

    Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。

     

    shiro使用广泛,shiro可以运行在web应用,非web应用,集群分布式应用中越来越多的用户开始使用shiro。

     java领域中springsecurity(原名Acegi)也是一个开源的权限管理框架,但是spring security依赖spring运行,而shiro就相对独立,最主要是因为shiro使用简单、灵活,所以现在越来越多的用户选择shiro。

     

     

    Shiro架构:

     

     

    详解:

    1 Subject
    Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权


    2 SecurityManager 
    SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
    SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。


    3 Authenticator
    Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
    4 Authorizer
    Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。


    5 realm
    Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
    注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。


    6 sessionManager
    sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
    7 SessionDAO
    SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。
    8 CacheManager
    CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。
    9 Cryptography
    Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

     

     

    Shiro与项目集成开发

    1 加入shiro的 jar包

     

      shiro-spring-1.2.3.jar

      shiro-web-1.2.3.jar


    2 web.xml添加shiro Filter


    <!-- shiro过虑器,DelegatingFilterProx会从spring容器中找shiroFilter -->
    <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
    <param-name>targetFilterLifecycle</param-name>
    <param-value>true</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>




    3 applicationContext-shiro.xml 


    <!-- Shiro 的Web过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager" />
    <!-- 如果没有认证将要跳转的登陆地址,http可访问的url,如果不在表单认证过虑器FormAuthenticationFilter中指定此地址就为身份认证地址 -->
    <property name="loginUrl" value="/login.action" />
    <!-- 没有权限跳转的地址 -->
    <property name="unauthorizedUrl" value="/refuse.jsp" />
    <!-- shiro拦截器配置 -->
    <property name="filters">
    <map>
    <entry key="authc" value-ref="formAuthenticationFilter" />
    </map>
    </property>
    <property name="filterChainDefinitions">
    <value>
    <!-- 必须通过身份认证方可访问,身份认 证的url必须和过虑器中指定的loginUrl一致 -->
    /loginsubmit.action = authc
    <!-- 退出拦截,请求logout.action执行退出操作 -->
    /logout.action = logout
    <!-- 无权访问页面 -->
    /refuse.jsp = anon
    <!-- roles[XX]表示有XX角色才可访问 -->
    /item/list.action = roles[item],authc
    /js/** anon
    /images/** anon
    /styles/** anon
    <!-- user表示身份认证通过或通过记住我认证通过的可以访问 -->
    /** = user
    <!-- /**放在最下边,如果一个url有多个过虑器则多个过虑器中间用逗号分隔,如:/** = user,roles[admin] -->


    </value>
    </property>
    </bean>




    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="userRealm" />

    </bean>


    <!-- 自定义 realm -->
    <bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1">
    </bean>
    <!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
    <bean id="formAuthenticationFilter"
    class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
    <!-- 表单中账号的input名称 -->
    <property name="usernameParam" value="usercode" />
    <!-- 表单中密码的input名称 -->
    <property name="passwordParam" value="password" />
    <!-- <property name="rememberMeParam" value="rememberMe"/> -->
    <!-- loginurl:用户登陆地址,此地址是可以http访问的url地址 -->
    <property name="loginUrl" value="/loginsubmit.action" />
    </bean>






    securityManager:这个属性是必须的。
    loginUrl:没有登录认证的用户请求将跳转到此地址,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。
    unauthorizedUrl:没有权限默认跳转的页面。




    4 使用shiro注解授权


    在springmvc.xml中配置shiro注解支持,可在controller方法中使用shiro注解配置权限:


    <!-- 开启aop,对类代理 -->
    <aop:config proxy-target-class="true"></aop:config>
    <!-- 开启shiro注解支持 -->
    <bean
    class="
    org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager" />
    </bean>


    修改Controller代码,在方法上添加授权注解,如下:


    // 查询商品列表
    @RequestMapping("/queryItem")
    @RequiresPermissions("item:query")
    public ModelAndView queryItem() throws Exception {


    上边代码@RequiresPermissions("item:query")表示必须拥有“item:query”权限方可执行。
    其它的方法参考示例添加注解,一边添加一边思考这比基于url拦截有什么好处。




    5 自定义realm


    此realm先不从数据库查询权限数据,当前需要先将shiro整合完成,在上边章节定义的realm基础上修改。


    public class CustomRealm1 extends AuthorizingRealm {


    @Autowired
    private SysService sysService;


    @Override
    public String getName() {
    return "customRealm";
    }


    // 支持什么类型的token
    @Override
    public boolean supports(AuthenticationToken token) {
    return token instanceof UsernamePasswordToken;
    }


    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
    AuthenticationToken token) throws AuthenticationException {


    // 从token中 获取用户身份信息
    String username = (String) token.getPrincipal();
    // 拿username从数据库中查询
    // ....
    // 如果查询不到则返回null
    if (!username.equals("zhang")) {// 这里模拟查询不到
    return null;
    }


    // 获取从数据库查询出来的用户密码
    String password = "123";// 这里使用静态数据模拟。。

    // 根据用户id从数据库取出菜单
    //...先用静态数据
    List<SysPermission> menus = new ArrayList<SysPermission>();;

    SysPermission sysPermission_1 = new SysPermission();
    sysPermission_1.setName("商品管理");
    sysPermission_1.setUrl("/item/queryItem.action");
    SysPermission sysPermission_2 = new SysPermission();
    sysPermission_2.setName("用户管理");
    sysPermission_2.setUrl("/user/query.action");

    menus.add(sysPermission_1);
    menus.add(sysPermission_2);

    // 构建用户身体份信息
    ActiveUser activeUser = new ActiveUser();
    activeUser.setUserid(username);
    activeUser.setUsername(username);
    activeUser.setUsercode(username);
    activeUser.setMenus(menus);


    // 返回认证信息由父类AuthenticatingRealm进行认证
    SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
    activeUser, password, getName());


    return simpleAuthenticationInfo;
    }


    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
    PrincipalCollection principals) {
    // 获取身份信息
    ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
    //用户id
    String userid = activeUser.getUserid();
    // 根据用户id从数据库中查询权限数据
    // ....这里使用静态数据模拟
    List<String> permissions = new ArrayList<String>();
    permissions.add("item:query");
    permissions.add("item:update");


    // 将权限信息封闭为AuthorizationInfo


    SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    for (String permission : permissions) {
    simpleAuthorizationInfo.addStringPermission(permission);
    }


    return simpleAuthorizationInfo;
    }


    }






    6 登录
    //用户登陆页面
    @RequestMapping("/login")
    public String login()throws Exception{
    return "login";
    }
    // 用户登陆提交
    @RequestMapping("/loginsubmit")
    public String loginsubmit(Model model, HttpServletRequest request)
    throws Exception {


    // shiro在认证过程中出现错误后将异常类路径通过request返回
    String exceptionClassName = (String) request
    .getAttribute("shiroLoginFailure");
    if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
    throw new CustomException("账号不存在");
    } else if (IncorrectCredentialsException.class.getName().equals(
    exceptionClassName)) {
    throw new CustomException("用户名/密码错误");
    } else{
    throw new Exception();//最终在异常处理器生成未知错误
    }
    }


    7 首页


    由于session由shiro管理,需要修改首页的controller方法:


    //系统首页
    @RequestMapping("/first")
    public String first(Model model)throws Exception{

    //主体
    Subject subject = SecurityUtils.getSubject();
    //身份
    ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
    model.addAttribute("activeUser", activeUser);
    return "/first";
    }




    8 退出

    由于使用shiro的sessionManager,不用开发退出功能,使用shiro的logout拦截器即可。


    <!-- 退出拦截,请求logout.action执行退出操作 -->
    /logout.action = logout

    展开全文
  • SEAndroid安全机制框架分析

    万次阅读 多人点赞 2014-07-14 01:00:02
    我们知道,Android系统基于Linux实现。针对传统Linux系统,NSA开发了一套安全机制SELinux,用来加强安全性。然而,由于Android系统有着...本文就对SEAndroid安全机制框架进行分析,以便后面可以更好地分析其实现细节。

           我们知道,Android系统基于Linux实现。针对传统Linux系统,NSA开发了一套安全机制SELinux,用来加强安全性。然而,由于Android系统有着独特的用户空间运行时,因此SELinux不能完全适用于Android系统。为此,NSA针对Android系统,在SELinux基础上开发了SEAndroid。本文就对SEAndroid安全机制框架进行分析,以便后面可以更好地分析其实现细节。

    老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

    《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

           SEAndroid安全机制所要保护的对象是系统中的资源,这些资源分布在各个子系统中,例如我们经常接触的文件就是分布文件子系统中的。实际上,系统中需要保护的资源非常多,除了前面说的文件之外,还有进程、socket和IPC等等。对于Android系统来说,由于使用了与传统Linux系统不一样的用户空间运行时,即应用程序运行时框架,因此它在用户空间有一些特有的资源是需要特别保护的,例如系统属性的设置。

           接下来,我们就通过图1来观察SEAndroid安全机制的整体框架,如下所示:

    图1 SEAndroid安全机制框架

            从图1可以看到,以SELinux文件系统接口为边界,SEAndroid安全机制包含有内核空间和用户空间两部分支持。在内核空间,主要涉及到一个称为SELinux LSM的模块。而在用户空间中,涉及到Security Context、Security Server和SEAndroid Policy等模块。这些内核空间模块和用户空间模块的作用以及交互如下所示:

            1. 内核空间的SELinux LSM模块负责内核资源的安全访问控制。

            2. 用户空间的SEAndroid Policy描述的是资源安全访问策略。系统在启动的时候,用户空间的Security Server需要将这些安全访问策略加载内核空间的SELinux LSM模块中去。这是通过SELinux文件系统接口实现的。

            3. 用户空间的Security Context描述的是资源安全上下文。SEAndroid的安全访问策略就是在资源的安全上下文基础上实现的。

            4. 用户空间的Security Server一方面需要到用户空间的Security Context去检索对象的安全上下文,另一方面也需要到内核空间去操作对象的安全上下文。

            5. 用户空间的selinux库封装了对SELinux文件系统接口的读写操作。用户空间的Security Server访问内核空间的SELinux LSM模块时,都是间接地通过selinux进行的。这样可以将对SELinux文件系统接口的读写操作封装成更有意义的函数调用。

            6. 用户空间的Security Server到用户空间的Security Context去检索对象的安全上下文时,同样也是通过selinux库来进行的。

            接下来,我们就从内核空间和用户空间两个角度来分析SEAndroid安全机制框架。

            一. 内核空间

            在内核空间中,存在一个SELinux LSM模块,这个模块包含有一个访问向量缓冲(Access Vector Cache)和一个安全服务(Security Server)。Security Server负责安全访问控制逻辑,即由它来决定一个主体访问一个客体是否是合法的。这里说的主体一般就是指进程,而客体就是主体要访问的资源,例如文件。

            与SELinux Security Server相关的一个内核子模块是LSM,全称是Linux Security Model。LSM可以说是为了SELinux而设计的,但是它是一个通用的安全模块,SELinux可以使用,其它的模块也同样可以使用。这体现了Linux内核模块的一个重要设计思想,只提供机制实现而不提供策略实现。在我们这个例子中,LSM实现的就是机制,而SELinux就是在这套机制下的一个策略实现。也就是说,你也可以通过LSM来实现自己的一套MAC安全机制。

            SELinux、LSM和内核中的子系统是如何交互的呢?首先,SELinux会在LSM中注册相应的回调函数。其次,LSM会在相应的内核对象子系统中会加入一些Hook代码。例如,我们调用系统接口read函数来读取一个文件的时候,就会进入到内核的文件子系统中。在文件子系统中负责读取文件函数vfs_read就会调用LSM加入的Hook代码。这些Hook代码就会调用之前SELinux注册进来的回调函数,以便后者可以进行安全检查。

            SELinux在进行安全检查的时候,首先是看一下自己的Access Vector Cache是否已经有结果。如果有的话,就直接将结果返回给相应的内核子系统就可以了。如果没有的话,就需要到Security Server中去进行检查。检查出来的结果在返回给相应的内核子系统的同时,也会保存在自己的Access Vector Cache中,以便下次可以快速地得到检查结果。

            上面描述的安全访问控制流程可以通过图2来总结,如下所示:

    图2 SELinux安全访问控制流程

           从图2可以看到,内核中的资源在访问的过程中,一般需要获得三次检查通过:

           1. 一般性错误检查,例如访问的对象是否存在、访问参数是否正确等。

           2. DAC检查,即基于Linux UID/GID的安全检查。

           3. SELinux检查,即基于安全上下文和安全策略的安全检查。

           二. 用户空间

           在用户空间中,SEAndroid包含有三个主要的模块,分别是安全上下文(Security Context)、安全策略(SEAndroid Policy)和安全服务(Security Server)。接下来我们就分别对它们进行描述。

           1. 安全上下文

           SEAndroid是一种基于安全策略的MAC安全机制。这种安全策略又是建立在对象的安全上下文的基础上的。这里所说的对象分为两种类型,一种称主体(Subject),一种称为客体(Object)。主体通常就是指进程,而客观就是指进程所要访问的资源,例如文件、系统属性等。

           安全上下文实际上就是一个附加在对象上的标签(Tag)。这个标签实际上就是一个字符串,它由四部分内容组成,分别是SELinux用户、SELinux角色、类型、安全级别,每一个部分都通过一个冒号来分隔,格式为“user:role:type:sensitivity”。

           例如,在开启了SEAndroid安全机制的设备上执行带-Z选项的ls命令,就可以看到一个文件的安全上下文:

    $ ls -Z /init.rc
    -rwxr-x--- root     root     u:object_r:rootfs:s0 init.rc
           上面的命令列出文件/init.rc的安全上下文为“u:object_r:rootfs:s0”,这表明文件/init.rc的SELinux用户、SELinux角色、类型和安全级别分别为u、object_r、rootfs和s0。

           又如,在开启了SEAndroid安全机制的设备上执行带-Z选项的ps命令,就可以看到一个进程的安全上下文:

    $ ps -Z
    LABEL                          USER     PID   PPID  NAME
    u:r:init:s0                    root      1     0     /init
    ......
           上面的命令列出进程init的安全上下文为“u:r:init:s0”,这表明进程init的SELinux用户、SELinux角色、类型和安全级别分别为u、r、init和s0。

           在安全上下文中,只有类型(Type)才是最重要的,SELinux用户、SELinux角色和安全级别都几乎可以忽略不计的。正因为如此,SEAndroid安全机制又称为是基于TE(Type Enforcement)策略的安全机制。不过为了方便理解安全上下文,接下来我们还是简单地对SELinux用户、SELinux角色和安全级别的作用进行介绍。

           对于进程来说,SELinux用户和SELinux角色只是用来限制进程可以标注的类型。而对于文件来说,SELinux用户和SELinux角色就可以完全忽略不计。为了完整地描述一个文件的安全上下文,通常将它的SELinux角色固定为object_r,而将它的SELinux用户设置为创建它的进程的SELinux用户。

           在SEAndroid中,只定义了一个SELinux用户u,因此我们通过ps -Z和ls -Z命令看到的所有的进程和文件的安全上下文中的SELinux用户都为u。同时,SEAndroid也只定义了一个SELinux角色r,因此,我们通过ps -Z命令看到的所有进程的安全上下文中的SELinux角色都为r。

           通过external/sepolicy/users和external/sepolicy/roles文件的内容,我们就可以看到SEAndroid所定义的SELinux用户和SELinux角色。

           文件external/sepolicy/users的内容如下所示:

    user u roles { r } level s0 range s0 - mls_systemhigh;
           上述语句声明了一个SELinux用户u,它可用的SELinux角色为r,它的默认安全级别为s0,可用的安全级别范围为s0~mls_systemhigh,其中,mls_systemhigh为系统定义的最高安全级别。

           文件external/sepolicy/roles的内容如下所示:

    role r;
    role r types domain;
           第一个语句声明了一个SELinux角色r;第二个语句允许SELinux角色r与类型domain关联。

           上面提到,对于进程来说,SELinux用户和SELinux角色只是用来限制进程可以标注的类型,这是如何体现的呢?以前面列出的external/sepolicy/users和external/sepolicy/roles文件内容为例,如果没有出现其它的user或者role声明,那么就意味着只有u、r和domain可以组合在一起形成一个合法的安全上下文,而其它形式的安全上下文定义均是非法的。

           读者可能注意到,前面我们通过ps -Z命令看到进程init的安全上下文为“u:r:init:s0”,按照上面的分析,这是不是一个非法的安全上下文呢?答案是否定的,因为在另外一个文件external/sepolicy/init.te中,通过type语句声明了类型init,并且将domain设置为类型init的属性,如下所示:

    type init, domain;
           由于init具有属性domain,因此它就可以像domain一样,可以和SELinux用户u和SELinux角色组合在一起形成合法的安全上下文。

           关于SELinux用户和SELinux角色,我们就介绍到这里,接下来我们再介绍安全级别。安全级别实际上也是一个MAC安全机制,它是建立在TE的基础之上的。在SELinux中,安全级别是可选的,也就是说,可以选择启用或者不启用。

           安全级别最开始的目的是用来对美国政府分类文件进行访问控制的。在基于安全级别的MAC安全机制中,主体(subject)和客体(object)都关联有一个安全级别。其中,安全级别较高的主体可以读取安全级别较低的客体,而安全级别较低的主体可以写入安全级别较高的客体。前者称为“read down”,而后者称为“write up”。通过这种规则,可以允许数据从安全级别较低的主体流向安全级别较高的主体,而限制数据从安全级别较高的主体流向安全级别较低的主体,从而有效地保护了数据。注意,如果主体和客体的安全级别是相同的,那么主体是可以对客体进行读和写的。

            通过图3可以看到基于安全级别的MAC安全机制的数据流向控制,如下所示:

    图3 基于安全级别的MAC安全机制数据流

           在图3中,我们定义了两个安全级别:PUBLIC和SECRET,其中,SECRET的安全级别高于PUBLIC。

           在实际使用中,安全级别是由敏感性(Sensitivity)和类别(Category)两部分内容组成的,格式为“sensitivity[:category_set]”,其中,category_set是可选的。例如,假设我们定义有s0、s1两个Sensitivity,以c0、c1、c2三个Category,那么“s0:c0,c1”表示的就是Sensitivity为s0、Category为c0和c1的一个安全级别。

           介绍完成SELinux用户、SELinux角色和安全级别之后,最后我们就介绍类型。在SEAndroid中,我们通常将用来标注文件的安全上下文中的类型称为file_type,而用来标注进程的安全上下文的类型称为domain,并且每一个用来描述文件安全上下文的类型都将file_type设置为其属性,每一个用来进程安全上下文的类型都将domain设置为其属性。

           将一个类型设置为另一个类型的属性可以通过type语句实现。例如,我们前面提到的用来描述进程init的安全策略的文件external/sepolicy/init.te,就使用以下的type语句来将类型 domain设置类型init的属性:

    type init domain;
            这样就可以表明init描述的类型是用来描述进程的安全上下文的。

            同样,如果我们查看另外一个文件external/sepolicy/file.te,可以看到App数据文件的类型声明:

    type app_data_file, file_type, data_file_type;
            上述语句表明类型app_data_file具有属性file_type,即它是用来描述文件的安全上下文的。

            了解了SEAndroid安全机制的安全上下文之后,我们就可以继续Android系统中的对象的安全上下文是如何定义的了。这里我们只讨论四种类型的对象的安全上下文,分别是App进程、App数据文件、系统文件和系统属性。这四种类型对象的安全上下文通过四个文件来描述:mac_permissions.xml、seapp_contexts、file_contexts和property_contexts,它们均位于external/sepolicy目录中。

            文件external/sepolicy/mac_permissions.xml的内容如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <policy>
    
        <!-- Platform dev key in AOSP -->
        <signer signature="@PLATFORM" >
          <seinfo value="platform" />
        </signer>
    
        <!-- Media dev key in AOSP -->
        <signer signature="@MEDIA" >
          <seinfo value="media" />
        </signer>
    
        <!-- shared dev key in AOSP -->
        <signer signature="@SHARED" >
          <seinfo value="shared" />
        </signer>
    
        <!-- release dev key in AOSP -->
        <signer signature="@RELEASE" >
          <seinfo value="release" />
        </signer>
    
        <!-- All other keys -->
        <default>
          <seinfo value="default" />
        </default>
    
    </policy>
           文件mac_permissions.xml给不同签名的App分配不同的seinfo字符串,例如,在AOSP源码环境下编译并且使用平台签名的App获得的seinfo为“platform”,使用第三方签名安装的App获得的seinfo签名为"default"。

            这个seinfo描述的其实并不是安全上下文中的Type,它是用来在另外一个文件external/sepolicy/seapp_contexts中查找对应的Type的。文件external/sepolicy/seapp_contexts的内容如下所示:

    # Input selectors: 
    #   isSystemServer (boolean)
    #   user (string)
    #   seinfo (string)
    #   name (string)
    #   sebool (string)
    # isSystemServer=true can only be used once.
    # An unspecified isSystemServer defaults to false.
    # An unspecified string selector will match any value.
    # A user string selector that ends in * will perform a prefix match.
    # user=_app will match any regular app UID.
    # user=_isolated will match any isolated service UID.
    # All specified input selectors in an entry must match (i.e. logical AND).
    # Matching is case-insensitive.
    # Precedence rules:
    #     (1) isSystemServer=true before isSystemServer=false.
    #     (2) Specified user= string before unspecified user= string.
    #     (3) Fixed user= string before user= prefix (i.e. ending in *).
    #     (4) Longer user= prefix before shorter user= prefix. 
    #     (5) Specified seinfo= string before unspecified seinfo= string.
    #     (6) Specified name= string before unspecified name= string.
    #     (7) Specified sebool= string before unspecified sebool= string.
    #
    # Outputs:
    #   domain (string)
    #   type (string)
    #   levelFrom (string; one of none, all, app, or user)
    #   level (string)
    # Only entries that specify domain= will be used for app process labeling.
    # Only entries that specify type= will be used for app directory labeling.
    # levelFrom=user is only supported for _app or _isolated UIDs.
    # levelFrom=app or levelFrom=all is only supported for _app UIDs.
    # level may be used to specify a fixed level for any UID. 
    #
    isSystemServer=true domain=system
    user=system domain=system_app type=system_data_file
    user=bluetooth domain=bluetooth type=bluetooth_data_file
    user=nfc domain=nfc type=nfc_data_file
    user=radio domain=radio type=radio_data_file
    user=_app domain=untrusted_app type=app_data_file levelFrom=none
    user=_app seinfo=platform domain=platform_app type=platform_app_data_file
    user=_app seinfo=shared domain=shared_app type=platform_app_data_file
    user=_app seinfo=media domain=media_app type=platform_app_data_file
    user=_app seinfo=release domain=release_app type=platform_app_data_file
    user=_isolated domain=isolated_app

           文件中的注释解释了如何在文件seapp_contexts查找对象的Type,这里不再累述,只是举两个例子来说明。

           从前面的分析可知,对于使用平台签名的App来说,它的seinfo为“platform”。用户空间的Security Server在为它查找对应的Type时,使用的user输入为"_app"。这样在seapp_contexts文件中,与它匹配的一行即为:

    user=_app seinfo=platform domain=platform_app type=platform_app_data_file

           这样我们就可以知道,使用平台签名的App所运行在的进程domain为“platform_app”,并且它的数据文件的file_type为“platform_app_data_file”。

           又如,使用第三方签名的App的seinfo为“default”。用户空间的Security Server在为它查找对应的Type时,使用的user输入也为"_app"。我们注意到,在seapp_contexts文件中,没有一行对应的user和seinfo分别为“_app”和“default”。但是有一行是最匹配的,即:

    user=_app domain=untrusted_app type=app_data_file levelFrom=none
           这样我们就可以知道,使用第三方签名的App所运行在的进程domain为“unstrusted_app”,并且它的数据文件的file_type为“app_data_file”。

           接下来我们再来看系统文件的安全上下文是如何定义的。通过查看external/sepolicy/file_contexts文件,我们就可以看到系统文件的安全上下文描述,如下所示:

    ###########################################
    # Root
    /           u:object_r:rootfs:s0
    
    # Data files
    /adb_keys       u:object_r:rootfs:s0
    /default.prop       u:object_r:rootfs:s0
    /fstab\..*      u:object_r:rootfs:s0
    /init\..*       u:object_r:rootfs:s0
    /res(/.*)?      u:object_r:rootfs:s0
    /ueventd\..*        u:object_r:rootfs:s0
    
    # Executables
    /charger        u:object_r:rootfs:s0
    /init           u:object_r:rootfs:s0
    /sbin(/.*)?     u:object_r:rootfs:s0
    
    ......
    
    #############################
    # System files
    #
    /system(/.*)?       u:object_r:system_file:s0
    /system/bin/ash     u:object_r:shell_exec:s0
    /system/bin/mksh    u:object_r:shell_exec:s0
    
    ......
           文件file_contexts通过正则表达式来描述系统文件的安全上下文。例如,在上面列出的内容的最后三行中,倒数第三行的正则表达式表示在/system目录下的所有文件的安全上下文均为“u:object_r:system_file:s0”,最后两行的正则表达式则表示文件/system/bin/ash和/system/bin/mksh的安全上下文应为“u:object_r:shell_exec:s0”。虽然倒数第三行的正则表达式描述的文件涵盖后面两个正则表达示描述的文件,但是后面两个正则表达式描述的方式更加具体,因此/system/bin/ash和/system/bin/mksh两个文件的最终安全上下文都被设置为“u:object_r:shell_exec:s0”。

            在Android系统中,有一种特殊的资源——属性,App通过读写它们能够获得相应的信息,以及控制系统的行为,因此,SEAndroid也需要对它们进行保护。这意味着Android系统的属性也需要关联有安全上下文。这是通过文件external/sepolicy/property_contexts来描述的,它的内容如下所示:

    ##########################
    # property service keys
    #
    #
    net.rmnet0              u:object_r:radio_prop:s0
    net.gprs                u:object_r:radio_prop:s0
    net.ppp                 u:object_r:radio_prop:s0
    net.qmi                 u:object_r:radio_prop:s0
    net.lte                 u:object_r:radio_prop:s0
    net.cdma                u:object_r:radio_prop:s0
    gsm.                    u:object_r:radio_prop:s0
    persist.radio           u:object_r:radio_prop:s0
    net.dns                 u:object_r:radio_prop:s0
    sys.usb.config          u:object_r:radio_prop:s0
    ......
           属性的安全上下文与文件的安全上下文是类似的,它们的SELinux用户、SELinux角色和安全级别均定义为u、object_r和s0。从上面列出的内容可以看出,以net.开头的几个属性,以及所有以gsm.开头的属性、persist.radio和sys.usb.config属性的安全上下文均被设置为”u:object_r:radio_prop:s0“。这意味着只有有权限访问Type为radio_prop的资源的进程才可以访问这些属性。

           2. 安全策略

           上面我们分析了SEAndroid安全机制中的对象安全上下文,接下来我们就继续分析SEAndroid安全机制中的安全策略。SEAndroid安全机制中的安全策略是在安全上下文的基础上进行描述的,也就是说,它通过主体和客体的安全上下文,定义主体是否有权限访问客体。

           前面提到,SEAndroid安全机制主要是使用对象安全上下文中的类型来定义安全策略,这种安全策略就称Type Enforcement,简称TE。在external/sepolicy目录中,所有以.te为后缀的文件经过编译之后,就会生成一个sepolicy文件。这个sepolicy文件会打包在ROM中,并且保存在设备上的根目录下,即它在设备上的路径为/sepolicy。

           接下来,我们就通过app.te文件的内容来分析SEAndroid安全机制为使使用平台签名的App所定义的安全策略,相关的内容如下所示:

    #
    # Apps signed with the platform key.
    #
    type platform_app, domain;
    permissive platform_app;
    app_domain(platform_app)
    platform_app_domain(platform_app)
    # Access the network.
    net_domain(platform_app)
    # Access bluetooth.
    bluetooth_domain(platform_app)
    unconfined_domain(platform_app)
    ......
           前面在分析seapp_contexts文件的时候,我们提到,使用平台签名的App所运行在的进程的domain指定为"platform_app"。从上面列出的内容可以看出,platform_app接下来会通过app_domain、platform_app_domain、net_domain、bluetooth_domain和unconfined_domain宏分别加入到其它的domain中去,以便可以获得相应的权限。接下来我们就以unconfined_domain宏为例,分析platform_app获得了哪些权限。 

           宏unconfined_domain定义在文件te_macros文件中,如下所示:

    ......
    
    #####################################
    # unconfined_domain(domain)
    # Allow the specified domain to do anything.
    #
    define(`unconfined_domain', `
    typeattribute $1 mlstrustedsubject;
    typeattribute $1 unconfineddomain;
    ')
    
    ......
           $1引用的就是unconfined_domain的参数,即platform_app。通过接下来的两个typeattribute语句,为platform_app设置了mlstrustedsubject和unconfineddomain两个属性。也就是说,mlstrustedsubject和unconfineddomain这两个Type具有权限,platform_app这个Type也具有。接下来我们主要分析unconfineddomain这个Type具有哪些权限。

           文件unconfined.te定义了unconfineddomain这个Type所具有的权限,如下所示:

    allow unconfineddomain self:capability_class_set *;
    allow unconfineddomain kernel:security *;
    allow unconfineddomain kernel:system *;
    allow unconfineddomain self:memprotect *;
    allow unconfineddomain domain:process *;
    allow unconfineddomain domain:fd *;
    allow unconfineddomain domain:dir r_dir_perms;
    allow unconfineddomain domain:lnk_file r_file_perms;
    allow unconfineddomain domain:{ fifo_file file } rw_file_perms;
    allow unconfineddomain domain:socket_class_set *;
    allow unconfineddomain domain:ipc_class_set *;
    allow unconfineddomain domain:key *;
    allow unconfineddomain fs_type:filesystem *;
    allow unconfineddomain {fs_type dev_type file_type}:{ dir blk_file lnk_file sock_file fifo_file } *;
    allow unconfineddomain {fs_type dev_type file_type}:{ chr_file file } ~entrypoint;
    allow unconfineddomain node_type:node *;
    allow unconfineddomain node_type:{ tcp_socket udp_socket rawip_socket } node_bind;
    allow unconfineddomain netif_type:netif *;
    allow unconfineddomain port_type:socket_class_set name_bind;
    allow unconfineddomain port_type:{ tcp_socket dccp_socket } name_connect;
    allow unconfineddomain domain:peer recv;
    allow unconfineddomain domain:binder { call transfer set_context_mgr };
    allow unconfineddomain property_type:property_service set;
            一个Type所具有的权限是通过allow语句来描述的,以下这个allow语句:

    allow unconfineddomain domain:binder { call transfer set_context_mgr };
            表明domain为unconfineddomain的进程可以与其它进程进行binder ipc通信(call),并且能够向这些进程传递Binder对象(transfer),以及将自己设置为Binder上下文管理器(set_context_mgr)。

            注意,SEAndroid使用的是最小权限原则,也就是说,只有通过allow语句声明的权限才是允许的,而其它没有通过allow语句声明的权限都是禁止,这样就可以最大限度地保护系统中的资源。

            如果我们继续分析app.te的内容,会发现使用第三方签名的App所运行在的进程同样是加入到unconfineddomain这个domain的,如下所示:

    #
    # Untrusted apps.
    #
    type untrusted_app, domain;
    permissive untrusted_app;
    app_domain(untrusted_app)
    net_domain(untrusted_app)
    bluetooth_domain(untrusted_app)
    unconfined_domain(untrusted_app)
            这是不是意味着使用平台签名和第三方签名的App所具有的权限都是一样的呢?答案是否定的。虽然使用平台签名和第三方签名的App在SEAndroid安全框架的约束下都具有unconfineddomain这个domain所赋予的权限,但是别忘记,在进行SEAndroid安全检查之前,使用平台签名和第三方签名的App首先要通过DAC检查,也就是要通过传统的Linux UID/GID安全检查。由于使用平台签名和第三方签名的App在安装的时候分配到的Linux UID/GID是不一样的,因此就决定了它们所具有权限是不一样的。

            同时,这里使用平台签名和第三方签名的App之所以会同时被赋予unconfineddomain这个domain的权限,是因为前面我们分析的app.te文件是来自于Android 4.3的。在Android 4.3中,SEAndroid安全机制是试验性质的,并且启用的是Permissive模式,也就是即使主体违反了安全策略,也只是会发出警告,而不会真的拒绝执行。如果我们分析的是Android 4.4的app.te文件,就会发现,使用第三方签名的App不再具有大部分unconfineddomain这个domain的权限,因为Android 4.4的SEAndroid安全机制不再是试验性质的,并且启用的是Enforcing模式。

           以上描述的就是基于TE的安全策略,它的核心思想就是最小权限原则,即主体对客体拥有的权限必须要通过allow语句定义才允许,否则的话,一切都是禁止的。

           前面我们还提到,SEAndroid安全机制的安全策略经过编译后会得到一个sepolicy文件,并且最终保存在设备上的根据目录下。注意,如果我们什么也不做,那么保存在这个sepolicy文件中的安全策略是不会自动加载到内核空间的SELinux LSM模块去的。它需要我们在系统启动的过程中进行加载。

            系统中第一个启动的进程是init进程。我们知道,Init进程在启动的过程中,执行了很多的系统初始化工作,其中就包括加载SEAndroid安全策略的工作,如下所示:

    int main(int argc, char **argv)
    {
        ......
    
        union selinux_callback cb;
        cb.func_log = klog_write;
        selinux_set_callback(SELINUX_CB_LOG, cb);
    
        cb.func_audit = audit_callback;
        selinux_set_callback(SELINUX_CB_AUDIT, cb);
    
        INFO("loading selinux policy\n");
        if (selinux_enabled) {
            if (selinux_android_load_policy() < 0) {
                selinux_enabled = 0;
                INFO("SELinux: Disabled due to failed policy load\n");
            } else {
                selinux_init_all_handles();
            }
        } else {
            INFO("SELinux:  Disabled by command line option\n");
        }
    
        ......
    }
            上述代码定义在文件system/core/init/init.c中。

            这里调用到了三个与SEAndroid相关的函数:selinux_set_callback、selinux_android_load_policy和selinux_init_all_handles,其中,selinux_set_callback和selinux_android_load_policy来自于libselinux,而selinux_init_all_handles也是定义在文件system/core/init/init.c中,并且它最终也是通过调用libselinux的函数来打开前面分析file_contexts和property_contexts文件,以便可以用来查询系统文件和系统属性的安全上下文。

            函数selinux_set_callback用来向libselinux设置SEAndroid日志和审计回调函数,而函数selinux_android_load_policy则是用来加载安全策略到内核空间的SELinux LSM模块中去。我们重点关注函数selinux_android_load_policy的实现。

            函数selinux_android_load_policy定义在文件external/libselinux/src/android.c,它的实现如下所示:

    int selinux_android_load_policy(void)
    {
        char *mnt = SELINUXMNT;
        int rc;
        rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
        if (rc < 0) {
            if (errno == ENODEV) {
                /* SELinux not enabled in kernel */
                return -1;
            }
            if (errno == ENOENT) {
                /* Fall back to legacy mountpoint. */
                mnt = OLDSELINUXMNT;
                rc = mkdir(mnt, 0755);
                if (rc == -1 && errno != EEXIST) {
                    selinux_log(SELINUX_ERROR,"SELinux:  Could not mkdir:  %s\n",
                        strerror(errno));
                    return -1;
                }
                rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
            }
        }
        if (rc < 0) {
            selinux_log(SELINUX_ERROR,"SELinux:  Could not mount selinuxfs:  %s\n",
                    strerror(errno));
            return -1;
        }
        set_selinuxmnt(mnt);
    
        return selinux_android_reload_policy();
    }
             SELINUXMNT、OLDSELINUXMNT和SELINUXFS是三个宏,它们定义在文件external/libselinux/src/policy.h文件中,如下所示:

    /* Preferred selinuxfs mount point directory paths. */
    #define SELINUXMNT "/sys/fs/selinux"
    #define OLDSELINUXMNT "/selinux"
    
    /* selinuxfs filesystem type string. */
    #define SELINUXFS "selinuxfs"
            回到函数selinux_android_load_policy中,我们不难发现它的实现逻辑如下所示:

            A. 以/sys/fs/selinux为安装点,安装一个类型为selinuxfs的文件系统,也就是SELinux文件系统,用来与内核空间的SELinux LSM模块通信。

            B. 如果不能在/sys/fs/selinux这个安装点安装SELinux文件系统,那么再以/selinux为安装点,安装SELinux文件系统。

            C. 成功安装SELinux文件系统之后,接下来就调用另外一个函数selinux_android_reload_policy来将SEAndroid安全策略加载到内核空间的SELinux LSM模块中去。

            在较旧版本的Linux系统中,SELinux文件系统是以/selinux为安装点的,不过后面较新的版本都是以/sys/fs/selinux为安装点的,Android系统使用的是后者。

            函数selinux_android_reload_policy也是定义在文件external/libselinux/src/android.c中,它的实现如下所示:

    static const char *const sepolicy_file[] = {
            "/data/security/current/sepolicy",
            "/sepolicy",
            0 };
    
    ......
    
    int selinux_android_reload_policy(void)
    {
        int fd = -1, rc;
        struct stat sb;
        void *map = NULL;
        int i = 0;
    
        while (fd < 0 && sepolicy_file[i]) {
            fd = open(sepolicy_file[i], O_RDONLY | O_NOFOLLOW);
            i++;
        }
        if (fd < 0) {
            selinux_log(SELINUX_ERROR, "SELinux:  Could not open sepolicy:  %s\n",
                    strerror(errno));
            return -1;
        }
        if (fstat(fd, &sb) < 0) {
            selinux_log(SELINUX_ERROR, "SELinux:  Could not stat %s:  %s\n",
                    sepolicy_file[i], strerror(errno));
            close(fd);
            return -1;
        }
        map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
        if (map == MAP_FAILED) {
            selinux_log(SELINUX_ERROR, "SELinux:  Could not map %s:  %s\n",
                sepolicy_file[i], strerror(errno));
            close(fd);
            return -1;
        }
    
        rc = security_load_policy(map, sb.st_size);
        if (rc < 0) {
            selinux_log(SELINUX_ERROR, "SELinux:  Could not load policy:  %s\n",
                strerror(errno));
            munmap(map, sb.st_size);
            close(fd);
            return -1;
        }
    
        munmap(map, sb.st_size);
        close(fd);
        selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", sepolicy_file[i]);
    
        return 0;
    }
             函数selinux_android_reload_policy的执行过程如下所示:

             A. 依次从/data/security/current和根目录寻找sepolicy文件,找到之后就打开,获得一个文件描述符fd。

             B. 通过文件描述符fd将前面打开的sepolicy文件的内容映射到内存中来,并且得到它的起始地址为map。

             C. 调用另外一个函数security_load_policy将已经映射到内存中的sepolicy文件内容,即SEAndroid安全策略,加载到内核空间的SELinux LSM模块中去。

             D. 加载完成后,释放sepolicy文件占用的内存,并且关闭sepolicy文件。

             函数security_load_policy定义在文件external/libselinux/src/load_policy.c中,它的实现如下所示:

    int security_load_policy(void *data, size_t len)
    {
        char path[PATH_MAX];
        int fd, ret;
    
        if (!selinux_mnt) {N
            errno = ENOENT;
            return -1;
        }
    
        snprintf(path, sizeof path, "%s/load", selinux_mnt);
        fd = open(path, O_RDWR);
        if (fd < 0)
            return -1;
    
        ret = write(fd, data, len);
        close(fd);
        if (ret < 0)
            return -1;
        return 0;
    }
             selinux_mnt是一个全局变量,它描述的是SELinux文件系统的安装点。在我们这个情景中,它的值就等于/sys/fs/selinux。

             函数security_load_policy的实现很简单,它首先打开/sys/fs/selinux/load文件,然后将参数data所描述的安全策略写入到这个文件中去。由于/sys/fs/selinux是由内核空间的SELinux LSM模块导出来的文件系统接口,因此当我们将安全策略写入到位于该文件系统中的load文件时,就相当于是将安全策略从用户空间加载到SELinux LSM模块中去了。以后SELinux LSM模块中的Security Server就可以通过它来进行安全检查。

             3. Security Server

             用户空间的Security Server主要是用来保护用户空间资源的,以及用来操作内核空间对象的安全上下文的,它由应用程序安装服务PackageManagerService、应用程序安装守护进程installd、应用程序进程孵化器Zygote进程以及init进程组成。其中,PackageManagerService和installd负责创建App数据目录的安全上下文,Zygote进程负责创建App进程的安全上下文,而init进程负责控制系统属性的安全访问。

             应用程序安装服务PackageManagerService在启动的时候,会在/etc/security目录中找到我们前面分析的mac_permissions.xml文件,然后对它进行解析,得到App签名或者包名与seinfo的对应关系。当PackageManagerService安装App的时候,它就会根据其签名或者包名查找到对应的seinfo,并且将这个seinfo传递给另外一个守护进程installed。

             守护进程installd负责创建App数据目录。在创建App数据目录的时候,需要给它设置安全上下文,使得SEAndroid安全机制可以对它进行安全访问控制。Installd根据PackageManagerService传递过来的seinfo,并且调用libselinux库提供的selabel_lookup函数到前面我们分析的seapp_contexts文件中查找到对应的Type。有了这个Type之后,installd就可以给正在安装的App的数据目录设置安全上下文了,这是通过调用libselinux库提供的lsetfilecon函数来实现的。

            从前面Android应用程序进程启动过程的源代码分析Android系统进程Zygote启动过程的源代码分析这两篇文章可以知道,在Android系统中,Zygote进程负责创建应用程序进程。应用程序进程是SEAndroid安全机制中的主体,因此它们也需要设置安全上下文,这是由Zygote进程来设置的。组件管理服务ActivityManagerService在请求Zygote进程创建应用程序进程之前,会到PackageManagerService中去查询对应的seinfo,并且将这个seinfo传递到Zygote进程。于是,Zygote进程在fork一个应用程序进程之后,就会使用ActivityManagerService传递过来的seinfo,并且调用libselinux库提供的selabel_lookup函数到前面我们分析的seapp_contexts文件中查找到对应的Domain。有了这个Domain之后,Zygote进程就可以给刚才创建的应用程序进程设置安全上下文了,这是通过调用libselinux库提供的lsetcon函数来实现的。

            前面提到,在Android系统中,属性也是一项需要保护的资源。Init进程在启动的时候,会创建一块内存区域来维护系统中的属性,接着还会创建一个Property服务。这个Property服务通过socket提供接口给其它进程访问Android系统中的属性。其它进程通过socket来和Property服务通信时,Property服务可以获得它的安全上下文。有了这个安全上下文之后,Property服务就可以通过libselinux库提供的selabel_lookup函数到前面我们分析的property_contexts去查找要访问的属性的安全上下文了。有了这两个安全上下文之后,Property服务就可以决定是否允许一个进程访问它所指定的属性了。

            至此,我们就分析完成SEAndroid安全机制的整体框架了。有了这些基础知识之后,接下来我们就可以更加深入地去分析一些具体的使用情景了。例如,我们前面介绍的用户空间的Security Server是如何一步一步地设置应用程序数据目录和应用程序进程的安全上下文的,以及Init进程是如何控制系统中的属性访问的,敬请关注!更多信息可以关注老罗的新浪微博:http://weibo.com/shengyangluo

    展开全文
  • shiro安全框架

    万次阅读 2012-11-28 10:28:03
    Shiro 是 JAVA 世界中新近出现的权限框架,较之 JAAS 和 Spring Security,Shiro 在保持强大功能的同时,还在简单性和灵活性...在 2009 年 3 月初之前,这个安全框架叫做 J-security,由于某些原因,更名为 Shiro(或
  • 【转载】Web前端框架图

    千次阅读 2017-06-15 17:54:08
    今天学堂君在整理相关web前端开发自学知识的时候,发现了一些牛逼的web前端开发工程师已经帮我们整理出来非常方便收藏和学习的知识框架图。非常的实用!无论你是web前端工程师 还是打算进入或者转型web前端开发行业...
  • 操作系统内核框架图整理

    千次阅读 2013-06-22 23:40:26
    Android框架: IOS框架(简): web_os框架: windows NT框架: Minix3框架: linux框架(简): Solaris框架: GNU_Linux_FOSS: linux框架(完整): 不管阅读哪一个系统...
  • Openstack管理框架分析以及创建

    千次阅读 2016-04-29 15:34:41
    Openstack管理框架分析及创建(domain、project、service、...:openstack管理框架 1、》》》创建域(域是登陆的时候指定的): Domains:表示一组projects和users的集合。 每一个project或user只能属于一个do
  • JAVA安全控制框架 —— Shiro

    千次阅读 2016-04-05 16:25:55
    查看原文:http://ibloger.net/article/126.htmlApache Shiro官网:http://shiro.apache.org/什么是Shiro... 一个强大且易用的轻量级Java安全框架, 执行身份验证(Authentication)、(Authorization)授权、(Cryptography)
  • MobSF移动安全检测框架简述

    千次阅读 2017-03-24 14:08:04
    移动安全分析框架 (MobSF) 是一个智能的、集成型的、开源移动App(安卓/iOS)自动检测框架,能用于静态检测和动态检测。该框架可以进行高效迅速的移动应用安全分析。文章将介绍MobSF的使用指南和检测项目,并对框架...
  • 安全认证框架Shiro (二)- shiro过滤器工作原理

    万次阅读 多人点赞 2017-11-15 17:27:48
    安全认证框架Shiro (二)- shiro过滤器工作原理 安全认证框架Shiro 二- shiro过滤器工作原理 第一前言 第二ShiroFilterFactoryBean入口 第三请求到来解析过程 第四过滤器执行原理 第五总结第一:前言由于工作原因,...
  • 文章目录前言Sentry和Ranger的概述 前言 上篇文章后半部分提到了业界流行的...熟悉掌握使用外部权限管理框架,并且将它们合理地应用于自身内部大数据组件系统内,无疑将会大大提高内部组件使用的安全性。 Sen...
  • 网络信息安全知识框架

    万次阅读 多人点赞 2018-12-30 22:10:32
    如需要下列知识的详细内容,欢迎评论或私信联系我。 第0章 基础概述 1.网络信息安全的原则(包括...1.1 网络安全的概念 (信息安全的4个层面、信息安全所涉及学科、研究信息安全的重要性、网络安全的概念) 1.2 ...
  • java 权限管理框架

    千次阅读 2015-08-10 18:44:38
    它能够轻松处理登录控制、URL权限控制和(业务级)数据级权限管理,实现权限与业务分离。Ralasafe是一款开箱即用的中间件,XML配置和JAVA编程工作量非常少,基本都使用图形化操作方式。非常简单易用,开发经验不丰富...
  • 基于Struts2框架的名片管理系统

    千次阅读 2020-10-02 13:55:45
    本篇博文将分享一款基于Struts2框架的名片管理系统,JSP引擎为Tomcat9.0,数据库采用的是MySQL5.5,集成开发环境为Eclipse IDE for Java EE Developers。 名片管理系统主要包括用户管理和名片管理功能。用户功能...
  • Shiro权限管理框架详解

    千次阅读 2020-02-07 21:55:48
    基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。 权限管理包括用户身份...
  • ,那么要么我们自己用API做事,要么Spring帮我们管理都很方便。   6.断点分析 我们以如下的配置文件为便说明怎么解析的: [main] jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm dataSource=...
  • SNMP管理框架详解

    千次阅读 2008-04-28 15:28:00
    1. 了解SNMP管理框架SNMP(Simple Network Management Protocol,简单网络管理协议)是一个应用层协议,提供了SNMP管理者和SNMP代理间报文格式的消息通信。它规定了在网络环境中对设备进行监视和管理
  • SSM+Redis结构框架图及概述

    千次阅读 2018-01-13 22:50:39
    最近在学习SSM+Redis框架,所以今天根据这个学期学到的知识先对框架做一个总结,先有个概念。...1、Spring IoC(Inversion of Control,控制反转)承担了一个资源管理、整合、即插即拔的功能。举个例
  • Shiro安全框架入门使用方法

    万次阅读 2017-08-15 20:18:22
    框架介绍Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任 何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。...
  • SpringBoot集成Shiro安全框架

    千次阅读 多人点赞 2019-06-14 17:50:32
    SpringBoot集成Shiro安全框架欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中...
  • 因为其牵涉到版本很多,笔者就以最新的WMF 5.0 正式版和WMF 5.1 公测版为例子,说明其到底包含了什么.Window管理框架WMF(Window Management Framework) 5.0 和 Window管理框架WMF(Window Management Framework) 5.1...
  • 3. 掌握基于url的权限管理(不使用Shiro权限框架的情况下实现权限管理) 4. shiro实现用户认证 5. shiro实现用户授权 6. shiro与企业web项目整合开发的方法 权限管理原理知识 什么是权限管理 只要有用户参与的系统一般...
  • Flask(python web框架)安全

    千次阅读 2021-03-04 15:34:45
    Flask 使用 ORM 对象关系映射的数据库管理方式,同时 Flask 框架内部也封装了许多安全性的函数,对于 SQL 注入拥有一定防护力,如 Flask 中单引号会自动进行转义 XSS: Flask 使用 Jinja2 模板引擎,Jinja 会默认...
  • 第四章 Android 框架安全 来源:Yury Zhauniarovich | Publications 译者:飞龙 协议:CC BY-NC-SA 4.0 如我们在第1.2节中所描述的那样,应用程序框架级别上的安全性由 IPC 引用监视器实现。 在 4.1 节中,...
  • [web安全] Web框架安全

    千次阅读 2015-06-25 22:49:35
    一、MVC框架安全 MVC是Model-View-Controller的缩写,它将Web应用分为三层,View层负责用户试图、页面展示等工作;Controller负责应用的逻辑实现,接收View层传入的用户请求,并转发给对应的Model做处理;Model层则
  • Android 安全框架 -- 总概

    千次阅读 2017-02-13 10:29:50
    先吐槽一下,那些说Android不安全的人(早期版本的确存在一些问题,比如APP权限管理太粗放,这个我会在后面的文章进行讲解),到底有没有认真了解过它的安全架构,它从低至上的安全机制难道不让人心动? 既然Android...
  • FRESCO:SDN安全控制器框架

    千次阅读 2014-09-26 16:20:13
    FRESCO:SDN安全控制器框架 一、前言 SDN(软件定义网络)把网络的控制层与数据层进行分离,它给网络定制提供了完全的灵活性。通过对网络中所有网络设备的集中控制,我们发现它不仅消除了设备配置带来的繁琐工,还让...
  • shiro(java安全框架

    万次阅读 2018-10-21 23:07:52
    以下都是综合之前的人加上自己的一些小总结Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 241,056
精华内容 96,422
关键字:

安全管理框架图