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

Liferay基于角色的登录重定向实现及空指针问题排查

问题解决:Liferay登录Hook角色跳转与空指针修复

空指针异常原因分析

你的代码出现NPE有两个核心原因:

  1. RoleLocalService未注入:Liferay的OSGi组件需要通过@Reference注解注入服务,直接使用未初始化的roleLocalService会导致空指针;
  2. 未处理fetchRole返回null的情况:如果角色名称不存在(比如大小写错误),fetchRole会返回null,此时调用getRoleId()会触发NPE。

修改后的完整代码

@Component(
    immediate = true,
    property = {"key=login.events.post"},
    service = LifecycleAction.class
)
public class MyCustomLoginHookLoginPostAction extends Action {

    private final static String PUBLIC_PAGE_CONTEXT = "/web";
    private final static String PRIVATE_PAGE_CONTEXT = "/group";
    // 站点1的友好URL,根据实际情况修改
    private final static String SITE1_FRIENDLY_URL = "/site1";
    // 管理员页面和经理页面的路径,根据实际页面友好URL修改
    private final static String ADMIN_PAGE_PATH = "/admin-dashboard";
    private final static String MANAGER_PAGE_PATH = "/manager-workspace";

    @Reference
    private RoleLocalService roleLocalService;

    @Override
    public void run(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ActionException {
        final long companyId = PortalUtil.getCompanyId(httpServletRequest);
        String path = PrefsPropsUtil.getString(companyId, PropsKeys.DEFAULT_LANDING_PAGE_PATH);

        try {
            path = getCustomLandingPage(httpServletRequest);
        } catch (PortalException e) {
            throw new ActionException(e);
        }

        if (Validator.isNotNull(path)) {
            final HttpSession httpSession = httpServletRequest.getSession();
            httpSession.setAttribute(WebKeys.LAST_PATH, new LastPath(StringPool.BLANK, path));
        }
    }

    private String getCustomLandingPage(HttpServletRequest httpServletRequest) throws PortalException {
        String path = null;
        final long companyId = PortalUtil.getCompanyId(httpServletRequest);
        User user = PortalUtil.getUser(httpServletRequest);

        // 1. 优先处理角色对应的跳转逻辑
        // 获取Administrator角色,注意角色名称是Liferay内置的,大小写敏感
        Role roleAdministrator = roleLocalService.fetchRole(companyId, "Administrator");
        if (roleAdministrator != null && roleLocalService.hasUserRole(user.getUserId(), roleAdministrator.getRoleId())) {
            // 跳转至站点1的管理员页面
            path = PUBLIC_PAGE_CONTEXT + SITE1_FRIENDLY_URL + ADMIN_PAGE_PATH;
            return path;
        }

        // 获取Manager角色
        Role roleManager = roleLocalService.fetchRole(companyId, "Manager");
        if (roleManager != null && roleLocalService.hasUserRole(user.getUserId(), roleManager.getRoleId())) {
            // 跳转至站点1的经理页面
            path = PUBLIC_PAGE_CONTEXT + SITE1_FRIENDLY_URL + MANAGER_PAGE_PATH;
            return path;
        }

        // 2. 原有站点跳转逻辑
        List<Organization> orgs = user.getOrganizations();
        if (orgs != null && !orgs.isEmpty()) {
            for (Organization org : orgs) {
                Group orgSite = org.getGroup();
                if (orgSite == null) continue; // 增加空判断,避免后续NPE

                int publicPageCount = orgSite.getPublicLayoutsPageCount();
                int privatePageCount = orgSite.getPrivateLayoutsPageCount();

                if (publicPageCount > 0) {
                    path = PUBLIC_PAGE_CONTEXT + orgSite.getFriendlyURL();
                    break;
                } else if (privatePageCount > 0) {
                    path = PRIVATE_PAGE_CONTEXT + orgSite.getFriendlyURL();
                    break;
                }
            }
        }

        if (Validator.isNull(path)) {
            List<Group> sites = user.getGroups();
            if (sites != null && !sites.isEmpty()) {
                for (Group site : sites) {
                    if (site == null) continue; // 增加空判断

                    int publicPageCount = site.getPublicLayoutsPageCount();
                    int privatePageCount = site.getPrivateLayoutsPageCount();

                    if (publicPageCount > 0) {
                        path = PUBLIC_PAGE_CONTEXT + site.getFriendlyURL();
                        break;
                    } else if (privatePageCount > 0) {
                        path = PRIVATE_PAGE_CONTEXT + site.getFriendlyURL();
                        break;
                    }
                }
            }
        }

        if (Validator.isNull(path)) {
            path = PrefsPropsUtil.getString(companyId, PropsKeys.DEFAULT_LANDING_PAGE_PATH);
        }

        return path;
    }
}

关键修改点说明

  1. 注入RoleLocalService:通过@Reference注解让Liferay的OSGi容器自动注入服务实例,解决roleLocalService为null的问题;
  2. 角色校验的空安全处理:每次调用fetchRole后先判断角色是否为null,再调用hasUserRole和getRoleId();
  3. 优先角色跳转逻辑:将角色判断放在原有站点逻辑之前,确保管理员/经理用户优先跳转到指定页面;
  4. 增加Group空判断:在原有逻辑中增加orgSite == null和site == null的判断,避免潜在的空指针;
  5. 配置页面路径常量:将站点和页面路径定义为常量,方便后续修改维护,注意要根据你实际的站点友好URL和页面路径调整这些值。

额外注意事项

  • 角色名称是大小写敏感的,Liferay内置的Administrator和Manager角色名称必须完全匹配;
  • 如果管理员/经理页面是私有页面,需要将PUBLIC_PAGE_CONTEXT替换为PRIVATE_PAGE_CONTEXT;
  • 测试时确保用户确实拥有对应的角色,并且页面路径正确存在。

内容的提问来源于stack exchange,提问作者SnIpEr ReLoAdEd

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 18:06:17