Spring AOP實(shí)現(xiàn)功能權(quán)限校驗(yàn)功能的示例代碼
實(shí)現(xiàn)功能權(quán)限校驗(yàn)的功能有多種方法,其一使用攔截器攔截請(qǐng)求,其二是使用AOP拋異常。
首先用攔截器實(shí)現(xiàn)未登錄時(shí)跳轉(zhuǎn)到登錄界面的功能。注意這里沒(méi)有使用AOP切入,而是用攔截器攔截,因?yàn)锳OP一般切入的是service層方法,而攔截器是攔截控制器層的請(qǐng)求,它本身也是一個(gè)處理器,可以直接中斷請(qǐng)求的傳遞并返回視圖,而AOP則不可以。
1.使用攔截器實(shí)現(xiàn)未登錄時(shí)跳轉(zhuǎn)到登錄界面的功能
1.1 攔截器SecurityInterceptor
package com.jykj.demo.filter;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.alibaba.fastjson.JSON;
import com.jykj.demo.util.Helper;
import com.jykj.demo.util.Result;
public class SecurityInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("SecurityInterceptor:"+request.getContextPath()+","+request.getRequestURI()+","+request.getMethod());
HttpSession session = request.getSession();
if (session.getAttribute(Helper.SESSION_USER) == null) {
System.out.println("AuthorizationException:未登錄!"+request.getMethod());
if("POST".equalsIgnoreCase(request.getMethod())){
response.setContentType("text/html; charset=utf-8");
PrintWriter out = response.getWriter();
out.write(JSON.toJSONString(new Result(false,"未登錄!")));
out.flush();
out.close();
}else{
response.sendRedirect(request.getContextPath()+"/login");
}
return false;
} else {
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
}
}
1.2.spring-mvc.xml(攔截器配置部分)
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<mvc:resources mapping="/resources/**" location="/resources/" />
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*"/> <!-- 攔截/ /test /login 等等單層結(jié)構(gòu)的請(qǐng)求 -->
<mvc:mapping path="/**/*.aspx"/><!-- 攔截后綴為.aspx的請(qǐng)求 -->
<mvc:mapping path="/**/*.do"/><!-- 攔截后綴為 .do的請(qǐng)求 -->
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/signIn"/>
<mvc:exclude-mapping path="/register"/>
<bean class="com.jykj.demo.filter.SecurityInterceptor">
</bean>
</mvc:interceptor>
</mvc:interceptors>
這里需要特別說(shuō)明:攔截器攔截的路徑最好是帶有后綴名的,否則一些靜態(tài)的資源文件不好控制,也就是說(shuō)請(qǐng)求最好有一個(gè)統(tǒng)一的格式如 .do 等等,這樣匹配與過(guò)濾速度會(huì)非???。如果不這樣,例如 用 /** 來(lái)攔截所有的請(qǐng)求,則頁(yè)面渲染速度會(huì)非常慢,因?yàn)橘Y源文件也被攔截了。
2.使用AOP實(shí)現(xiàn)功能權(quán)限校驗(yàn)
對(duì)于功能權(quán)限校驗(yàn)也可以類(lèi)似地用攔截器來(lái)實(shí)現(xiàn),只不過(guò)會(huì)攔截所有的請(qǐng)求,對(duì)不需要權(quán)限校驗(yàn)的請(qǐng)求沒(méi)有很好的過(guò)濾功能,所以采用AOP指定攔截需要校驗(yàn)的方法的方式來(lái)實(shí)現(xiàn)之。
2.1 切面類(lèi) PermissionAspect
package com.jykj.demo.filter;
import java.io.IOException;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import com.jykj.demo.annotation.ValidatePermission;
import com.jykj.demo.exception.AccessDeniedException;
import com.jykj.demo.service.SysUserRolePermService;
/**
* 事件日志 切面,凡是帶有 @ValidatePermission 以及@ResponseBody注解 控制器 都要進(jìn)行 功能權(quán)限檢查,
* 若無(wú)權(quán)限,則拋出AccessDeniedException 異常,該異常將請(qǐng)求轉(zhuǎn)發(fā)至一個(gè)控制器,然后將異常結(jié)果返回
* @author Administrator
*
*/
public class PermissionAspect {
@Autowired
SysUserRolePermService sysUserRolePermService;
public void doBefore(JoinPoint jp) throws IOException{
System.out.println(
"log PermissionAspect Before method: " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName());
Method soruceMethod = getSourceMethod(jp);
if(soruceMethod!=null){
ValidatePermission oper = soruceMethod.getAnnotation(ValidatePermission.class);
if (oper != null) {
int fIdx = oper.idx();
Object[] args = jp.getArgs();
if (fIdx>= 0 &&fIdx<args.length){
int functionId = (Integer) args[fIdx];
String rs = sysUserRolePermService.permissionValidate(functionId);
System.out.println("permissionValidate:"+rs);
if(rs.trim().isEmpty()){
return ;//正常
}
}
}
}
throw new AccessDeniedException("您無(wú)權(quán)操作!");
}
private Method getSourceMethod(JoinPoint jp){
Method proxyMethod = ((MethodSignature) jp.getSignature()).getMethod();
try {
return jp.getTarget().getClass().getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
return null;
}
}
2.2自定義注解ValidatePermission
package com.jykj.demo.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Descrption該注解是標(biāo)簽型注解,被此注解標(biāo)注的方法需要進(jìn)行權(quán)限校驗(yàn)
*/
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface ValidatePermission {
/**
* @Description功能Id的參數(shù)索引位置 默認(rèn)為0,表示功能id在第一個(gè)參數(shù)的位置上,-1則表示未提供,無(wú)法進(jìn)行校驗(yàn)
*/
int idx() default 0;
}
說(shuō)明: AOP切入的是方法,不是某個(gè)控制器請(qǐng)求,所以不能直接返回視圖來(lái)中斷該方法的請(qǐng)求,但可以通過(guò)拋異常的方式達(dá)到中斷方法執(zhí)行的目的,所以在before通知中,如果通過(guò)驗(yàn)證直接return返回繼續(xù)執(zhí)行連接點(diǎn)方法,否則拋出一個(gè)自定義異常AccessDeniedException來(lái)中斷連接點(diǎn)方法的執(zhí)行。該異常的捕獲可以通過(guò)系統(tǒng)的異常處理器(可以看做控制器)來(lái)捕獲并跳轉(zhuǎn)到一個(gè)視圖或者一個(gè)請(qǐng)求。這樣就達(dá)到攔截請(qǐng)求的目的。所以需要配置異常處理器。
2.3 spring-mvc.xml(異常處理器配置,以及aop配置)
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- <property name="defaultErrorView" value="rediret:/error"></property> -->
<property name="exceptionMappings">
<props>
<!--<prop key="com.jykj.demo.exception.AuthorizationException">redirect:/login</prop>-->
<prop key="com.jykj.demo.exception.AccessDeniedException">forward:/accessDenied</prop>
</props>
</property>
</bean>
<bean id="aspectPermission" class="com.jykj.demo.filter.PermissionAspect" />
<!-- 對(duì)帶有@ValidatePermission和ResponseBody注解的controller包及其子包所有方法執(zhí)行功能權(quán)限校驗(yàn) -->
<aop:config proxy-target-class="true">
<aop:aspect ref="aspectPermission">
<aop:pointcut id="pc"
expression="@annotation(com.jykj.demo.annotation.ValidatePermission)
and @annotation(org.springframework.web.bind.annotation.ResponseBody)
and execution(* com.jykj.demo.controller..*.*(..)) " />
<aop:before pointcut-ref="pc" method="doBefore"/>
</aop:aspect>
</aop:config>
2.4 注解需要進(jìn)行功能校驗(yàn)的控制器請(qǐng)求
@RequestMapping(value = "/moduleAccess.do", method = RequestMethod.POST, produces="text/html;charset=utf-8")
@ResponseBody
@ValidatePermission
public String moduleAccess(int fid,String action,FrmModule module) {
System.out.println("fid:"+fid+",action:"+action);
int rs = -1;
try{
if(Helper.F_ACTION_CREATE.equals(action)){
rs = moduleService.access(module,Helper.DB_ACTION_INSERT);
//module.setModuleid(rs);
module = moduleService.selectByPrimaryKey(rs);
}else if(Helper.F_ACTION_EDIT.equals(action)){
rs = moduleService.access(module,Helper.DB_ACTION_UPDATE);
module = moduleService.selectByPrimaryKey(module.getModuleid());
}else if(Helper.F_ACTION_REMOVE.equals(action)){
rs = moduleService.access(module,Helper.DB_ACTION_DELETE);
}else{
return JSON.toJSONString(new Result(false,"請(qǐng)求參數(shù)錯(cuò)誤:action"));
}
}catch(Exception e){
e.printStackTrace();
return JSON.toJSONString(new Result(false,"操作失敗,出現(xiàn)異常,請(qǐng)聯(lián)系管理員!"));
}
if(rs<0){
return JSON.toJSONString(new Result(false,"操作失敗,請(qǐng)聯(lián)系管理員!"));
}
return JSON.toJSONString(new Result(true,module));
}
2.5 異常處理器將請(qǐng)求轉(zhuǎn)發(fā)到的控制器請(qǐng)求 forward:/accessDenied
@RequestMapping(value = "/accessDenied",produces = "text/html;charset=UTF-8")
@ResponseBody
public String accessDenied(){
return JSON.toJSONString(new Result(false,"您沒(méi)有權(quán)限對(duì)此進(jìn)行操作!"));
}
2.6 請(qǐng)求校驗(yàn)不通過(guò)時(shí) 由上述的控制器返回 結(jié)果本身
如下所示:
{"info":"您沒(méi)有權(quán)限對(duì)此進(jìn)行操作!","success":false}
2.7 功能校驗(yàn)service 示例
/**
* 校驗(yàn)當(dāng)前用戶(hù)在某個(gè)模塊的某個(gè)功能的權(quán)限
* @param functionId
* @return 空字符串表示 有權(quán)限 ,否則是錯(cuò)誤信息
* @throws Exception
*/
public String permissionValidate(int functionId){
Object o = request.getSession().getAttribute(Helper.SESSION_USER);
//if(o==null) throw new AuthorizationException();
SysUser loginUser= (SysUser)o;
if(loginUser.getUserid() == 1) return "";
try{
return mapper.permissionValidate(loginUser.getUserid(),functionId);
}catch(Exception ex){
ex.printStackTrace();
return "數(shù)據(jù)庫(kù)操作出現(xiàn)異常!";
}
}
說(shuō)明: 這里僅僅是對(duì)帶有@ValidatePermission和@ResponseBody注解的controller包及其子包所有方法進(jìn)行切入,這樣肯定是不夠通用的,應(yīng)該是對(duì)帶有@ValidatePermission的方法進(jìn)行切入,在切面類(lèi)中通過(guò)判斷該方法是否有@ResponseBody注解來(lái)拋出不一樣的異常,若帶有@ResponseBody注解則拋出上述的異常返回json字符串,
否則,應(yīng)該拋出另一個(gè)自定義異常然后將請(qǐng)求重定向到一個(gè)合法的視圖如error.jsp .
通過(guò)客戶(hù)端發(fā)送 /moduleAccess.do 請(qǐng)求,該請(qǐng)求對(duì)應(yīng)的方法同時(shí)具有@ValidatePermission和@ResponseBody,并且有功能Id參數(shù)fid,這樣AOP可以切入該方法,執(zhí)行doBefore通知,通過(guò)功能參數(shù)fid,對(duì)它結(jié)合用戶(hù)id進(jìn)行權(quán)限校驗(yàn),若校驗(yàn)通過(guò)直接返回,程序繼續(xù)執(zhí)行,否則拋出自定義異常AccessDeniedException,該異常由系統(tǒng)捕獲(需要配置異常處理器)并發(fā)出請(qǐng)求 forward:/accessDenied ,然后對(duì)應(yīng)的控制器 /accessDenied 處理該請(qǐng)求返回一個(gè)包含校驗(yàn)失敗信息的json給客戶(hù)端。這樣發(fā)送 /moduleAccess.do 請(qǐng)求,如果校驗(yàn)失敗,轉(zhuǎn)發(fā)到了/accessDenied請(qǐng)求,否則正常執(zhí)行。繞了這么一個(gè)大圈子才實(shí)現(xiàn)它。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- SpringBoot使用AOP實(shí)現(xiàn)統(tǒng)一角色權(quán)限校驗(yàn)
- SpringBoot中使用AOP實(shí)現(xiàn)日志記錄功能
- SpringBoot使用AOP實(shí)現(xiàn)日志記錄功能詳解
- Spring AOP如何自定義注解實(shí)現(xiàn)審計(jì)或日志記錄(完整代碼)
- 在springboot中使用AOP進(jìn)行全局日志記錄
- Spring AOP實(shí)現(xiàn)復(fù)雜的日志記錄操作(自定義注解)
- SpringAop實(shí)現(xiàn)操作日志記錄
- springMVC自定義注解,用AOP來(lái)實(shí)現(xiàn)日志記錄的方法
- 使用Spring AOP做接口權(quán)限校驗(yàn)和日志記錄
相關(guān)文章
基于Java反射的map自動(dòng)裝配JavaBean工具類(lèi)設(shè)計(jì)示例代碼
這篇文章主要給大家介紹了關(guān)于基于Java反射的map自動(dòng)裝配JavaBean工具類(lèi)設(shè)計(jì)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧2018-10-10
IDEA集成git和使用步驟的實(shí)現(xiàn)方法
這篇文章主要介紹了IDEA集成git和使用步驟的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Spring?Boot中@Import三種使用方式實(shí)例詳解
這篇文章主要介紹了Spring?Boot中@Import三種使用方式,主要有引入普通類(lèi),引入importSelector的實(shí)現(xiàn)類(lèi)及引入importBeanDefinitionRegister的實(shí)現(xiàn)類(lèi),結(jié)合實(shí)例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下2022-11-11
解決IDEA?JDK9沒(méi)有module-info.java的問(wèn)題
這篇文章主要介紹了解決IDEA?JDK9沒(méi)有module-info.java的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
如何在Spring中使用編碼方式動(dòng)態(tài)配置Bean詳解
這篇文章主要給大家介紹了關(guān)于如何在Spring中使用編碼方式動(dòng)態(tài)配置Bean的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05

