訪問安全控制解決方案
本文是《輕量級 Java Web 框架架構設計》的系列博文。
今天想和大家簡單的分享一下,在 Smart 中是如何做到訪問安全控制的。也就是說,當沒有登入或 Session 過期時所做的操作,會自動退回到首頁(例如:登入頁面),以防止使用者進行非法操作。
這件事情或許是每個具備安全性考慮的系統都需要的功能,我們可以分兩種情況來處理使用者的請求:
- 普通請求
- AJAX 請求
下面是具體的實現過程,您別忘了繫好安全帶,我們這就出發了!
第一步:在 Smart Framework 中定義一個認證異常類AuthException
public class AuthException extends RuntimeException {
public AuthException() {
super();
}
public AuthException(String message) {
super(message);
}
public AuthException(String message, Throwable cause) {
super(message, cause);
}
public AuthException(Throwable cause) {
super(cause);
}
}
沒啥內容,就一個普通的RuntimeException 而已,其實就是想自定義一種異常型別,以區別於其他的異常,方便在框架中進行處理。
第二步:在 Smart Sample 中定義一個 AuthAspect,用於攔截 Action 的所有請求
@Bean
@Aspect(pkg = "com.smart.sample.action")
@Order(0)
public class AuthAspect extends BaseAspect {
@Override
public boolean filter(Class<?> cls, Method method, Object[] params) {
String className = cls.getSimpleName();
String methodName = method.getName();
return !(
className.equals("UserAction") &&
(methodName.equals("login") || methodName.equals("logout")
)
);
}
@Override
public void before(Class<?> cls, Method method, Object[] params) throws Exception {
User user = DataContext.Session.get("user");
if (user == null) {
throw new AuthException();
}
}
}
以上程式碼中需注意一下幾點:
- 攔截到 action 包,即攔截所有的 Action 類。
- 使用 Order 註解定義 AOP 攔截順序,為 0 表示最先攔截。【新特性】
- 在 filter 方法中排除掉幾個特殊的 Action 方法,例如:login 與 logout,因為它們是無需進行安全控制的。
- 在 before 方法中從 Session 中獲取 User 資料(已在 login 時放入到 Session),若為空,說明 Session 已過期,則主動丟擲 AuthException。
這樣 AOP 框架(也就是 BaseAspect 類及其相關 Proxy Chain 等)就可以處理這個自定義異常了。
還記得 Order 註解嗎?它曾經在單元測試中出現過,現在還可以用於定義 AOP 順序。
友情提示:
- 對單元測試感興趣的朋友,請閱讀《像這樣做單元測試》。
- 若您還不熟悉 Smart AOP 的話,請閱讀《使用“鏈式代理”實現 AOP》。
第三步:在 BaseAspect 中將異常繼續往上拋,拋給它的呼叫者
public abstract class BaseAspect implements Proxy {
@Override
public final void doProxy(ProxyChain proxyChain) throws Exception {
...
begin();
try {
if (filter(cls, method, params)) {
before(cls, method, params);
Object result = proxyChain.doProxyChain();
after(cls, method, params, result);
} else {
proxyChain.doProxyChain();
}
} catch (Exception e) {
error(cls, method, params, e);
throw e; // 將異常繼續往上拋,拋給它的呼叫者
} finally {
end();
}
}
...
}
那麼 Aspect 的呼叫者是誰呢?也就是說,誰用 Aspect 呢?至少 Action 是一個關鍵性使用者。
那麼 Action 又是誰來呼叫呢?當然就是 DispatchServlet 了。
於是,順騰摸瓜,找到了呼叫的起源。
第四步:修改 DispatchServlet,處理 AuthException
@WebServlet("/*")
public class DispatcherServlet extends HttpServlet {
...
private void handleActionMethod(HttpServletRequest request, HttpServletResponse response, ActionBean actionBean, List<Object> paramList) {
// 從 ActionBean 中獲取 Action 相關屬性
Class<?> actionClass = actionBean.getActionClass();
Method actionMethod = actionBean.getActionMethod();
// 從 BeanHelper 中建立 Action 例項
Object actionInstance = BeanHelper.getInstance().getBean(actionClass);
// 呼叫 Action 方法
Object actionMethodResult;
try {
actionMethod.setAccessible(true); // 取消型別安全檢測(可提高反射效能)
actionMethodResult = actionMethod.invoke(actionInstance, paramList.toArray());
} catch (Exception e) {
// 處理 Action 方法異常【★】
handleActionMethodException(request, response, e);
// 直接返回
return;
}
// 處理 Action 方法返回值
handleActionMethodReturn(request, response, actionMethodResult);
}
private void handleActionMethodException(HttpServletRequest request, HttpServletResponse response, Exception e) {
if (e.getCause() instanceof AuthException) {
// 若為認證異常,則分兩種情況進行處理
if (WebUtil.isAJAX(request)) {
// 若為 AJAX 請求,則傳送 403 錯誤
WebUtil.sendError(403, response);
} else {
// 否則重定向到首頁
WebUtil.redirectRequest(request.getContextPath() + "/", response);
}
} else {
// 若為其他異常,則記錄錯誤日誌
logger.error("呼叫 Action 方法出錯!", e);
}
}
...
}
大家可以看到以上程式碼的【★】處,也就是呼叫 Action 方法時的異常處理程式碼,請見下面的 handleActionMethodException 方法。邏輯如下:
- 首先判斷該異常是否為 AuthException,如果是,則需要分兩種情況來考慮。
- 若為 AJAX 請求,則向 Response 中傳送 403 錯誤程式碼。後面的事情就交給前端的 AJAX 回撥函式來做吧,請見下文。
- 否則(即為普通請求),重定向到首頁,即重定向傳送“/”請求。
- 若不是 AuthException,則輸出錯誤日誌。
以上程式碼中涉及到了幾個關鍵的 WebUtil 方法,其實這些都是對 Servlet API 的一個簡單的封裝。程式碼片段如下:
public class WebUtil {
...
// 重定向請求
public static void redirectRequest(String path, HttpServletResponse response) {
try {
response.sendRedirect(path);
} catch (Exception e) {
logger.error("重定向請求出錯!", e);
throw new RuntimeException(e);
}
}
// 傳送錯誤程式碼
public static void sendError(int code, HttpServletResponse response) {
try {
response.sendError(code);
} catch (Exception e) {
logger.error("傳送錯誤程式碼出錯!", e);
throw new RuntimeException(e);
}
}
// 判斷是否為 AJAX 請求
public static boolean isAJAX(HttpServletRequest request) {
return request.getHeader("X-Requested-With") != null;
}
}
以上可以看到,普通請求非常簡單,直接 redirect 就行了。而對於 AJAX 請求回撥問題,我們可使用 jQuery 來輕鬆實現。
第五步:使用 jQuery 來處理 AJAX 非法訪問
/* 全域性變數 */
var BASE = '/smart-sample'; // 應用 Context 名稱(若為空字串表示應用以 ROOT 來發布)
...
$(function() {
$.ajaxSetup({
cache: false,
error: function(jqXHR, textStatus, errorThrown) {
switch (jqXHR.status) {
case 403:
document.write('');
location.href = BASE + '/';
break;
case 503:
alert(errorThrown);
break;
}
}
});
...
}
首先,定義一個全域性變數 BASE,代表應用的 Context 名稱,使用者可自行修改。
然後,進行 AJAX 全域性設定(使用了 jQuery 的 $.ajaxSetup 方法),在 error 回撥函式中處理 403 錯誤,故意清空頁面內容,並返回到應用首頁(效果與普通請求非法訪問時相同)。
以上就是 Smart Framework 關於安全性控制的解決方案,能否使用更簡單更高效的方式來實現?等候您的評論。
來源:資陽網站建設