| 類型 | 基類 | 生命週期 | 適用場景 |
| Classic Tag | TagSupport | doStartTag → doEndTag | 簡單邏輯、JSP 1.x |
| Simple Tag | SimpleTagSupport | doTag() 一次性執行 | 現代首選、體驗更好 |
| Tag File | .tag 文件 | 無 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. 部署與驗證
- TLD 文件放在
WEB-INF/tags/permission.tld 或 META-INF/tags/permission.tld(JAR 包內)
- 標籤類編譯到
WEB-INF/classes/com/example/tag/PermissionTag.class
- 啟動容器,訪問 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) { %>
<% } %>
最佳實踐總結
- 優先用 SimpleTagSupport:API 簡單、不需管理生命週期
- 屬性支援 rtexprvalue:允許 EL 表達式動態傳值
- body-content: scriptless:標籤體可含 EL、JSTL、標準動作,無 Scriptlet
- 權限計算前置:登錄時計算緩存,標籤只做查找
- 通配符匹配:支援
/module/* 批量授權
- 考慮框架整合:Spring Security、Shiro 已提供標籤庫,避免重複造輪子
總結
自定義 JSP 標籤將「權限判斷邏輯」與「頁面渲染」分離,實現聲明式授權。**SimpleTagSupport + TLD + EL 表達式**是現代標準開發流程。生產環境建議優先使用成熟安全框架提供的標籤庫,僅在輕量級項目或特殊定制需求時自行開發。
- 上一篇文章: Java 獲取時間方法總結:DateFormat、Timestamp、LocalDateTime 三種方案對比
- 下一篇文章: Eclipse 快捷鍵大全:高效開發必備的 30+ 核心組合鍵分類速查
Copyright ©
程式碼之家 All Rights Reserved