鉴权是每个系统的标配,以前也试过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整个鉴权模型非常简单,可定制性也很高,目前为止用的很顺手
希望能给需要的人带来一点启发,也希望大大们提出宝贵建议!