宣告式安全


EJB3的宣告式安全,可以使用Annotation來宣告基於Role的授權,指明類別或哪些方法可以被哪些角色所存取。

您可以在類別上使用@DeclarRoles宣告有效的Role名稱,例如:
@Stateless
@DeclarRoles({"admin", "manager"})
public class HelloBeanImpl implements HelloBean {
    ....
}

如果您沒有宣告有效的Role名稱,則容器也會自動根據所設定的@RolesAllowed來建立,@RolesAllowed可以套用在類別或方法上,若套用在類別上,則該類別的所有方法都必須是指定的Role才可以存取,若套用在方法上,則該方法必須是指定的Role才可以存取,例如:
@Stateless
@DeclarRoles({"admin", "manager"})
public class HelloBeanImpl implements HelloBean {
    @RolesAllowed("admin")
    public void doAdmin() {
        ...
    }
    ...
}

您也可以使用@PermitAll或@DenyAll來標註整個類別或方法,標註@PermitAll表示可以被任何Role使用呼叫,@DenyAll則是相反,表示任何Role都不得使用呼叫,在某些環境中,不適合呼叫某些方法或類別時,您就可以使用@DenyAll來設定,而不用直接修改程式碼。

您可以使用@RunAs標註類別或方法,在執行時A Role的方法中,若呼叫了某些B Role才能呼叫的方法,則可暫時以B Role來執行,例如:
@Stateless
@DeclarRoles({"admin", "manager"})
@RunAs("admin")
public class HelloBeanImpl implements HelloBean {
    @RolesAllowed("admin")
    public void doAdmin() {
        ...
    }
    @RolesAllowed("admin")
    public void doAdmin() {
        ...
    }
    ...

    ...

    @RolesAllowed("manager")
    public void doManager() {
        ...
        doAdmin(); // 需要 admin 的 Role 才能執行
        ...
    }
    ...
}

manager的Role執行doManager(),由於其中執行了admin的Role才可以執行的方法,若沒有設定如上的@RunAs,則會發生錯誤。

Java EE的安全是基於JAAS(Java Authentication and Authorization Service),若為Web應用程式,可以搭配Web容器的URL Pattern為基礎的宣告安全設定,當使用者登入後,Web容器會將通過驗證的Principal傳遞給EJB容器,可以很簡單的完成驗證與EJB3上 的宣告授權。若不透過Web容器的宣告安全設定而想使用EJB3宣告授權,需要深入了解JAAS的內容,這已不在這個文件的說明範圍。

以下配合Web容器的 宣告式基本驗證,讓Web容器為您作驗證,並在Servlet中呼叫EJB3的Bean,看看如何使用EJB3的宣告安全,首先是Bean的撰寫:
  • HelloBean.java
package onlyfun.caterpillar;

import javax.ejb.Remote;

@Remote
public interface HelloBean {
public String doHello(String message);
public String doSecurity(String message);
}

  • HelloBeanImpl.java
package onlyfun.caterpillar;

import javax.annotation.security.*;
import javax.ejb.Stateless;

@Stateless
public class HelloBeanImpl implements HelloBean {
@RolesAllowed("foo")
public String doSecurity(String message) {
return message + "security processed....";
}

public String doHello(String message) {
return message + "processed....";
}
}

其中doSecurity()必須是foo的Role才可以存取,其它方法則無限制,若您撰寫一個Servlet如下:
  • HelloServlet.java
package onlyfun.caterpillar;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.ejb.EJB;

public class HelloServlet extends HttpServlet {
@EJB
private HelloBean hello;

protected void processRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String method = request.getParameter("method");

if("security".equals(method)) {
response.getWriter().println(hello.doSecurity("info...."));
}
else {
response.getWriter().println(hello.doHello("hello...."));
}
}

protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

public String getServletInfo() {
return "Short description";
}
}

這個Servlet使用Web的安全宣告加以保護,只能是foo或orz的Role才可以請求:
  • web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>onlyfun.caterpillar.HelloServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>

<security-role>
<role-name>foo</role-name>
</security-role>
<security-role>
<role-name>orz</role-name>
</security-role>

<security-constraint>
<display-name>SecurityConstraint</display-name>
<web-resource-collection>
<web-resource-name>Secret Information</web-resource-name>
<url-pattern>/HelloServlet</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>foo</role-name>
<role-name>orz</role-name>
</auth-constraint>
</security-constraint>

<login-config>
<auth-method>BASIC</auth-method>
<realm-name>file</realm-name>
</login-config>
</web-app>

若您的Glassfish上有caterpillar及justin使用者,群組分別設為fooGroup與orzGroup,而sun-web.xml的設定如下:
...
  <security-role-mapping>
    <role-name>foo</role-name>
    <group-name>fooGroup</group-name>
  </security-role-mapping>
  <security-role-mapping>
    <role-name>orz</role-name>
    <group-name>orzGroup</group-name>
  </security-role-mapping>
...

若您請求/HelloServlet,並指定method=security參數,當使用caterpillar登入時,才可以呼叫HelloBean的doSecurity(),若使用justin登入,雖可以通過驗證來請求,但因Role不正確,所以呼叫HelloBean的doSecurity()時,就會出現授權失敗的錯誤。

若不指定method=security參數,則呼叫的是HelloBean的doHello(),則無論登入的是caterpillar或justin,都可以正確執行。