`
冷静
  • 浏览: 146927 次
  • 性别: Icon_minigender_1
  • 来自: 佛山
社区版块
存档分类
最新评论

基于 拦截器实现细粒度的基于角色的存取控制

    博客分类:
  • Java
阅读更多

文章转自:http://www.ibm.com/developerworks/cn/java/j-lo-struts2-rbac/

引言

 

Apache Struts 作为最成功的 MVC Web 框架早已得到了广泛的应用,但是其自身也暴露出不少缺点,从而引出了 Struts 2 。 Struts 2 摒弃了原来 Struts 1 的设计, 而是转向了 webwork2,并结合 Struts 已有的优点,试图打造出一个集众家所长的完美 Web 框架。 Struts 2 因此也具备 webwork2 中的一个非常重要的特性 - 拦截器 (Interceptor) 。拦截器会在 Action 执行之前和之后被执行(如下图),是一种典型 AOP 实现。


图 1. Struts 2 的体系结构

Struts 2 本身提供了一个 org.apache.struts2.interceptor.RolesInterceptor 拦截器以方便开发人员来实现存取控制。但该拦截器的实现是建立在 J2EE 容器提供的存取控制机制之上的。容器提供的存取控制实现粒度较粗,往往无法满足多数应用的需求。在许多项目中,用户所应该具有的权限是由多种因素而决定,往往在不同的上下文中拥有不同的角色。例如在一个社交项目中,一个用户会在不同的社团里拥有不同的角色,如成员,管理员,来宾等。他的具体角色取决于当前所处社团的标识符。另外,用户的角色还和他所要操作的资源类型有关。比如,在这个社交站点中,用户可以创建自己的日程表,把这个日程表共享给其他用户或者委托给其他人管理。这样对日程表这种类型资源,就会有创建者,阅览者和管理者三种角色。在更复杂应用中,用户的角色可能还会受更多因素决定,这就要求存取控制要有更细的粒度,能够处理更加复杂的逻辑。

为了满足这个需求,在基于 Struts 2 的 Web 应用开发中,我们也可以利用拦截器来实现一个应用托管的基于角色的存取控制(RBAC, Role-Based Access Control)系统, 让其能够管理更细粒度的资源。该系统在 Struts 2 的配置文件中定义 Action 可以由那些角色来调用,即对角色进行授权。拦截器在 Action 调用之前,对当前用户进行权限认证来决定 Action 是否应该被执行。

下面我们就基于 Hibernate+Spring+Struts2 框架来完成这个系统的实现。为了使系统结构更加清晰易于维护,我们将这个系统分为域模型层、持久层和服务层来实现。这种分层结构是目前 Web 开发广为使用的一种模式。

模型层实现

这系统中我们只需要一个实体 UserRole, 用来定义用户在不同的上下文中所具有的角色。在清单中,我们使用了 Java Persistence API (Hibernate 从 3.2 开始已经开始支持 JPA)中提供的 JDK 5.0 注解来对模型到数据库表之间的映射进行定义。


清单 1.

				
 @Entity 
 public class UserRole { 
  private Long id; 
  private User user; 
  private String objectType; 
  private Long objectId; 
  private String role; 
  public UserRole(Long userId, String role, String objectType, Long objectId) { 
    User user = new User(); 
    user.setId(userId); 
    this.user = user; 
    this.role = role; 
    this.objectType = objectType; 
    this.objectId = objectId; 
  } 
  @Id 
  @GeneratedValue(strategy = GenerationType.AUTO) 
  public Long getId() { 
    return id; 
  } 
  public void setId(Long id) { 
    this.id = id; 
  } 
  @ManyToOne 
  @JoinColumn(name = "userId", nullable = false) 
  public User getUser() { 
    return user; 
  } 
  public void setUser(User user) { 
    this.user = user; 
  } 
  public String getObjectType() { 
    return objectType; 
  } 
  public void setObjectType(String objectType) { 
    this.objectType = objectType; 
  } 
  public Long getObjectId() { 
    return objectId; 
  } 
  public void setObjectId(Long objectId) { 
    this.objectId = objectId; 
  } 
  public String getRole() { 
    return role; 
  } 
  public void setRole(String role) { 
    this.role = role; 
  } 
 }

 

注意这里边有两个比较特殊的字段 objectTypeobjectId,它们用来表明用户在具体哪个资源上拥有的角色。 objectType 指资源的类型,objectId 指资源的标识。比如我们要将用户 Mike 加为某个日程表的管理员,则表中新增记录的 user 字段为 Mike 在 user 表中的 ID,objectType 为“calendar”,objectID 为这个日程表 ID,role 为角色的名字“admin”。当然,如果您的应用中不同类型资源都使用唯一的全局 ID,objectType 这个字段也可以省略。

DAO 层实现

代码清单 2 定义了对 UserRole 进行 CRUD 的 DAO 接口,代码清单 3 则是它的实现。通过 @PersistenceContext 注解来让容器注入 JPA 中的实体管理器 EntityManager 。 UserRoleDaoImpl 调用 EntityManager 来对 UserRole 进行持久化到数据库中。


清单 2

				
public interface UserRoleDao { 
 public void create(UserRole userRole); 
 public void update(UserRole userRole); 
 public UserRole find(Long userId, String objectType, Long objectId); 
 }



清单 3

				
public class UserRoleDaoImpl implements UserRoleDao { 
  private EntityManager entityManager; 
  public EntityManager getEntityManager() { 
    return entityManager; 
  } 

  @PersistenceContext 
  public void setEntityManager(EntityManager entityManager) { 
    this.entityManager = entityManager; 
  } 

  public void create(UserRole userRole) { 
    entityManager.persist(userRole); 
  } 

  public UserRole find(Long userId, String objectType, Long objectId) { 
    Query query = entityManager.createQuery(
      "FROM UserRole ur WHERE ur.user.id=" + 
      userId + 
      " AND ur.objectType='" + 
      objectType + 
      "' AND ur.objectId=" + 
      objectId); 
    List result = query.getResultList(); 
    if (result.size() == 0) 
      return null; 
    return (UserRole)result.get(0); 
  } 

  public void update(UserRole userRole) { 
    entityManager.merge(userRole); 
  } 
 }

 

服务层实现

创建一个 RoleService 接口 (清单 4) 作为 façade, 清单 5 是具体实现。 RoleServiceImpl 的实现很简单,主要是封装了为用户分配角色和查询用户角色。注解 Transactional 用来将方法放置在一个事务中进行。在类声明上的 @Transactional(readOnly = true) 表示默认的事务为只读。 setUserRole 方法需要写入数据到数据库中,因此我们将其 readOnly 属性设置成 false.


清单 4

				
public interface RoleService { 
    public void setUserRole(Long userId, String role, String objectType, Long objectId); 
    public String findRole(Long userId, String objectType, Long objectId); 
 }



清单 5

				
@Transactional(readOnly = true) 
 public class RoleServiceImpl implements RoleService { 

 private UserRoleDao userRoleDao; 

 public void setUserRoleDao(UserRoleDao userRoleDao) { 
    this.userRoleDao = userRoleDao; 
 } 

  @Transactional(readOnly = false) 
  public void setUserRole(Long userId, String role, String objectType, Long objectId) { 
    UserRole userRole = new UserRole(userId, role, objectType, objectId); 
    UserRole userRoleInDB = userRoleDao.find(userId, objectType, objectId); 
    if (null == userRoleInDB) { 
      userRoleDao.create(userRole); 
    } else { 
      userRole.setId(userRoleInDB.getId()); 
      userRoleDao.update(userRole); 
    } 
  } 

  public String findRole(Long userId, String objectType, Long objectId) { 
    UserRole userRole = userRoleDao.find(userId, objectType, objectId); 
    if (userRole == null) { 
      return null; 
    } 
    return userRole.getRole(); 
  } 
 }

 

拦截器的实现

拦截器会在 Action 被执行之前被 Struts 2 框架所调用,我们利用这个特性来完成对用户身份的认证,只有用户具有正确角色方能执行 Action 。具体哪些角色可以执行 Action,需要在 Struts 2 的配置文件中指定,将在下一小节中详细阐述。这一点和 Struts 2 内置的 RolesInterceptor 类似,但我们的拦截器可以通过 objectTypeobjectId 来实现更加细粒度的认证。

要创建一个用于用户角色认证的拦截器。需要让其实现 com.opensymphony.xwork2.interceptor.Interceptor 接口并对 String intercept(ActionInvocation actionInvocation) throws Exception 方法进行实现。 如清单 6 。成员变量 roleService 是通过 Spring 的依赖注入被赋予 RoleServiceImpl 。 allowedRolesdisallowedRoles 分别存储了允许和不允许执行 Action 的角色,两者不能同时存在。 objectTypeobjectIdKey 分别表示资源的类型和资源 ID 在 HTTP 请求中的参数名。它们是做为 Interceptor 的参数在 Struts 2 配置文件中进行设置,会自动由 Struts 2 框架填充进来。


清单 6

				
public class RBACInterceptor implements Interceptor { 
   public static final String FORBIDDEN = "forbidden"; 
   private List<String> allowedRoles = new ArrayList<String>(); 
   private List<String> disallowedRoles = new ArrayList<String>(); 
   private RoleService roleService; 
   private String objectType; 
   private String objectIdKey;   
    
   public void setRoleService(RoleService roleService) { 
  this.roleService = roleService; 
   } 

   public void setObjectType(String objectType) { 
    this.objectType = objectType; 
  } 
  public void setObjectIdKey(String objectIdKey) { 
    this.objectIdKey = objectIdKey; 
  } 

  public void setAllowedRoles(String roles) { 
    if (roles != null) 
      allowedRoles = Arrays.asList(roles.split("[ ]*,[ ]*")); 
  } 

  public void setDisallowedRoles(String roles) { 
    if (roles != null) 
      disallowedRoles = Arrays.asList(roles.split("[ ]*,[ ]*")); 
  } 

  public void init() { 
  } 
    
  public void destroy() { 
  } 
    
  public String intercept(ActionInvocation actionInvocation) throws Exception { 
    HttpServletRequest request = ServletActionContext.getRequest(); 
    // Get object id 
    Long objectId = Long.valueOf(request.getParameter(objectIdKey)); 
    Map session = actionInvocation.getInvocationContext().getSession(); 
    // Get current user id 
    Long userId = (Long) session.get(Constant.KEY_CURRENT_USER); 
    // Get the user role 
    String userRole = roleService.findRole(userId, objectType, objectId); 

    if (!isAllowed(userRole)) { 
      // forbid invoking the action 
      return FORBIDDEN; 
    } else { 
      // allow invoking the action 
      return actionInvocation.invoke(); 
    } 
  } 

  // Check if the current user has correct role to invoke the action 
  protected boolean isAllowed(String userRole) { 
    if (allowedRoles.size() > 0) { 
      if (userRole == null) 
        return false; 
      return allowedRoles.contains(userRole); 
    } else if (disallowedRoles.size() > 0) { 
      if (userRole == null) 
        return true; 
      return !disallowedRoles.contains(userRole); 
    } 
    return true; 
  } 
 }

 

intercept 方法中我们根据当前用户的 ID,HTTP 请求参数中获得资源的 ID,所存取的资源类型来调用 RoleService 获得用户的角色。 然后再判断该角色是否在 allowedRolesdisallowedRoles 中来确定用户是否有权限调用 Action 。如果用户没权限,则将请求发送到名为“forbidden”的 result 。从这里可以看出,用户的角色验证与身份验证的作用完全不同。身份验证是验证用户是否网站注册用户,而角色认证是在用户为注册用户的前提下对用户相对于站内各种资源扮演的角色的辨别。

上面代码中用到了判断用户是否具有运行 Action 所要求的角色的函数 isAllowed()。它首先根据用户 ID 和 Action 作用于的对象的类型和 ID 从数据库查询到用户对应的角色,然后将用户角色与允许角色的列表逐个比较。如果允许角色列表包含用户实际角色则返回真,否则返回假;如果允许角色列表为空,则将用户角色与禁止角色的列表比较,如果用户的角色被禁止,则返回假,否则返回真。如果两个列表都为空,也返回真。这样既可以对某个 Action 配置允许访问角色列表,也可以配置拒绝访问列表。

使用

首先我需要在 Spring 的配置文件中添加系统中所涉及到各个 POJO,如清单 7 。


清单 7

				
<!-- Data Access Objects --> 
  <bean id="userRoleDao" class="com.sample.security.dao.impl.UserRoleDaoImpl"/> 
   
  <!-- Service Objects --> 
  <bean id="roleService" 
    class="com. sample.security.service.impl.RoleServiceImpl" > 
    <property name="userRoleDao" ref="userRoleDao" /> 
  </bean> 
  
  <!-- Interceptor Objects --> 
  <bean id="RBACInterceptor" scope="prototype" 
    class="com. sample.security.interceptor. RBACInterceptor "> 
    <property name="roleService"  ref="roleService" /> 
  </bean>

 

然后需要在 Struts 配置文件中对需要进行存取控制的 Action 进行配置。首先定义我们实现的拦截器,并把其加到拦截器栈中。在 <interceptors> …… </interceptors> 中添加下面的代码。

<interceptor name="RBAC ” class="RBACInterceptor" />

 

现在我们可以将 RBAC 拦截器添加到任意的 interceptor-stack 中,或者直接配置到任意的 Action 。添加下面清单中的内容到 Struts 2 配置文件中,将能够对在一个日程表中删除会议进行控制。


清单 8

				
<action name="deleteMeeting" class="com.demo.action.DeleteMeetingAction"> 
 <result>/WEB-INF/jsp/deleteMeetingResult.jsp</result> 
 <result name="forbidden">/WEB-INF/jsp/forbidden.jsp</result> 
    <interceptor-ref name="RBAC"> 
        <param name="allowedRoles">admin, owner</param> 
        <param name="objectType">calendar</param> 
 <param name="objectIdKey">id</param> 
 </interceptor-ref> 
 <interceptor-ref name="defaultStack" /> 
 </action>

 

至于用户角色的分配,我们可以定义一个 Action 通过 RoleService 来创建。如下面清单 9 的配置和清单 10 的代码实现了一个 Action 允许日程表的创建者来分配角色给其它人。


清单 9

				
<action name="assignCalendarRole" class="com.demo.action.AssignCalendarRoleAction"> 
 <result>/WEB-INF/jsp/deleteMeetingResult.jsp</result> 
 <result name="forbidden">/WEB-INF/jsp/forbidden.jsp</result> 
    <interceptor-ref name="RBAC"> 
        <param name="allowedRoles">owner</param> 
        <param name="objectType">calendar</param> 
 <param name="objectIdKey">id</param> 
 </interceptor-ref> 
 <interceptor-ref name="defaultStack" /> 
 </action>



清单 10

				
public class AssignCalendarRoleAction extends ActionSupport { 
  private RoleService roleService; 
  private Long userId = 0; 
  private String userRole = "reader"; 
 private Long id = 0; 

  public AssignCalendarRoleAction (RoleService roleService) { 
    this.roleService = roleService; 
  } 

 public String execute() { 
 roleService.setUserRole(userId, userRole, "calendar", id); 
 return SUCCESS; 
  } 
 }

 

结束语

本文介绍了如何在 Spring+Hibernate+Struts2 框架中实现一个应用托管的 RBAC 系统,不同于容器提供的 RBAC,它能够更加细粒度地对各种资源进行存取控制。这里的实现非常简单,还需要许多地方可以进行扩展和完善(比如对用户组的支持),希望能对读者起到抛砖引玉的作用。

 

分享到:
评论

相关推荐

    Struts2拦截器实现权限控制demo

    在这个“Struts2拦截器实现权限控制demo”中,我们将深入探讨如何利用拦截器来实现细粒度的用户权限管理。 首先,我们需要了解Struts2中的拦截器工作原理。拦截器是基于Java的动态代理模式实现的,它们按照配置的...

    struts2拦截器实现权限控制

    在Struts2中,拦截器扮演着至关重要的角色,它们是实现应用逻辑和业务规则的关键组件,尤其在权限控制方面。这篇博客文章“struts2拦截器实现权限控制”深入探讨了如何利用Struts2的拦截机制来执行用户访问权限的...

    springmvc +mybatis采用策略设计模式基于拦截器实现按年分表

    在本项目的分表策略中,拦截器可能会在请求到达控制器之前或之后,根据请求中的特定信息(如时间戳或日期范围)来确定应该使用的数据库表,从而实现动态路由。 5. **分表策略**: 分表是数据库优化的一种手段,...

    springboot拦截器实现拦截器 权限校验,登录demo

    通过拦截器,我们可以统一处理这些常见的安全问题,而不是在每个控制器方法中重复同样的代码。这个简单的设计使得我们的应用更加模块化,更易于维护。 最后,关于"springboot3"这个压缩包子文件的文件名称,可能指...

    java 拦截器实现

    这些拦截器可以用来实现如权限验证、日志记录、事务控制等多种功能。 5. **代码示例**: 创建一个简单的JDK动态代理拦截器: ```java interface MyService { void doSomething(); } class MyServiceImpl ...

    运用struts2技术的拦截器实现的页面检测和用户注册

    拦截器是基于Java的动态代理机制实现的,它们在Action调用之前和之后执行,形成一个拦截器链。开发者可以通过配置struts.xml或struts.properties文件来定义拦截器链和其顺序。 1. **创建拦截器** 在Struts2中,...

    struts2 拦截器实现登录控制

    在Struts2中,拦截器扮演着至关重要的角色,它们是实现业务逻辑、验证、日志、性能优化等核心功能的关键组件。这篇博客“struts2 拦截器实现登录控制”显然会探讨如何利用拦截器来实现用户登录验证,从而保护应用...

    权限控制之粗粒度与细粒度概念及实现简单介绍

    基于URL拦截的方式是实现权限控制的一种常用方式,例如通过filter过虑器实现URL拦截,也可以使用SpringMVC的拦截器实现基于URL的拦截。 最后,使用权限管理框架可以实现权限控制,例如使用Shiro框架实现粗粒度权限...

    基于ssh拦截器框架Struts2拦截器的登录验证实现

    拦截器是基于Java的动态代理实现的,它可以看作是一系列处理请求的中间层,这些中间层可以在动作执行前后插入额外的行为。拦截器的执行顺序由配置文件决定,使得开发者可以灵活地控制业务流程。 在登录验证实现中,...

    拦截器实现权限管理

    通过拦截请求,我们可以对用户的操作进行细粒度的控制,确保系统安全的同时,提供了良好的用户体验。然而,这种方案也有其局限性,如权限的静态性和无法实时响应权限变化。因此,在实际应用中,需要根据项目需求选择...

    struts2 用拦截器 实现用户权限登录

    在Struts2中,拦截器是实现业务逻辑和控制流程之间解耦的关键组件。本教程将详细介绍如何使用Struts2的拦截器来实现用户权限登录功能。 ### 一、拦截器的原理与作用 1. **原理**:拦截器工作在Action调用之前,它...

    基于Shiro 拦截URL,实现权限控制

    在实现URL权限控制时,Shiro通过定义一系列的拦截器(Interceptors)来过滤请求。这些拦截器可以基于URL路径或特定的HTTP方法(如GET、POST等)来决定是否允许用户访问。Shiro的Web模块提供了一个名为`...

    CXF WebService带有拦截器

    此外,结合Spring Security等框架,我们可以进一步实现细粒度的权限控制,比如基于角色的访问控制(RBAC)。 总结起来,"CXF WebService带有拦截器"的实践是Web服务开发中的一个重要方面,它允许我们在不侵入核心...

    Java SpringBoot实现的过滤器(和拦截器)控制登录页面跳转

    本项目主要关注的是如何使用过滤器(Filter)和拦截器(Interceptor)来实现登录页面的控制与跳转。以下是对这些知识点的详细说明: 1. **SpringBoot**: SpringBoot是Spring框架的一个子项目,旨在简化Spring应用...

    webwork 权限拦截器

    总的来说,WebWork的权限拦截器是实现细粒度权限控制的有效手段,它能够灵活地集成到现有系统中,提升系统的安全性和用户体验。通过自定义拦截器,开发者可以根据项目的具体需求实现各种复杂的权限策略,确保只有...

    mybatis 分页拦截器及拦截器配置

    MyBatis中的拦截器(Interceptor)是基于Java的动态代理机制实现的,它可以拦截执行SQL的生命周期中的某些环节,如:预处理、结果映射等。在分页拦截器中,它会在执行查询之前对SQL进行修改,自动添加LIMIT和OFFSET...

    Jfinal通过拦截器实现登录验证

    在IT行业中,Jfinal是一个基于Java的轻量级Web开发框架,它...同时,拦截器的灵活性让我们可以轻松地扩展功能,如增加角色权限控制、日志记录等。在实际开发中,合理利用拦截器可以极大地提高代码的可维护性和安全性。

    SpringMVC配置拦截器实现登录控制的方法

    4. SpringMVC中的拦截器实现登录控制的流程:首先配置拦截器,指定需要拦截的URL路径和排除的URL路径(如登录页面和静态资源路径等)。拦截器的实现类中,preHandle方法中首先检查请求的URI是否在排除的URL列表中,...

    spring+springMVC+mybatis拦截器分页 源码

    SpringMVC中的拦截器(Interceptor)扮演着关键角色,它可以在请求被控制器处理之前或之后执行自定义逻辑。例如,拦截器可以用来实现登录验证、记录日志、性能监控等功能。在"spring+springMVC+mybatis拦截器分页"的...

Global site tag (gtag.js) - Google Analytics