实现功能权限校验的功能有多种方法,其一使用拦截器拦截请求,其二是使用AOP抛异常。
首先用拦截器实现未登录时跳转到登录界面的功能。注意这里没有使用AOP切入,而是用拦截器拦截,因为AOP一般切入的是service层方法,而拦截器是拦截控制器层的请求,它本身也是一个处理器,可以直接中断请求的传递并返回视图,而AOP则不可以。
1.使用拦截器实现未登录时跳转到登录界面的功能
1.1 拦截器SecurityInterceptor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
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(拦截器配置部分)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<!-- 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 等等单层结构的请求 --> < mvc:mapping path = "/**/*.aspx" /> <!-- 拦截后缀为.aspx的请求 --> < mvc:mapping path = "/**/*.do" /> <!-- 拦截后缀为 .do的请求 --> < 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 > |
这里需要特别说明:拦截器拦截的路径最好是带有后缀名的,否则一些静态的资源文件不好控制,也就是说请求最好有一个统一的格式如 .do 等等,这样匹配与过滤速度会非常快。如果不这样,例如 用 /** 来拦截所有的请求,则页面渲染速度会非常慢,因为资源文件也被拦截了。
2.使用AOP实现功能权限校验
对于功能权限校验也可以类似地用拦截器来实现,只不过会拦截所有的请求,对不需要权限校验的请求没有很好的过滤功能,所以采用AOP指定拦截需要校验的方法的方式来实现之。
2.1 切面类 PermissionAspect
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
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注解 控制器 都要进行 功能权限检查, * 若无权限,则抛出AccessDeniedException 异常,该异常将请求转发至一个控制器,然后将异常结果返回 * @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( "您无权操作!" ); } 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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该注解是标签型注解,被此注解标注的方法需要进行权限校验 */ @Target (value = ElementType.METHOD) @Retention (value = RetentionPolicy.RUNTIME) @Documented public @interface ValidatePermission { /** * @Description功能Id的参数索引位置 默认为0,表示功能id在第一个参数的位置上,-1则表示未提供,无法进行校验 */ int idx() default 0 ; } |
说明: AOP切入的是方法,不是某个控制器请求,所以不能直接返回视图来中断该方法的请求,但可以通过抛异常的方式达到中断方法执行的目的,所以在before通知中,如果通过验证直接return返回继续执行连接点方法,否则抛出一个自定义异常AccessDeniedException来中断连接点方法的执行。该异常的捕获可以通过系统的异常处理器(可以看做控制器)来捕获并跳转到一个视图或者一个请求。这样就达到拦截请求的目的。所以需要配置异常处理器。
2.3 spring-mvc.xml(异常处理器配置,以及aop配置)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
< 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" /> <!-- 对带有@ValidatePermission和ResponseBody注解的controller包及其子包所有方法执行功能权限校验 --> < 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 注解需要进行功能校验的控制器请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@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 , "请求参数错误:action" )); } } catch (Exception e){ e.printStackTrace(); return JSON.toJSONString( new Result( false , "操作失败,出现异常,请联系管理员!" )); } if (rs< 0 ){ return JSON.toJSONString( new Result( false , "操作失败,请联系管理员!" )); } return JSON.toJSONString( new Result( true ,module)); } |
2.5 异常处理器将请求转发到的控制器请求 forward:/accessDenied
1
2
3
4
5
|
@RequestMapping (value = "/accessDenied" ,produces = "text/html;charset=UTF-8" ) @ResponseBody public String accessDenied(){ return JSON.toJSONString( new Result( false , "您没有权限对此进行操作!" )); } |
2.6 请求校验不通过时 由上述的控制器返回 结果本身
如下所示:
{"info":"您没有权限对此进行操作!","success":false}
2.7 功能校验service 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/** * 校验当前用户在某个模块的某个功能的权限 * @param functionId * @return 空字符串表示 有权限 ,否则是错误信息 * @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 "数据库操作出现异常!" ; } } |
说明: 这里仅仅是对带有@ValidatePermission和@ResponseBody注解的controller包及其子包所有方法进行切入,这样肯定是不够通用的,应该是对带有@ValidatePermission的方法进行切入,在切面类中通过判断该方法是否有@ResponseBody注解来抛出不一样的异常,若带有@ResponseBody注解则抛出上述的异常返回json字符串,
否则,应该抛出另一个自定义异常然后将请求重定向到一个合法的视图如error.jsp .
通过客户端发送 /moduleAccess.do 请求,该请求对应的方法同时具有@ValidatePermission和@ResponseBody,并且有功能Id参数fid,这样AOP可以切入该方法,执行doBefore通知,通过功能参数fid,对它结合用户id进行权限校验,若校验通过直接返回,程序继续执行,否则抛出自定义异常AccessDeniedException,该异常由系统捕获(需要配置异常处理器)并发出请求 forward:/accessDenied ,然后对应的控制器 /accessDenied 处理该请求返回一个包含校验失败信息的json给客户端。这样发送 /moduleAccess.do 请求,如果校验失败,转发到了/accessDenied请求,否则正常执行。绕了这么一个大圈子才实现它。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:http://blog.csdn.net/houxuehan/article/details/51745175