You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Apache Shiro时注入PermissionRoles出现空指针异常排查

Apache Shiro授权环节空指针异常排查

我使用Apache Shiro实现认证与授权,基于签名JWT的认证功能正常,但授权环节出现问题:实现RolePermissionResolver接口的JpaRolePermissionResolver类中,调用注入的PermissionRoles实例的getAllPermissionsForRole()方法时触发空指针异常;但在标注@RequiresPermissions的REST接口hello方法里调用该实例的同名方法却能正常运行。

相关代码

JpaRolePermissionResolver类

@ApplicationScoped
@Default
public class JpaRolePermissionResolver implements RolePermissionResolver {
  @Inject
  PermissionRoles permissionRoles;
  @Override
  public Collection<Permission> resolvePermissionsInRole(String roleString) {
    /*PermissionRoles permissionRoles = new PermissionRoles();
    permissionRoles.getAllPermissionsForRole();*/
    permissionRoles.getAllPermissionsForRole();
    if(roleString.equals("adminplus")) {
      return List.of(new WildcardPermission("canupdate:read"));
    } else if(roleString.equals("mod")) {
      return  List.of(
        new WildcardPermission("troopers:read"),
        new WildcardPermission("troopers:create"),
        new WildcardPermission("troopers:update")
        //, ... but not delete.
      );
    } else if(roleString.equals("user")) {
      return List.of(new WildcardPermission("troopers:read"));
    }
    return List.of();
  }
}

IRolePermission接口

public interface IRolePermission extends Serializable {
   HashMap<String, PermissionRoles> getAllPermissionsForRole();
}

PermissionRoles类

@Stateless
@LocalBean
public class PermissionRoles implements IRolePermission {
  @PersistenceContext(unitName = "default")
  EntityManager em;
  private String role;
  private String permission;

  public PermissionRoles() {
  }
  public PermissionRoles(String role, String permission) {
    this.role = role;
    this.permission = permission;
  }

  public String getRole() {
    return role;
  }

  public void setRole(String role) {
    this.role = role;
  }

  public String getPermission() {
    return permission;
  }

  public void setPermission(String permission) {
    this.permission = permission;
  }

  @Override
  public HashMap<String, PermissionRoles> getAllPermissionsForRole() {
    String sqlQuery = "SELECT er.name as role_name, ARRAY_TO_STRING(ARRAY_AGG(ep.name), ',') as permission_name " +
      "FROM ecommerce_roles er " +
      "JOIN ecommerce_role_permission erp ON er.id = erp.role_id " +
      "JOIN ecommerce_permissions ep ON ep.id = erp.permission_id " +
      "WHERE er.status = 1 AND ep.status = 1 " +
      "GROUP BY role_name";
    Query query = em.createNativeQuery(sqlQuery);
    List<Object[]> rolesPermission = query.getResultList();
    System.out.println(sqlQuery);
    HashMap<String, PermissionRoles> permissionRoles = new HashMap<>();
    for(Object[] object : rolesPermission) {
      permissionRoles.put((String) object[0],new PermissionRoles((String) object[0], (String) object[1]));
    }
    return permissionRoles;
  }
}

REST接口方法

@Inject
PermissionRoles permissionRoles;

@GET
@Produces("text/plain")
@RequiresPermissions("canupdate:read")
public String hello() {
  permissionRoles.getAllPermissionsForRole();
  return "User resource";
}

问题原因及解决方案

核心原因

JpaRolePermissionResolver是Shiro的扩展组件,Shiro自身创建该类实例时不会触发CDI容器的依赖注入,导致permissionRoles字段始终为null,调用其方法时抛出空指针异常。而REST接口中的PermissionRoles由CDI容器管理,注入流程正常,因此能调用成功。

具体解决方案

方案1:让Shiro通过CDI获取JpaRolePermissionResolver实例

确保Shiro配置中,rolePermissionResolver不是直接通过new创建,而是从CDI容器中获取。示例配置类:

@ApplicationScoped
public class ShiroConfig {
  @Inject
  private JpaRolePermissionResolver rolePermissionResolver;

  @Produces
  public SecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    // 其他配置(如Realm、SessionManager等)
    securityManager.setRolePermissionResolver(rolePermissionResolver);
    return securityManager;
  }
}

方案2:修改PermissionRoles作用域并手动获取CDI实例

将PermissionRoles的@Stateless改为@ApplicationScoped,使其成为CDI管理的bean:

@ApplicationScoped
public class PermissionRoles implements IRolePermission {
  // 原有代码不变
}

然后在JpaRolePermissionResolver中手动获取CDI实例:

@ApplicationScoped
@Default
public class JpaRolePermissionResolver implements RolePermissionResolver {
  private PermissionRoles permissionRoles;

  public JpaRolePermissionResolver() {
    // 手动从CDI容器获取实例
    this.permissionRoles = CDI.current().select(PermissionRoles.class).get();
  }

  @Override
  public Collection<Permission> resolvePermissionsInRole(String roleString) {
    permissionRoles.getAllPermissionsForRole();
    // 原有逻辑不变
  }
}

方案3:在resolvePermissionsInRole中手动管理EntityManager

由于PermissionRoles是@Stateless EJB,只能在容器管理环境中使用。如果无法让Shiro通过CDI获取JpaRolePermissionResolver,可以直接在方法内手动获取EntityManager执行查询:

@Override
public Collection<Permission> resolvePermissionsInRole(String roleString) {
  // 手动创建EntityManagerFactory和EntityManager
  EntityManagerFactory emf = Persistence.createEntityManagerFactory("default");
  EntityManager em = emf.createEntityManager();
  
  String sqlQuery = "SELECT er.name as role_name, ARRAY_TO_STRING(ARRAY_AGG(ep.name), ',') as permission_name " +
    "FROM ecommerce_roles er " +
    "JOIN ecommerce_role_permission erp ON er.id = erp.role_id " +
    "JOIN ecommerce_permissions ep ON ep.id = erp.permission_id " +
    "WHERE er.status = 1 AND ep.status = 1 " +
    "GROUP BY role_name";
  Query query = em.createNativeQuery(sqlQuery);
  List<Object[]> rolesPermission = query.getResultList();
  
  // 权限结果处理逻辑(按需添加)
  
  // 关闭资源
  em.close();
  emf.close();
  
  // 原有角色权限映射逻辑不变
}

内容的提问来源于stack exchange,提问作者Hassan Shaitou

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 17:53:10