使用Apache Shiro时注入PermissionRoles出现空指针异常排查
我使用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

