安全性對於企業環境非常重要。在 Java EE 5 / GlassFish 環境中,您可以通過以下幾種方式實現安全性:
傳輸層安全性 (TLS) / 安全套接字層 (SSL) 技術
身份驗證 (Authentication) 和授權 (Authorization)
消息層安全性(僅適用於 GlassFish 中的 Web 服務)
本文討論身份驗證和授權。參考資料 [1]、[2] 和 [3] 討論了如何在客戶端和服務器端為 Enterprise JavaBeans 和 Web 服務建立 SSL 環境。Web 服務的消息層安全性將在以後的文章中討論。
身份驗證服務一般通過要求用戶輸入用戶名和密碼來實現校驗用戶身份的目的。在 Java EE 環境中,身份驗證是和域(realm)相關聯的。域可以通過多種方式存儲用戶身份信息,包括文件、LDAP 目錄、甚至是通過 JDBC 訪問的數據庫(請參閱 參考資料 [4])。它還可以與 Solaris 可拔插驗證模塊 (Pluggable Authentication Modules, PAM) 框架一起工作。
授權服務根據所運行的軟件和運行該軟件的用戶的身份來執行訪問控制授權操作。每次當用戶登錄時,應用程序都會為他/她賦予一組權限。
在 Java EE 5 之前,如果您希望在某個應用程序使用授權,則需要在應用程序部署描述符 ejb-jar.xml 或 web.xml 中指定授權信息。Java EE 5 的重要改進之一就是簡化了 Java EE 應用程序的開發。從 Java EE 5 開始,開發人員可以在 Java 源文件中指定注釋,而無需在部署描述符中加入元數據。注釋簡化了 Java EE 應用程序的開發,縮短了開發周期,並降低了總體擁有成本。
JSR 250(請參閱 參考資料 [5])定義了 Java 平台中的常用注釋。本文將討論 JSR 250 中定義的安全注釋,並演示如何在應用程序中通過它們來實現身份驗證和授權,以獲得安全性。
基本定義和示例
注釋 (Annotation) 是一種特殊的修飾符,可以與其他修飾符共同使用。注釋由 @ 符號、注釋類型和包含在括號中的元素值對列表組成。
本節討論 JSR 250 定義的常用安全注釋。共有 5 種(請參閱 參考資料 [6]):
javax.annotation.security.PermitAll
javax.annotation.security.DenyAll
javax.annotation.security.RolesAllowed
javax.annotation.security.DeclareRoles
javax.annotation.security.RunAs
@PermitAll、@DenyAll 和 @RolesAllowed 注釋是為指定 EJB 業務方法權限而定義的。@DeclareRoles 和 @RunAs 是 TYPE 級注釋,用於指定與角色相關的元數據。
對於 Web 模塊,您仍然需要在 web.xml 應用程序部署描述符中定義一個 <security-constraint> 來添加授權約束,這與 J2EE 1.4 相類似。在 Java EE 5 環境中,與權限相關的注釋僅為 EJB 模塊定義。下表總結了這些注釋的基本用法。有關詳細信息,請參閱 JSR 250 規范(參考資料 [5])。
注釋 目標 EJB 或其超類 Servlet 或 Web 庫 描述 類型 方法 @PermitAll X X X 指示某 EJB 的某個方法或所有業務方法允許被所有用戶訪問。 @DenyAll X X 指示 EJB 的某個方法不允許被任何用戶訪問。 @RolesAllowed X X X 指示 EJB 的某個方法或所有業務方法允許被角色列表中的用戶訪問。 @DeclareRoles X X X 定義安全檢查的角色,供 EJBContext.isCallerInRole、HttpServletRequest.isUserInRole 和 WebServiceContext.isUserInRole 使用。 @RunAs X X(不適用於非 EJB 超類) X(僅適用於 Servlet) 指定某個組件的 run-as 角色。
注意:
對於 @PermitAll、@DenyAll 和 @RolesAllowed 注釋,類級別的注釋適用於類,方法級的注釋適用於方法。方法級注釋覆蓋類級注釋行為。
示例:請參考以下代碼:
@Stateless
@RolesAllowed("javaee")
public class HelloEJB implements Hello {
@PermitAll
public String hello(String msg) {
return "Hello, " + msg;
}
public String bye(String msg) {
return "Bye, " + msg;
}
}
在該示例中,hello() 方法允許被所有用戶訪問,bye() 方法允許被 javaee 角色中的用戶訪問。
@DeclareRoles 注釋定義了某個組件將要使用的角色列表。在 Java EE 5 環境中,您可以通過 @javax.annotation.Resource 來查找資源,以及通過調用以下 API 來確認用戶是否屬於某個角色:
組件 檢查角色的 API EJB javax.ejb.EJBContext.isCallerInRole(role) Servlet javax.servlet.http.HttpServletRequest.isUserInRole(role) Web 服務 javax.xml.ws.WebServiceContext.isUserInRole(role)盡管 @PermitAll、@DenyAll 和 @RolesAllowed 注釋允許實現大部分的授權決策,但仍需要 @DeclareRoles 注釋來幫助實現更為復雜的邏輯。
例如,假設 hello 方法允許被屬於角色 A 但同時不屬於角色 B 的用戶訪問,則以下代碼片段可以實現此目的:
@Stateless
@DeclaresRoles({"A", "B"})
public class HelloEJB implements Hello {
@Resource private SessionContext sc;
public String hello(String msg) {
if (sc.isCallerInRole("A") && !sc.isCallerInRole("B")) {
...
} else {
...
}
}
}
在 Web 模塊中,您可以在 Servlet、過濾器和標記庫中指定 @DeclareRoles。JSP 頁面不支持注釋。
嵌入式 Web 服務調用中的 run-as 或調用者的身份標識和客戶身份標識之間的關系是沒有定義的。無定義則意味著您不能假定 Web 服務調用中的 run-as 和調用者身份標識作為客戶標識傳遞。
注釋的無效用法示例
同一方法中不能同時使用 @DenyAll、@PermitAll 和 @RolesAllowed。例如,以下這些用法是無效的:
@PermitAll
@DenyAll
public String hello()
有關詳細信息,請參閱 JSR 250 規范的 2.11 節。
同一方法中不能使用 @RolesAllowed 注釋兩次以上。
例如,以下用法無效並且會導致編譯失敗:
@RolesAllowed("javaee")
@RolesAllowed("j2ee")
public String hello()
可以在 @RolesAllowed 注釋中提供授權角色列表以實現想要的效果,如下所示:
@RolesAllowed({"javaee", "j2ee"})
public String hello()
安全注釋的繼承
本節討論安全注釋的繼承。因為 GlassFish 方法的默認行為是 @PermitAll,因此為便於閱讀,以下討論將省略該注釋。
繼承的一般規則如下:
對於方法,使用與繼承層次結構中選擇的方法有關聯的注釋。
對於 @RunAs 注釋,只考慮層次結構葉節點中的注釋。
對於 @DeclareRoles 注釋,繼承附加在繼承層次結構中。
下面這個示例演示了這些注釋繼承規則:
示例:在 EJB 的如下層次結構中:
可以使用以下方法權限:
Hello 方法 HelloBaseEJB 方法權限 HelloEJB 方法權限 hello1() 允許被 manager 角色中的用戶訪問 允許被所有用戶訪問 hello2() 允許被 employee 角色中的用戶訪問 允許被 staff 角色中的用戶訪問 hello3() 允許被 employee 角色中的用戶訪問 允許被 staff 角色中的用戶訪問
示例:在 Servlet 的以下層次結構中:
Servlet 定義的角色 RunAs HelloBaseServlet employee engineer HelloServlet employee、manager staff HelloServlet2 employee注意,HelloServlet2 中沒有設置 run-as 角色。
使用部署描述符
使用注釋可以簡化應用程序的部署描述符。但是在某些場景中,我們仍然需要或者更喜歡使用部署描述符。本節將描述這些場景。
對於 @RolesAllowed 的 EJB Web 服務端點,您需要在 sun-ejb-jar.xml 中指定 <login-config> 和 <auth-method> 元素來定義將要使用的身份驗證類型。對於用戶名密碼身份驗證,將 <auth-method> 元素設置為 BASIC,如以下示例所示。只有 EJB Web 服務端點需要執行此步驟,EJB 不需要。
<sun-ejb-jar>
<enterprise-beans>
<ejb>
<ejb-name>HelloEjb</ejb-name>
<webservice-endpoint>
<port-component-name>HelloEjb</port-component-name>
<login-config>
<auth-method>BASIC</auth-method>
<realm>default</realm>
</login-config>
</webservice-endpoint>
</ejb>
</enterprise-beans>
</sun-ejb-jar>
@PermitAll、@RolesAllowed 和 @DenyAll 注釋在 servlet 中不受支持。當使用 servlet 時,在 web.xml 部署描述符中指定身份驗證和授權方式。以下信息必須在 web.xml 中指定:
<security-constraint>/<web-resource-collection>
這些元素指定了受保護 Web 資源的 URL 模式和 HTTP 方法。
<security-constraint>/<auth-constraint>
這些元素指定了允許訪問受保護 Web 資源的角色列表。
<login-config>
該元素指定了將要執行的身份驗證類型(例如,<auth-method>(BASIC 或 FORM))、將要執行身份驗證的域 (<realm-name>),以及(如果是基於表格驗證)登錄表格和錯誤頁面的位置 (<form-login-config>)。
<security-role> 該元素指定了該 Web 應用程序使用的角色列表。該列表必須包含上面提到的 <auth-constraint> 中指定的角色。
例如,假設您希望保護 index.jsp 頁面的 GET 和 POST 方法,只允許 employee 角色訪問它們,則需要使用以下 web.xml 配置來實現:
...
<web-app>
<servlet>
...
</servlet>
<security-constraint>
<web-resource-collection>
<web-resource-name>MySecureResource</web-resource-name>
<url-pattern>/index.jsp</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>employee<role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>default</realm-name>
<login-config>
<security-role>
<role-name>employee</role-name>
</security-role>
</web-app>
對於只有某個角色才具有訪問權限的應用程序來說,您需要在應用程序使用的角色與應用服務器定義的組或主體之間建立映射。為了在應用程序所使用的 employee 角色和應用服務器默認域定義的 engineer 組之間建立映射,您仍然需要在運行時部署描述符 sun-application.xml、sun-ejb-jar.xml 或 sun-web.xml 中指定該安全角色映射。
例如,將以下代碼添加到 sun-application.xml 中就可以實現在 engineer 組的所有用戶與某個應用程序的 employee 角色之間建立映射。
<security-role-mapping>
<role-name>employee</employee>
<group-name>engineer</group-name>
</security-role-mapping>
部署描述符覆蓋注釋行為。
示例:假設有以下 Enterprise JavaBean:
@Stateless
public class HelloEJB implements Hello {
@PermitAll
public String hello1(String msg) {
return "1: Hello, " + msg;
}
@RolesAllowed("javaee")
public String hello2(String msg) {
return "2: Hello, " + msg;
}
@DenyAll
public String hello3(String msg) {
return "3: Hello, " + msg;
}
}
您可以通過應用程序部署描述符 ejb-jar.xml 來覆蓋方法權限:
<ejb-jar ...>
<enterprise-beans>
...
</enterprise-beans>
<assembly-descriptor>
<security-role>javaee</security-role>
<method-permission>
<role-name>javaee</role-name>
<method>
<ejb-name>HelloEJB</ejb-name>
<method-intf>Local</method-intf>
<method-name>hello1</method-name>
</method>
</method-permission>
<method-permission>
<unchecked/>
<method>
<ejb-name>HelloEJB</ejb-name>
<method-intf>Local</method-intf>
<method-name>hello3</method-name>
</method>
</method-permission>
<exclude-list>
<method>
<ejb-name>HelloEJB</ejb-name>
<method-intf>Local</method-intf>
<method-name>hello2</method-name>
</method>
</exclude-list>
</assembly-descriptor>
</ejb-jar>
行為如下:
方法 注釋 部署描述文件覆蓋 hello1() 允許被所有用戶訪問 允許被 javaee 角色的用戶訪問 hello2() 允許被 javaee 角色的用戶訪問 不允許被任何用戶訪問 hello3() 不允許被任何用戶訪問 允許被所有用戶訪問在 GlassFish 中,如果應用程序部署描述文件中沒有指定域,則該應用程序將使用默認域。您可以在運行時部署描述符 sun-application.xml 和 sun-ejb-jar.xml 中使用 <realm> 元素或在 web.xml 中使用 <realm-name> 元素來覆蓋該設置。
如果在 GlassFish 中使用 @RunAs 注釋,則需要將 run-as 角色關聯到一個主體,如果只關聯了一個主體,則該主體將作為 run-as 主體的默認值;如果關聯了多個主體,則需要明確設置 run-as 主體。以下示例演示了如何在 sun-ejb-jar.xml 中設置 run-as 主體:
...
<ejb>
...
<principal>
<name>user1</name>
</principal>
...
</ejb>
以下示例演示了如何在 sun-web.xml 中設置 run-as 主體:
...
<servlet>
<servlet-name>myServlet</servlet-name>
<principal-name>user1</principal-name>
...
</servlet>
...
結束語
總而言之,注釋可以通過身份驗證和授權為 Java EE 5 環境中的應用程序提供安全性。在 Java EE 5 環境中使用身份驗證和授權時,通常需要遵循以下幾個步驟:
創建 Java EE 應用程序。
使用 Enterprise JavaBeans 安全注釋建立授權約束,或者在 Web 模塊的 web.xml 文件中添加安全約束。
在相應的部署描述符中添加身份驗證需求 <login-config>。
建立域(如果未使用默認域),或者在應用程序部署描述符中指定 run-as 主體。
將 <security-role-mapping> 元素添加到運行時部署描述文件中,在應用程序角色和應用服務器組或主體之間建立映射。
打包和部署應用程序。