Java EE 5 引入 JSR 250 通用註解,將安全配置從 ejb-jar.xml/web.xml 遷移至 Java 代碼,簡化開發、縮短週期。本文系統梳理 5 大安全註解在 EJB、Servlet、Web Service 中的適用規則、繼承語義與部署描述符覆蓋機制。
| 註解 | 目標 | EJB | Servlet | Web Service | 核心用途 |
|---|---|---|---|---|---|
@PermitAll | 類/方法 | ✓ | ✗ | ✓ | 允許所有用戶訪問(含未認證) |
@DenyAll | 方法 | ✓ | ✗ | ✓ | 禁止所有用戶訪問 |
@RolesAllowed | 類/方法 | ✓ | ✗ | ✓ | 指定允許訪問的角色列表 |
@DeclareRoles | 類 | ✓ | ✓ | ✓ | 聲明組件使用的角色,供 isCallerInRole 檢查 |
@RunAs | 類 | ✓ | ✓ | ✗ | 指定組件以何種角色身份執行 |
關鍵限制: @PermitAll/@DenyAll/@RolesAllowed **不支援 Servlet**,Web 模組仍需在 web.xml 定義 <security-constraint>。
@Stateless
@RolesAllowed("javaee") // 類級:預設所有方法需 javaee 角色
public class HelloEJB implements Hello {
@PermitAll // 方法級覆蓋:hello() 允許匿名訪問
public String hello(String msg) { return "Hello, " + msg; }
public String bye(String msg) { return "Bye, " + msg; } // 繼承類級,需 javaee 角色
}
規則: 方法級註解覆蓋類級;同一方法不可同時標註 @PermitAll/@DenyAll/@RolesAllowed;@RolesAllowed 僅能出現一次,多角色用陣列 @RolesAllowed({"admin","user"})。
當授權決策需動態判斷(如「屬於角色 A 且不屬於角色 B」)時,單純註解不夠,需結合 @DeclareRoles 聲明角色,並在代碼中調用上下文 API:
| 組件 | 檢查角色 API |
|---|---|
| EJB | SessionContext.isCallerInRole(role) |
| Servlet | HttpServletRequest.isUserInRole(role) |
| Web Service | WebServiceContext.isUserInRole(role) |
@Stateless
@DeclareRoles({"A", "B"})
public class HelloEJB implements Hello {
@Resource SessionContext ctx;
public String hello(String msg) {
if (ctx.isCallerInRole("A") && !ctx.isCallerInRole("B")) {
return "Allowed: " + msg;
}
return "Denied";
}
}
| 註解 | 繼承規則 |
|---|---|
@PermitAll/@DenyAll/@RolesAllowed | 方法級:採用繼承鏈中被調用方法的註解;類級:被子類繼承,方法級覆蓋類級 |
@RunAs | **僅考慮葉節點(具體實現類)的註解**,父類忽略 |
@DeclareRoles | **疊加合併**:繼承鏈中所有聲明的角色彙總 |
若同時存在註解與 ejb-jar.xml/web.xml,**部署描述符優先**。示例:
// EJB 註解
@Stateless
public class HelloEJB {
@PermitAll public String hello1() { return "1"; }
@RolesAllowed("javaee") public String hello2() { return "2"; }
@DenyAll public String hello3() { return "3"; }
}
ejb-jar.xml 覆蓋:
<assembly-descriptor>
<security-role><role-name>javaee</role-name></security-role>
<method-permission>
<role-name>javaee</role-name>
<method><ejb-name>HelloEJB</ejb-name><method-name>hello1</method-name></method>
</method-permission>
<method-permission>
<unchecked/> <!-- 等同 @PermitAll -->
<method><ejb-name>HelloEJB</ejb-name><method-name>hello3</method-name></method>
</method-permission>
<exclude-list> <!-- 等同 @DenyAll -->
<method><ejb-name>HelloEJB</ejb-name><method-name>hello2</method-name></method>
</exclude-list>
</assembly-descriptor>
結果: hello1 需 javaee 角色;hello2 禁止訪問;hello3 允許所有。
<security-constraint>
<web-resource-collection>
<web-resource-name>SecureArea</web-resource-name>
<url-pattern>/admin/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>employee</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method> <!-- 或 FORM -->
<realm-name>default</realm-name>
</login-config>
<security-role><role-name>employee</role-name></security-role>
在 sun-application.xml / sun-ejb-jar.xml / sun-web.xml 中映射:
<security-role-mapping>
<role-name>employee</role-name>
<group-name>engineer</group-name> <!-- 映射到伺服器 engineer 群組 -->
</security-role-mapping>
EJB 在 sun-ejb-jar.xml 指定主體:
<ejb>
<ejb-name>HelloEJB</ejb-name>
<principal><name>user1</name></principal>
</ejb>
Servlet 在 sun-web.xml 指定:
<servlet>
<servlet-name>myServlet</servlet-name>
<principal-name>user1</principal-name>
</servlet>
web.xml 安全約束。<login-config> 指定認證方式(BASIC/FORM)與 Realm。<security-role-mapping> 完成角色映射。@RunAs 組件的主體身份。