鉴权是每个系统的标配,以前也试过shiro,但总是感觉过于繁琐,不符合自己的操作习惯。
好在JFinal的AOP支持够好,通过Interceptor可以很方便的定义自己的鉴权模型。
我实现的方案包括以下几个部分:
1、AuthRequire:鉴权注解
2、EInterceptorAuth:鉴权拦截器,可定义为全局,或控制器自由引用
3、扩展User Model,增加鉴权相关的具体实现方法
4、控制器层面的鉴权
5、视图模板层面的鉴权
关键部分的源码如下:
/** * 授权验证注解 * * @author netwild * */ public @interface AuthRequire { @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) public static @interface Guest{} @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) public static @interface Logined{} @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) public static @interface Perm{ String value(); } @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) public static @interface Perms{ String[] value(); } @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) public static @interface Role{ String value(); } @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) public static @interface Roles{ String[] value(); } }
/** * 鉴权拦截器 * * @author netwild * */ public class EInterceptorAuth implements Interceptor { public void intercept(Invocation inv) { EController controller = (EController)inv.getController(); HttpServletRequest request = controller.getRequest(); HttpServletResponse response = controller.getResponse(); //返回Cookie中的登录ID,视图层可根据currId是否为null判断当前的登录状态 String currentId = getCurrent(request, response); //根据登录ID,返回用户(User)实例 UserInterface user = getCurrentUser(currentId); boolean authAccept = false; //鉴权结果,默认为失败 Method method = inv.getMethod(); //当前访问的Action /** * 解析鉴权模式: * 1)先判断Action是否要求鉴权; * 2)如果没有,则继续判断Controller是否要求鉴权; * 3)如果仍然没有,则默认允许匿名访问 */ AuthMode mode = new AuthMode(); //鉴权模式对象封装 getMethodAuthMode(mode, method, controller.getClass()); if(mode.authCode == ErrorCode.REQ_GUEST){ //允许匿名 authAccept = true; //鉴权通过 }else if(user != null){ //不允许匿名,且当前已登录 switch(mode.authCode){ case REQ_ROLE: //根据单个角色进行鉴权 if(user.checkRole(mode.authId)) authAccept = true; break; case REQ_ROLES: //根据多个角色进行鉴权 if(user.checkRoles(mode.authIds)) authAccept = true; break; case REQ_PERM: //根据单个权限进行鉴权 if(user.checkPerm(mode.authId)) authAccept = true; break; case REQ_PERMS: //根据多个权限进行鉴权 if(user.checkPerms(mode.authIds)) authAccept = true; break; case REQ_LOGIN: //根据用户的登录状态进行鉴权 authAccept = true; break; default: break; } } //将登录标识ID及用户实例存入会话 //视图中即可使用“user”的系列鉴权方法实现按钮级鉴权 controller.setAttr(Const.ATTR_KEY_CURRENT, currentId); controller.setAttr(Const.ATTR_KEY_USER, user); if(!authAccept){ //身份认证失败 controller.renderErr(mode.authCode, "身份认证失败"); }else{ //认证通过,执行Controller inv.invoke(); } } /** * 优先进行Action层面的鉴权 * @param method * @param ctrl */ private void getMethodAuthMode(AuthMode mode, Method method, Class<?> ctrl){ if(method.isAnnotationPresent(AuthRequire.Role.class)){ mode.authCode = ErrorCode.REQ_ROLE; mode.authId = method.getAnnotation(AuthRequire.Role.class).value(); }else if(method.isAnnotationPresent(AuthRequire.Roles.class)){ mode.authCode = ErrorCode.REQ_ROLES; mode.authIds = method.getAnnotation(AuthRequire.Roles.class).value(); }else if(method.isAnnotationPresent(AuthRequire.Perm.class)){ mode.authCode = ErrorCode.REQ_PERM; mode.authId = method.getAnnotation(AuthRequire.Perm.class).value(); }else if(method.isAnnotationPresent(AuthRequire.Perms.class)){ mode.authCode = ErrorCode.REQ_PERMS; mode.authIds = method.getAnnotation(AuthRequire.Perms.class).value(); }else if(method.isAnnotationPresent(AuthRequire.Logined.class)){ mode.authCode = ErrorCode.REQ_LOGIN; }else if(method.isAnnotationPresent(AuthRequire.Guest.class)){ mode.authCode = ErrorCode.REQ_GUEST; }else{ getControllerAuthMode(mode, ctrl); } } /** * 进行Controller层面的鉴权,只有当Action未设置时有效 * @param ctrl */ private void getControllerAuthMode(AuthMode mode, Class<?> ctrl){ if(ctrl.isAnnotationPresent(AuthRequire.Role.class)){ mode.authCode = ErrorCode.REQ_ROLE; mode.authId = ctrl.getAnnotation(AuthRequire.Role.class).value(); }else if(ctrl.isAnnotationPresent(AuthRequire.Roles.class)){ mode.authCode = ErrorCode.REQ_ROLES; mode.authIds = ctrl.getAnnotation(AuthRequire.Roles.class).value(); }else if(ctrl.isAnnotationPresent(AuthRequire.Perm.class)){ mode.authCode = ErrorCode.REQ_PERM; mode.authId = ctrl.getAnnotation(AuthRequire.Perm.class).value(); }else if(ctrl.isAnnotationPresent(AuthRequire.Perms.class)){ mode.authCode = ErrorCode.REQ_PERMS; mode.authIds = ctrl.getAnnotation(AuthRequire.Perms.class).value(); }else if(ctrl.isAnnotationPresent(AuthRequire.Logined.class)){ mode.authCode = ErrorCode.REQ_LOGIN; }else{ mode.authCode = ErrorCode.REQ_GUEST; } } /** * 从缓存或数据库中根据当前登录ID获取用户对象 * @param currentId * @return */ private UserInterface getCurrentUser(String currentId){ if(EStr.notEmpty(currentId)){ return EZPlatLaunch.getUserService().findById(currentId); }else{ return null; } } /** * 鉴权模式封装 */ class AuthMode{ private ErrorCode authCode = null; //各种鉴权模式枚举 private String authId = null; //单条鉴权标识(单角色、单权限) private String[] authIds = null; //多条鉴权标识(多角色、多权限) } }
/** * 控制器鉴权 * 如果方法中定义了鉴权规则,则被忽略 * 如果方法和控制器都未定义鉴权规则,默认允许匿名 */ @AuthRequire.Role("SuperAdmin") //要求单条角色鉴权 //@AuthRequire.Roles({"UserAdmin", "FileAdmin"}) //要求多条角色授权 //@AuthRequire.Perm("addUser") //要求单条权限鉴权 //@AuthRequire.Perms({"addUser", "delUser"}) //要求多条权限鉴权 //@AuthRequire.Logined //要求登录鉴权 //@AuthRequire.Guest //允许匿名访问 public class OaController extends Controller { /** * 优先执行方法鉴权: * 如果未定义,则执行控制器鉴权 */ @AuthRequire.Role("SuperAdmin") //要求单条角色鉴权 //@AuthRequire.Roles({"UserAdmin", "FileAdmin"}) //要求多条角色授权 //@AuthRequire.Perm("addUser") //要求单条权限鉴权 //@AuthRequire.Perms({"addUser", "delUser"}) //要求多条权限鉴权 //@AuthRequire.Logined //要求登录鉴权 //@AuthRequire.Guest //允许匿名访问 public void addUser(){ //... } }
<!-- 视图模板中的鉴权 其中的"addUser"为权限别名 --> #if(user.checkPerm("addUser")??) <a href="...">添加用户</a> #end
整个鉴权模型非常简单,可定制性也很高,目前为止用的很顺手
希望能给需要的人带来一点启发,也希望大大们提出宝贵建议!