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

Java 自定義 JSP 標籤實戰:權限控制標籤完整開發流程

     編輯:Java 程式設計入門

自定義標籤類型對比

權限控制標籤需求

  • 標籤名:<perm:check uri="/user/list">...</perm:check>
  • 功能:當前用戶擁有指定 URI 權限時,輸出標籤體內容
  • 數據來源:Session 中的 User → Role → Privilege → Resource(uri)

1. 領域模型(簡化)

public class User {
    private Set roles;
    // getter/setter
}

public class Role {
    private Set privileges;
}

public class Privilege {
    private Set resources;
}

public class Resource {
    private String uri;  // 如 /user/list
}

2. 標籤處理類(SimpleTagSupport)

package com.example.tag;

import jakarta.servlet.jsp.JspException;
import jakarta.servlet.jsp.PageContext;
import jakarta.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

public class PermissionTag extends SimpleTagSupport {
    private String uri;  // 標籤屬性
    
    public void setUri(String uri) { this.uri = uri; }
    
    @Override
    public void doTag() throws JspException, IOException {
        // 1. 獲取 PageContext → Session → User
        PageContext pageContext = (PageContext) getJspContext();
        User user = (User) pageContext.getSession().getAttribute("loginUser");
        
        // 2. 未登錄直接返回
        if (user == null) return;
        
        // 3. 收集用戶所有資源 URI
        Set permittedUris = new HashSet<>();
        for (Role role : user.getRoles()) {
            for (Privilege priv : role.getPrivileges()) {
                for (Resource res : priv.getResources()) {
                    permittedUris.add(res.getUri());
                }
            }
        }
        
        // 4. 權限匹配:通配符支援(可選)
        boolean hasPermission = permittedUris.stream().anyMatch(u -> matchUri(u, uri));
        
        // 5. 有權限則輸出標籤體
        if (hasPermission) {
            getJspBody().invoke(null);  // null 表示輸出到當前 JspWriter
        }
    }
    
    // 簡單通配符匹配:/user/* 匹配 /user/list、/user/add
    private boolean matchUri(String pattern, String requestUri) {
        if (pattern.equals(requestUri)) return true;
        if (pattern.endsWith("*")) {
            String prefix = pattern.substring(0, pattern.length() - 1);
            return requestUri.startsWith(prefix);
        }
        return false;
    }
}

3. TLD 文件定義

<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="https://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://xmlns.jcp.org/xml/ns/javaee 
                            https://xmlns.jcp.org/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
        version="2.1">
    
    <tlib-version>1.0</tlib-version>
    <short-name>perm</short-name>
    <uri>/tags/permission</uri>
    
    <tag>
        <name>check</name>
        <tag-class>com.example.tag.PermissionTag</tag-class>
        <body-content>scriptless</body-content>
        <attribute>
            <name>uri</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>  <!-- 支援 EL 表達式 -->
            <type>java.lang.String</type>
        </attribute>
    </tag>
</taglib>

4. JSP 頁面使用

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="perm" uri="/tags/permission" %>
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>

<html>
<body>
    <h1>系統導航</h1>
    <ul>
        <perm:check uri="${pageContext.request.contextPath}/user/list">
            <li><a href="${pageContext.request.contextPath}/user/list">用戶管理</a></li>
        </perm:check>
        
        <perm:check uri="${pageContext.request.contextPath}/role/list">
            <li><a href="${pageContext.request.contextPath}/role/list">角色管理</a></li>
        </perm:check>
        
        <perm:check uri="${pageContext.request.contextPath}/log/*">
            <li><a href="${pageContext.request.contextPath}/log/view">日誌查看</a></li>
        </perm:check>
    </ul>
</body>
</html>

5. 部署與驗證

  1. TLD 文件放在 WEB-INF/tags/permission.tld 或 META-INF/tags/permission.tld(JAR 包內)
  2. 標籤類編譯到 WEB-INF/classes/com/example/tag/PermissionTag.class
  3. 啟動容器,訪問 JSP,驗證不同角色用戶看到不同菜單

進階優化

緩存用戶權限(避免每次標籤重複計算)

// 在登錄時計算並存入 Session
Set allUris = user.getRoles().stream()
    .flatMap(r -> r.getPrivileges().stream())
    .flatMap(p -> p.getResources().stream())
    .map(Resource::getUri)
    .collect(Collectors.toSet());
session.setAttribute("permittedUris", allUris);

// 標籤中直接讀取
Set permittedUris = (Set) pageContext.getSession().getAttribute("permittedUris");

整合 Spring Security(若使用)

// 替換為 Spring Security 授權標籤
<%@ taglib prefix="sec" uri="https://www.springframework.org/security/tags" %>
<sec:authorize access="hasAuthority('/user/list')">...</sec:authorize>

標籤檔實現(無 Java 代碼,.tag 文件)


<%@ attribute name="uri" required="true" rtexprvalue="true" %>
<%@ tag body-content="scriptless" %>
<%
    Set uris = (Set) session.getAttribute("permittedUris");
    boolean has = uris != null && uris.contains(uri);
%>
<% if (has) { %>
    
<% } %>

最佳實踐總結

  1. 優先用 SimpleTagSupport:API 簡單、不需管理生命週期
  2. 屬性支援 rtexprvalue:允許 EL 表達式動態傳值
  3. body-content: scriptless:標籤體可含 EL、JSTL、標準動作,無 Scriptlet
  4. 權限計算前置:登錄時計算緩存,標籤只做查找
  5. 通配符匹配:支援 /module/* 批量授權
  6. 考慮框架整合:Spring Security、Shiro 已提供標籤庫,避免重複造輪子

總結

自定義 JSP 標籤將「權限判斷邏輯」與「頁面渲染」分離,實現聲明式授權。**SimpleTagSupport + TLD + EL 表達式**是現代標準開發流程。生產環境建議優先使用成熟安全框架提供的標籤庫,僅在輕量級項目或特殊定制需求時自行開發。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
類型基類生命週期適用場景
Classic TagTagSupportdoStartTag → doEndTag簡單邏輯、JSP 1.x
Simple TagSimpleTagSupportdoTag() 一次性執行現代首選、體驗更好
Tag File.tag 文件無 Java 代碼頁面片段復用、前端主導