首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

基於 Struts 2 攔截器實現細粒度 RBAC 存取控制

     編輯:Java 相關知識

Struts 2 內建 RolesInterceptor 基於 J2EE 容器授權,粒度較粗。本文示範如何利用攔截器機制,結合 Hibernate+Spring 實現應用層面的細粒度角色基礎存取控制(RBAC)。

業務場景與挑戰

  • 用戶在不同情境(社團、專案、日曆)擁有不同角色
  • 角色取決於:用戶 ID + 資源類型 + 資源 ID
  • 例:Mike 在日曆 #123 是 admin,在日曆 #456 是 reader
  • 容器授權無法表達「資源實例級」權限

領域模型:UserRole 實體


@Entity
public class UserRole {
  private Long id;
  private User user;
  private String objectType;   // 資源類型,如 "calendar"
  private Long objectId;       // 資源 ID
  private String role;         // 角色名,如 "admin", "owner", "reader"
  
  // JPA 註解映射...
  @ManyToOne @JoinColumn(name="userId")
  public User getUser() { ... }
}

複合鍵 (userId, objectType, objectId) 唯一決定角色。

DAO 層:JPA EntityManager 操作


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

public class UserRoleDaoImpl implements UserRoleDao {
  @PersistenceContext EntityManager em;
  
  public UserRole find(Long userId, String objectType, Long objectId) {
    Query q = em.createQuery(
      "FROM UserRole ur WHERE ur.user.id=?1 AND ur.objectType=?2 AND ur.objectId=?3"
    ).setParameter(1, userId).setParameter(2, objectType).setParameter(3, objectId);
    List r = q.getResultList();
    return r.isEmpty() ? null : (UserRole)r.get(0);
  }
  // create/update 省略...
}

服務層:RoleService 門面


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

@Transactional(readOnly=true)
public class RoleServiceImpl implements RoleService {
  private UserRoleDao userRoleDao;
  
  @Transactional(readOnly=false)
  public void setUserRole(Long userId, String role, String objectType, Long objectId) {
    UserRole ur = new UserRole(userId, role, objectType, objectId);
    UserRole existing = userRoleDao.find(userId, objectType, objectId);
    if (existing == null) userRoleDao.create(ur);
    else { ur.setId(existing.getId()); userRoleDao.update(ur); }
  }
  
  public String findRole(Long userId, String objectType, Long objectId) {
    UserRole ur = userRoleDao.find(userId, objectType, objectId);
    return ur == null ? null : ur.getRole();
  }
}

核心:RBACInterceptor 實現


public class RBACInterceptor implements Interceptor {
  public static final String FORBIDDEN = "forbidden";
  
  private List allowedRoles = new ArrayList<>();
  private List disallowedRoles = new ArrayList<>();
  private RoleService roleService;
  private String objectType;      // 資源類型
  private String objectIdKey;     // HTTP 參數名
  
  // Setter 供 Spring 注入與 Struts 配置注入...
  
  public String intercept(ActionInvocation invocation) throws Exception {
    HttpServletRequest req = ServletActionContext.getRequest();
    Long objectId = Long.valueOf(req.getParameter(objectIdKey));
    Map session = invocation.getInvocationContext().getSession();
    Long userId = (Long) session.get(Constant.KEY_CURRENT_USER);
    
    String userRole = roleService.findRole(userId, objectType, objectId);
    
    if (!isAllowed(userRole)) return FORBIDDEN;
    return invocation.invoke();  // 繼續執行 Action
  }
  
  protected boolean isAllowed(String userRole) {
    if (!allowedRoles.isEmpty()) {
      return userRole != null && allowedRoles.contains(userRole);
    } else if (!disallowedRoles.isEmpty()) {
      return userRole == null || !disallowedRoles.contains(userRole);
    }
    return true; // 無限制
  }
}

關鍵點:

  • 從 Session 取得當前用戶 ID
  • 從 HTTP 參數取得資源 ID(如 id=123)
  • 結合配置的 objectType 查詢角色
  • 支援「允許清單」與「拒絕清單」兩種模式
  • 無權限時回傳 forbidden,由 Result 處理錯誤頁

Spring 整合配置


<bean id="userRoleDao" class="...UserRoleDaoImpl"/>
<bean id="roleService" class="...RoleServiceImpl">
  <property name="userRoleDao" ref="userRoleDao"/>
</bean>
<bean id="RBACInterceptor" scope="prototype" class="...RBACInterceptor">
  <property name="roleService" ref="roleService"/>
</bean>

Struts 2 配置套用攔截器


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

<action name="deleteMeeting" class="...DeleteMeetingAction">
  <result>/deleteMeetingResult.jsp</result>
  <result name="forbidden">/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 name="assignCalendarRole" class="...AssignCalendarRoleAction">
  <result>/assignResult.jsp</result>
  <result name="forbidden">/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>

deleteMeeting 允許 admin 或 owner;assignCalendarRole 僅允許 owner 分配角色。

角色分配 Action 範例


public class AssignCalendarRoleAction extends ActionSupport {
  private RoleService roleService;
  private Long userId, id;
  private String userRole = "reader";
  
  public AssignCalendarRoleAction(RoleService roleService) { this.roleService = roleService; }
  
  public String execute() {
    roleService.setUserRole(userId, userRole, "calendar", id);
    return SUCCESS;
  }
}

總結與擴展方向

此實現展示了應用層 RBAC 的核心架構,優於容器授權於:資源實例級粒度、動態角色解析、與業務邏輯整合。可擴展方向:用戶群組支援、權限繼承、快取優化、審計日誌等。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved