在 web 系統中,經(jīng)常需要對每個(gè)頁(yè)面的訪(fǎng)問(wèn)進(jìn)行權限控制。譬如,要進(jìn)入 xx 公司的開(kāi)放 平臺, isv 需要注冊成為開(kāi)發(fā)者,開(kāi)發(fā)者的狀態(tài)有審核中、有效、凍結、拒絕、刪除等狀態(tài),然后根據不同的狀態(tài),開(kāi)發(fā)者可以訪(fǎng)問(wèn)不同的頁(yè)面。只有有效或凍結狀態(tài)可以訪(fǎng)問(wèn)只讀功能的頁(yè)面(即該頁(yè)面的訪(fǎng)問(wèn)不會(huì )造成后臺數據的變化),只有有效狀態(tài)可以訪(fǎng)問(wèn)具有寫(xiě)功能的頁(yè)面。
如何實(shí)現該訪(fǎng)問(wèn)控制的需求呢?最直觀(guān)的做法就是:在每個(gè)頁(yè)面對應的 controller 里,都去調用查詢(xún)開(kāi)發(fā)者的服務(wù),然后判斷開(kāi)發(fā)者的狀態(tài)。
- @Controller
- @RequestMapping("/appDetail.htm")
- public class AppDetailController {
- @RequestMapping(method = RequestMethod.GET)
- public String doGet(ModelMap modelMap, HttpServletRequest httpServletRequest) {
- //1. 開(kāi)發(fā)者有效性判斷
- Developer developer = developerManageServiceClient
- .getByCardNo(cardNo);
- if (null == developer){
- return ERROR_VM;
- }
- if (DeveloperStatus.VALID != developer.getStatus() && DeveloperStatus.FREEZE != developer.getStatus()) {
- return ERROR_VM;
- }
- //2. 業(yè)務(wù)操作,此處省略
- }
- }
-
- @Controller
- @RequestMapping("/appBaseInfoEdit.htm")
- public class AppBaseInfoEditController {
- @RequestMapping(method = RequestMethod.POST)
- public String modify(ModelMap modelMap, HttpServletRequest httpServletRequest, AppBaseInfoForm appBaseInfoForm) {
- //1. 開(kāi)發(fā)者有效性判斷
- Developer developer = developerManageServiceClient
- .getByCardNo(cardNo);
- if (null == developer){
- return ERROR_VM;
- }
- if (DeveloperStatus.VALID != developer.getStatus()) {
- return ERROR_VM;
- }
- //2. 業(yè)務(wù)操作,此處省略
- }
- }
appDetail.htm 對應的頁(yè)面需要開(kāi)發(fā)者的狀態(tài)為有效或者凍結, appBaseInfoEdit.htm 對應的頁(yè)面需要開(kāi)發(fā)者的狀態(tài)為有效。
采用這種方式有以下缺點(diǎn):
- 多個(gè) controller 里實(shí)現同樣的代碼,造成代碼冗余;
- 對于每個(gè) controller 的主體功能來(lái)說(shuō),對開(kāi)發(fā)者狀態(tài)的檢查是一個(gè)橫切關(guān)注點(diǎn),將這種關(guān)注點(diǎn)摻和在主功能里,會(huì )使得主體業(yè)務(wù)邏輯不清晰。
所以,可以基于A(yíng)OP 將這種橫切關(guān)注點(diǎn)以攔截器的方式實(shí)現,但存在的一個(gè)問(wèn)題是,攔截器如何知道某個(gè)頁(yè)面的訪(fǎng)問(wèn)對開(kāi)發(fā)者狀態(tài)的要求呢?可以基于注解實(shí)現。譬如:
- @Controller
- @RequestMapping("/appDetail.htm")
- @Permission(permissionTypes = { PermissionEnum.DEVELOPER_VALID })
- public class AppDetailController {
- @RequestMapping(method = RequestMethod.GET)
- public String doGet(ModelMap modelMap, HttpServletRequest httpServletRequest) {
- //1. 業(yè)務(wù)操作,此處省略
- }
- }
-
- @Controller
- @RequestMapping("/appBaseInfoEdit.htm")
- @Permission(permissionTypes = { PermissionEnum.DEVELOPER_VALID, PermissionEnum.DEVELOPER_FREEZE })
- public class AppBaseInfoEditController {
- @RequestMapping(method = RequestMethod.POST)
- public String modify(ModelMap modelMap, HttpServletRequest httpServletRequest, AppBaseInfoForm appBaseInfoForm) {
- //1. 業(yè)務(wù)操作,此處省略
- }
- }
@Permission(permissionTypes = { PermissionEnum.DEVELOPER_VALID }) ,表示開(kāi)發(fā)者的狀態(tài)必須是有效; @Permission(permissionTypes = { PermissionEnum.DEVELOPER_VALID, PermissionEnum.DEVELOPER_FREEZE }) ,表示開(kāi)發(fā)者的狀態(tài)必須是有效或者凍結。這樣,每個(gè)controller 的主體業(yè)務(wù)邏輯就清晰了。
下面分析一下注解和攔截器是如何實(shí)現的:
注解實(shí)現:
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Permission {
- /** 檢查項枚舉 */
- PermissionEnum[] permissionTypes() default {};
- /** 檢查項關(guān)系 */
- RelationEnum relation() default RelationEnum.OR;
- }
RelationEnum 該枚舉表示各檢查項( permissionTypes )之間的關(guān)系, OR 表示至少需要滿(mǎn)足其中一個(gè)檢查項, AND 表示需要滿(mǎn)足所有檢查項
- /**
- * 權限檢查攔截器
- *
- * @author xianwu.zhang
- * @version $Id: PermissionCheckInterceptor.java, v 0.1 2012-10-25 下午07:48:11 xianwu.zhang Exp $
- */
- public class PermissionCheckInterceptor extends HandlerInterceptorAdapter {
- /** 權限檢查服務(wù) */
- private PermissionCheckProcessor permissionCheckProcessor;
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- Class<?> clazz = handler.getClass();
- if (clazz.isAnnotationPresent(Permission.class)) {
- Permission permission = (Permission) clazz.getAnnotation(Permission.class);
- return permissionCheckProcessor.process(permission, request,response);
- }
- return true;
- }
- }
- * 權限檢查器
- * @author xianwu.zhang
- * @version $Id: PermissionCheckProcessor.java, v 0.1 2012-11-5 下午05:13:17 xianwu.zhang Exp $
- */
- public class PermissionCheckProcessor {
- public boolean process(Permission permission, HttpServletRequest request, HttpServletResponse response) {
- PermissionEnum[] permissionTypes = permission.permissionTypes();
- try {
- String cardNo = OperationContextHolder.getPrincipal().getUserId();
- HttpSession session = request.getSession(false);
- If(null != session){
- //查詢(xún)開(kāi)發(fā)者
- Developer developer = developerManageServiceClient .getByCardNo(cardNo);
- if (null != developer && checkPermission(permissionTypes, permission.relation(),developer .getStatus())) {
- return true;
- }
- }
- sendRedirect(response, ISV_APPLY_URL);
- return false;
- } catch (Exception e) {
- sendRedirect(response, ISV_APPLY_URL);
- return false;
- }
- }
- //省略
- }
-
- private void sendRedirect(HttpServletResponse response, String redirectURI) {
- URIBroker uriBroker = uriBrokerManager.getUriBroker(redirectURI);
- String url = uriBroker.render();
- try {
- response.sendRedirect(url);
- } catch (IOException e) {
- logger.error("轉向頁(yè)面:" + url + "跳轉出錯:", e);
- }
- }
- }
Xml 配置如下:
- <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
- <property name="interceptors">
- <list>
- <ref bean="permissionCheckInterceptor" />
- </list>
- </property>
- </bean>
-
- <bean id="permissionCheckInterceptor"
- class="com.xxx.xxx.web.home.interceptor.PermissionCheckInterceptor" />
- <bean id="permissionCheckProcessor"
- class="com.xxx.xxx.web.home.interceptor.PermissionCheckProcessor" />
還有一個(gè)小細節可以?xún)?yōu)化一下,現在是每訪(fǎng)問(wèn)一個(gè)頁(yè)面,都會(huì )經(jīng)過(guò)這個(gè)攔截器,攔截器里面都有一次 webservice 調用以查詢(xún)開(kāi)發(fā)者信息(開(kāi)發(fā)者 cardNo 和開(kāi)發(fā)者狀態(tài))。但真實(shí)場(chǎng)景中,99.99% 的情況是,用戶(hù)在同一個(gè) session 下完成所有的業(yè)務(wù),即訪(fǎng)問(wèn)的所有頁(yè)面都具有同一個(gè)sessison ,因此可以在開(kāi)發(fā)者第一次訪(fǎng)問(wèn)頁(yè)面時(shí),通過(guò) webservice 調用查詢(xún)到開(kāi)發(fā)者信息,如果權限校驗通過(guò),則將開(kāi)發(fā)者 cardNo 和開(kāi)發(fā)者狀態(tài)放在 session 里,那么在 session 未失效前, 開(kāi)發(fā)者再次訪(fǎng)問(wèn)其他頁(yè)面時(shí),可以在攔截器里先判斷目前登陸的用戶(hù)卡號是否與 session 里存儲的cardNo 相同,如果相同,則就不需要再調用 webserivce 了,可以直接取 session 里存儲的開(kāi)發(fā)者狀態(tài)來(lái)進(jìn)行權限校驗,這樣就減少了大量的不必要的 webservice 調用。
- /**
- * 權限檢查器
- * @author xianwu.zhang
- * @version $Id: PermissionCheckProcessor.java, v 0.1 2012-11-5 下午05:13:17 xianwu.zhang Exp $
- */
- public class PermissionCheckProcessor {
-
- public boolean process(Permission permission, HttpServletRequest request,
- HttpServletResponse response) {
- PermissionEnum[] permissionTypes = permission.permissionTypes();
- try {
- String cardNo = OperationContextHolder.getPrincipal().getUserId();
- HttpSession session = request.getSession(false);
- if (null != session) {
- String developerCardNo = (String) session.getAttribute("developerCardNo");
- if (StringUtil.isNotBlank(cardNo) && StringUtil.equals(cardNo, developerCardNo)) {
- String status = (String) session.getAttribute("status");
- if (checkPermission(permissionTypes, permission.relation(), status)) {
- return true;
- }
- } else {
- Developer developer = developerManageServiceClient .getByCardNo(cardNo);
- if (null != developer
- && checkPermission(permissionTypes, permission.relation(), developer
- .getStatus())) {
- session.setAttribute("status", developer.getStatus());
- session.setAttribute("developerCardNo ", cardNo);
- return true;
- }
- }
- }
- sendRedirect(response, ISV_APPLY_URL);
- return false;
- } catch (Exception e) {
- sendRedirect(response, ISV_APPLY_URL);
- return false;
- }
- }
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請
點(diǎn)擊舉報。