Web 容器的宣告式安全管理,僅能針對 URL 來設定哪些資源必須受到保護,如果打算依不同的角色在同一個頁面中設定可存取的資源,例如只有站長或版面管理員可以看到刪除整個討論串的功能,一般使用者不行,那麼顯然地無法單純使用宣告式安全管理來達成。
在 Servlet 3.0 中,HttpServletRequest
新增了三個與安全有關的方法:authenticate()
、login()
、logout()
。
首先來看到 authenticate()
方法,如果在 web.xml 設定為表單驗證:
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.html</form-login-page>
</form-login-config>
</login-config>
接下來,在程式流程中,可以使用 authenticate()
,只讓通過驗證的使用者才可以觀看:
package cc.openhome;
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebServlet("/user")
public class User extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if(request.authenticate(response)) {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("必須驗證過使用者才可以看到的資料");
out.println("<a href='logout'>登出</a>");
}
}
}
如果 authenticate()
的結果是 false
,表示使用者未曾登入,在 service()
完成後,會自動 forward 至登入表單:
<!DOCTYPE html>
<html>
<head>
<title>登入</title>
<meta charset="UTF-8">
</head>
<body>
<form action="login" method="post">
名稱:<input type="text" name="user"><br>
密碼:<input type="password" name="passwd" autocomplete="off"><br>
<input type="submit" value="送出">
</form>
</body>
</html>
在登入表單中,可以決定登入驗證時的 action
、請求參數等,執行登入時,可以使用請求物件的 login()
方法:
package cc.openhome;
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebServlet("/login")
public class Login extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String user = request.getParameter("user");
String passwd = request.getParameter("passwd");
try {
request.login(user, passwd);
response.sendRedirect("user");
} catch(ServletException ex) {
response.sendRedirect("login.html");
}
}
}
如果登入成功,Session ID 會更換。若要登出,可以使用請求物件的 logout()
方法:
package cc.openhome;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/logout")
public class Logout extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.logout();
response.sendRedirect("login.html");
}
}
在 Servlet 3.0 之前,HttpServletRequest
上就已存在三個與安全相關的方法:getUserPrincipal()
、getRemoteUser()
及 isUserInRole()
。
getUserPrincipal()
與 EJB 元件的溝通有關,這邊不加以討論。getRemoteUser()
可以取得登入使用者的名稱(如果驗證成功的話)或是傳回 null
(如果沒有驗證成功的使用者),不過並不常用。
比較常用的是 isUserInRole()
方法,可以傳給它一個角色名稱,如果登入的使用者屬於該角色則傳回 true
,否則傳回 false
(沒有登入就呼叫也會傳回 false
)。一個基本的使用方式像是:
if(request.isUserInRole("admin") || request.isUserInRole("manager")) {
// 進行站長或版面管理員才可以作的事,例如呼叫刪除討論串的方法之類的
}
上面的程式碼中,將角色名稱直接寫死了。如果不想在程式碼中寫死角色的名稱,則有兩個方式可以解決。第一個方式是透過 Servlet 初始參數的設定。第二個方式,則可以在 <servlet>
標籤中設定 <security-role-ref>
,透過 <role-link>
與 <role-name>
將程式碼中的名稱跟實際角色名稱對應起來。例如若 web.xml的定義如下:
<web-app…>
<servlet>
<security-role-ref>
<role-name>administrator</role-name>
<role-link>admin</role-link>
</security-role-ref>
..
</servlet>
// 略…
<security-role>
<role-name>admin</role-name>
<role-name>manager</role-name>
</security-role>
</web-app>
如果 Servlet 程式碼中是這麼寫的:
if(request.isUserInRole("administrator")) {
// 略...
}
則根據 web.xml 中 <security-role-ref>
的設定,administrator 名稱將對應至實際的角色名稱為
admin`。