首页 > 技术文章 > 基于SpringAOP与自定义注解的鉴权功能

tiro8183 2020-10-16 21:08 原文

什么是 AOP

  在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率.--摘自百度百科

  就拿本文主题来举个例子,我写了一段实现鉴权功能的代码,但是在我的项目中有大量的方法在调用前都需要鉴权,我需要怎么做?是在每个方法前面都拷一段鉴权代码吗?显然这不是一个很好的选择.我把鉴权代码单独写成一个类使用@Aspect将其声明为切面,然后使用@Pointcut注解将切面织入到需要鉴权的Class或者Package中,这就是面向切面编程.
  但是上面的方法有一个缺点就是不够灵活,因为我不同的方法可能需要鉴定不同的权限,这里我们再改进一下,将自定义注解作为切点,然后通过在不同的方法上添加自定义注解来实现不同权限的鉴定,这也就是本文的主要内容了.对SpringAOP及自定义注解不熟悉的同学可以先去了解一下,这里就不多介绍了.

基于SpringAOP与自定义注解实现鉴权

基本思路

  • 编写自定义注解作为切点
  • 然后在切面类中编写鉴权逻辑代码
  • 最后在需要鉴权的方法前添加自定义注解

这样在调用添加了鉴权注解的方法时就是先执行鉴权切面类的代码来进行鉴权

鉴权切点类代码示例

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Authority {
	AuthorityType value() default AuthorityType.Validate;

	FuncIdType[] funcIdTypes() default {};

	ModuleIdType[] moduleIdTypes() default {};
}

鉴权切面类代码示例

@Aspect
@Component
public class AuthorityAspect {
	private static Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);

	// 日志切点
	@Pointcut("@annotation(com.hzjd.jdmp.annotation.Authority)")
	public void executeService() {
	}
	//	验证是否具有该功能的权限
	private boolean validateFunc(FuncIdType[] funcIdTypes) {
		HttpSession session = ActionStackManager.getSession();
		SessionStackData sessionStackData = SessionStackData.getSessionStackData(session);
		if (sessionStackData.getRl() == null) {
			return false;
		}
		for (int i = 0; i < funcIdTypes.length; i++) {
			if (sessionStackData.getRl().haveRight(funcIdTypes[i])) {
				return true;
			}
		}
		return false;
	}

	//	验证是否具有该模块的权限
	private boolean validateModule(ModuleIdType[] moduleIdTypes) {
		HttpSession session = ActionStackManager.getSession();
		SessionStackData sessionStackData = SessionStackData.getSessionStackData(session);
		if (sessionStackData.getRm() == null) {
			return false;
		}
		for (int i = 0; i < moduleIdTypes.length; i++) {
			if (sessionStackData.getRm().haveRight(moduleIdTypes[i])) {
				return true;
			}
		}
		return false;
	}

	private Object toError(Boolean responseBody) {
		if (responseBody) {
			Map<String, Object> map = new HashMap<String, Object>();
			map.put("success", false);
			map.put("msg", "您没有操作此功能的权限");
			String json = JsonUtil.toJsonObject(map).toString();
			HttpServletResponse response = ActionStackManager.getResponse();
			response.setCharacterEncoding("UTF-8");
			response.setContentType("application/json; charset=utf-8");
			try {
				response.getWriter().write(json);
			} catch (IOException e) {
				e.printStackTrace();
			}
			return null;
		} else {
			return "errauth";
		}
	}

	private Object toLogin() {
		HttpServletResponse response = ActionStackManager.getResponse();
		String loginUrl = ActionStackManager.getRequest().getContextPath() + "/index";
		String str = "<script language='javascript'>alert('会话过期,请重新登录');" + "window.top.location.href='" + loginUrl
				+ "';</script>";
		response.setContentType("text/html;charset=UTF-8");// 解决中文乱码
		PrintWriter writer;
		try {
			writer = response.getWriter();
			writer.write(str);
			writer.flush();
		} catch (IOException e) {
		}
		return null;
	}

	@Around(value = "executeService()")
	public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		Signature signature = proceedingJoinPoint.getSignature();
		MethodSignature methodSignature = (MethodSignature) signature;
		Method targetMethod = methodSignature.getMethod();
		Method realMethod = proceedingJoinPoint.getTarget().getClass()
				.getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());
		Authority authority = targetMethod.getAnnotation(Authority.class);
		Class<?> clazz = targetMethod.getClass();
		logger.debug("realMethod:" + realMethod.getName());
		logger.debug("realMethod Class:" + realMethod.getDeclaringClass().getSimpleName());
		boolean pass = false;
		boolean responseBody = targetMethod.isAnnotationPresent(ResponseBody.class);
		if (clazz != null && targetMethod != null) {
			if (authority != null) {
				if (AuthorityType.NoValidate == authority.value()) {
					logger.debug("标记为不验证,放行");
					// 标记为不验证,放行
					pass = true;
				} else if (AuthorityType.NoAuthority == authority.value()) {
					logger.debug("不验证权限,验证是否登录");
					// 不验证权限,验证是否登录
					pass = BaseSearchForm.getLoginUser() != null;
					if (!pass) {
						return toLogin();
					}
				} else {
					if (BaseSearchForm.getLoginUser() != null) {
						logger.debug("正在验证权限");
						if (authority.funcIdTypes() != null && authority.funcIdTypes().length > 0) {
							pass = validateFunc(authority.funcIdTypes());
						}
						if (!pass) {
							if (authority.moduleIdTypes() != null && authority.moduleIdTypes().length > 0) {
								pass = validateModule(authority.moduleIdTypes());
							}
						}
					} else {
						return toLogin();
					}
				}
			}
		}
		if (!pass) {
			return toError(responseBody);
		} else {
			logger.debug("验证权限通过");
		}
		Object obj = proceedingJoinPoint.proceed();
		return obj;
	}
}

实现方法示例

@RequestMapping(value = "/updateOwerPwd")
@SystemLogAnnotation
@Authority(value = AuthorityType.NoAuthority)
public String updateOwerPwd(ModelMap model, HttpServletRequest request, HttpServletResponse response)
        throws Exception {
    ActionStackManager.initNewData(model, request, response);
    try {
        HttpSession session = ActionStackManager.getRequest().getSession();
        SysUser sysUser = SessionStackData.getSessionStackData(session).getSysUser();
        if (sysUser == null) {
            session.invalidate();
            return forward("login");
        }
        sysUser = sysUserService.findById(sysUser.getUserid());
        if (sysUser == null) {
            session.invalidate();
            return forward("login");
        }
        String oldpassword = getRequestParameter("oldpwd");
        String password = getRequestParameter("pwd1");
        if (!sysUser.getPassword().equals(MD5Utils.getBASE64MD5(oldpassword)))
            return forwardError("旧密码输入不正确,修改密码失败!");
        sysUser.setPassword(MD5Utils.getBASE64MD5(password));
        if (!sysUserService.update(sysUser))
            return forwardError("修改密码失败!");
        return forward("includes/main");
    } catch (Exception e) {
        logError(e);
        return forwardError(e);
    } finally {
        ActionStackManager.removeThreadData();
    }
}

推荐阅读