给JFinal添加Shiro插件功能,支持Shiro全部注解-实现篇

2015年8月3日更新:java

支持JFinal 2.0 版本,同时给出了一些实际代码,想见gitgit

@JFinal给出了一些好的建议,已重构部分代码。apache

代码放在oschina的git上,访问地址:app

http://git.oschina.net/myaniu/jfinalshiroplugin函数

最近用JFinal作个东西,须要进行较为精细的权限控制,研究后决定用Shiro来实现。因而给JFinal作了一个插件。性能

作了两版,初版采用Shiro自己的aop来作,使用拦截器实现,每次请求都要检查注解,根据注解构建5个访问控制拦截器进行处理。仔细研究下,以为每一个请求须要处理的访问控制注解在系统启动时应该能所有得到,何不在启动时构建好。这样性能会好一些。Shiro原有的一套处理不适合在启动时构建好,因而从新设计了,可是具体处理逻辑仍是来自Shiro,代码直接搬过来。放代码。ui

1)改造JFinal类,增长一个得到Routes的方法。this

 

2)定义访问控制检查接口.net

package com.jfinal.ext.plugin.shiro;

import org.apache.shiro.authz.AuthorizationException;

/**
 * 访问控制处理器接口
 * @author dafei
 *
 */
interface AuthzHandler {
	/**
	 * 访问控制检查
	 * @throws AuthorizationException 受权异常
	 */
	public void assertAuthorized()throws AuthorizationException;
}

3)定义访问控制抽象基类。插件

abstract class AbstractAuthzHandler implements AuthzHandler {

	/**
	 * 得到Shiro的Subject对象。
	 * @return
	 */
	 protected Subject getSubject() {
	     return SecurityUtils.getSubject();
	 }
}

4)定义五种权限检查处理器。

/**
 * 基于角色的访问控制处理器,非单例模式运行。
 * @author dafei
 *
 */
class RoleAuthzHandler extends AbstractAuthzHandler {

	private final Annotation annotation;

	public RoleAuthzHandler(Annotation annotation){
		this.annotation = annotation;
	}

	public void assertAuthorized() throws AuthorizationException {
		//if (!(annotation instanceof RequiresRoles)) return;
        RequiresRoles rrAnnotation = (RequiresRoles) annotation;
        String[] roles = rrAnnotation.value();

        if (roles.length == 1) {
            getSubject().checkRole(roles[0]);
            return;
        }
        if (Logical.AND.equals(rrAnnotation.logical())) {
            getSubject().checkRoles(Arrays.asList(roles));
            return;
        }
        if (Logical.OR.equals(rrAnnotation.logical())) {
            // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
            boolean hasAtLeastOneRole = false;
            for (String role : roles) if (getSubject().hasRole(role)) hasAtLeastOneRole = true;
            // Cause the exception if none of the role match, note that the exception message will be a bit misleading
            if (!hasAtLeastOneRole) getSubject().checkRole(roles[0]);
        }
	}
}
/**
 * 基于权限的访问控制处理器,非单例模式运行。
 * @author dafei
 *
 */
class PermissionAuthzHandler extends AbstractAuthzHandler {
	private final Annotation annotation;

	public PermissionAuthzHandler(Annotation annotation) {
		this.annotation = annotation;
	}

	public void assertAuthorized() throws AuthorizationException {
		if (!(annotation instanceof RequiresPermissions))
			return;

		RequiresPermissions rpAnnotation = (RequiresPermissions) annotation;
		String[] perms = rpAnnotation.value();
		Subject subject = getSubject();

		if (perms.length == 1) {
			subject.checkPermission(perms[0]);
			return;
		}
		if (Logical.AND.equals(rpAnnotation.logical())) {
			getSubject().checkPermissions(perms);
			return;
		}
		if (Logical.OR.equals(rpAnnotation.logical())) {
			// Avoid processing exceptions unnecessarily - "delay" throwing the
			// exception by calling hasRole first
			boolean hasAtLeastOnePermission = false;
			for (String permission : perms)
				if (getSubject().isPermitted(permission))
					hasAtLeastOnePermission = true;
			// Cause the exception if none of the role match, note that the
			// exception message will be a bit misleading
			if (!hasAtLeastOnePermission)
				getSubject().checkPermission(perms[0]);

		}

	}

}
/**
 * 已认证经过访问控制处理器
 * 单例模式运行。
 *
 * @author dafei
 *
 */
class AuthenticatedAuthzHandler extends AbstractAuthzHandler {

	private static AuthenticatedAuthzHandler aah = new AuthenticatedAuthzHandler();

	private AuthenticatedAuthzHandler(){}

	public static  AuthenticatedAuthzHandler me(){
		return aah;
	}

	public void assertAuthorized() throws AuthorizationException {
		if (!getSubject().isAuthenticated() ) {
            throw new UnauthenticatedException( "The current Subject is not authenticated.  Access denied." );
        }
	}
}
/**
 * 认证经过或已记住的用户访问控制处理器
 * 单例模式运行。
 * @author dafei
 *
 */
class UserAuthzHandler extends AbstractAuthzHandler {
	private static UserAuthzHandler uah = new UserAuthzHandler();

	private UserAuthzHandler(){}

	public static  UserAuthzHandler me(){
		return uah;
	}

	public void assertAuthorized() throws AuthorizationException {
		if (getSubject().getPrincipal() == null) {
            throw new UnauthenticatedException("Attempting to perform a user-only operation.  The current Subject is " +
                    "not a user (they haven't been authenticated or remembered from a previous login).  " +
                    "Access denied.");
        }
	}
}
/**
 * 访客访问控制处理器
 * @author dafei
 *
 */
class GuestAuthzHandler extends AbstractAuthzHandler {
	private static GuestAuthzHandler gah = new GuestAuthzHandler();

	private GuestAuthzHandler(){}

	public static  GuestAuthzHandler me(){
		return gah;
	}

	public void assertAuthorized() throws AuthorizationException {
		 if (getSubject().getPrincipal() != null) {
	            throw new UnauthenticatedException("Attempting to perform a guest-only operation.  The current Subject is " +
	                    "not a guest (they have been authenticated or remembered from a previous login).  Access " +
	                    "denied.");
	        }
	}

}

5)定义一个组合访问处理器,用来统一几个处理器。

class CompositeAuthzHandler implements AuthzHandler {

	private final List<AuthzHandler> authzHandlers;

	public CompositeAuthzHandler(List<AuthzHandler> authzHandlers){
		this.authzHandlers = authzHandlers;
	}

	public void assertAuthorized() throws AuthorizationException {
		for(AuthzHandler authzHandler : authzHandlers){
			authzHandler.assertAuthorized();
		}
	}
}

6)定义了一个注解,用来清除权限注解(主要用来清除Controller上的访问控制注解)

/**
 * 用来清除全部的Shiro访问控制注解,适合于Controller绝大部分方法都须要作访问控制,个别不须要作访问控制的场合。
 * 仅能用在方法上。
 * @author dafei
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ClearShiro {
}

7)实现ShiroPlugin方法

package com.jfinal.ext.plugin.shiro;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;

import com.jfinal.config.Routes;
import com.jfinal.core.ActionKey;
import com.jfinal.core.Controller;
import com.jfinal.core.JFinal;
import com.jfinal.plugin.IPlugin;

/**
 * Shiro插件,启动时加载全部Shiro访问控制注解。
 * @author dafei
 *
 */
@SuppressWarnings("unchecked")
public class ShiroPlugin implements IPlugin {

	private static final String SLASH = "/";

	/**
	 * Shiro的几种访问控制注解
	 */
	private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES = new Class[] {
			RequiresPermissions.class, RequiresRoles.class, RequiresUser.class,
			RequiresGuest.class, RequiresAuthentication.class };

/**
 * 路由设定
 */
 private final Routes routes;


 /**
 * 构造函数
 * @param routes 路由设定
 */
 public ShiroPlugin(Routes routes){
 this.routes = routes;
 }

	/**
	 * 中止插件
	 */
	public boolean stop() {
		return true;
	}

	/**
	 * 启动插件
	 */
	public boolean start() {
		//获取全部路由设定。这里修改了JFinal类,增长了一个getRoutes方法。
		//Routes routes = JFinal.me().getRoutes();
		Set<String> excludedMethodName = buildExcludedMethodName();
		ConcurrentMap<String, AuthzHandler> authzMaps = new ConcurrentHashMap<String, AuthzHandler>();
		//逐个访问全部注册的Controller,解析Controller及action上的全部Shiro注解。
		//并依据这些注解,actionKey提早构建好权限检查处理器。
		for (Entry<String, Class<? extends Controller>> entry : routes
				.getEntrySet()) {
			Class<? extends Controller> controllerClass = entry.getValue();

			// 获取Controller的全部Shiro注解。
			List<Annotation> controllerAnnotations = getAuthzAnnotations(controllerClass);
                        String controllerKey = entry.getKey();
			// 逐个遍历方法。
			Method[] methods = controllerClass.getMethods();
			for (Method method : methods) {
				//排除掉Controller基类的全部方法,而且只关注没有参数的Action方法。
				if (!excludedMethodName.contains(method.getName())
						&& method.getParameterTypes().length == 0) {
					//若该方法上存在ClearShiro注解,则对该action不进行访问控制检查。
					if(isClearShiroAnnotationPresent(method)){
						continue;
					}
					//获取方法的全部Shiro注解。
					List<Annotation> methodAnnotations = getAuthzAnnotations(method);
					//依据Controller的注解和方法的注解来生成访问控制处理器。
					AuthzHandler authzHandler = createAuthzHandler(
							controllerAnnotations, methodAnnotations);
					//生成访问控制处理器成功。
					if (authzHandler != null) {
						//构建ActionKey,参考ActionMapping中实现
						String actionKey = createActionKey(controllerClass,
								method,controllerKey);
						//添加映射
						authzMaps.put(actionKey, authzHandler);
					}
				}
			}
		}
		//注入到ShiroKit类中。ShiroKit类以单例模式运行。
		ShiroKit.init(authzMaps);
		return true;
	}

	/**
	 * 从Controller方法中构建出须要排除的方法列表
	 * @return
	 */
	private Set<String> buildExcludedMethodName() {
		Set<String> excludedMethodName = new HashSet<String>();
		Method[] methods = Controller.class.getMethods();
		for (Method m : methods) {
			if (m.getParameterTypes().length == 0)
				excludedMethodName.add(m.getName());
		}
		return excludedMethodName;
	}

	/**
	 * 依据Controller的注解和方法的注解来生成访问控制处理器。
	 * @param controllerAnnotations  Controller的注解
	 * @param methodAnnotations 方法的注解
	 * @return 访问控制处理器
	 */
	private AuthzHandler createAuthzHandler(
			List<Annotation> controllerAnnotations,
			List<Annotation> methodAnnotations) {

		//没有注解
		if (controllerAnnotations.size() == 0 && methodAnnotations.size() == 0) {
			return null;
		}
		//至少有一个注解
		List<AuthzHandler> authzHandlers = new ArrayList<AuthzHandler>(5);
		for (int index = 0; index < 5; index++) {
			authzHandlers.add(null);
		}

		// 逐个扫描注解,如果相应的注解则在相应的位置赋值。
		scanAnnotation(authzHandlers, controllerAnnotations);
		// 逐个扫描注解,如果相应的注解则在相应的位置赋值。函数的注解优先级高于Controller
		scanAnnotation(authzHandlers, methodAnnotations);

		// 去除空值
		List<AuthzHandler> finalAuthzHandlers = new ArrayList<AuthzHandler>();
		for (AuthzHandler a : authzHandlers) {
			if (a != null) {
				finalAuthzHandlers.add(a);
			}
		}
		authzHandlers = null;
		// 存在多个,则构建组合AuthzHandler
		if (finalAuthzHandlers.size() > 1) {
			return new CompositeAuthzHandler(finalAuthzHandlers);
		}
		// 一个的话直接返回
		return finalAuthzHandlers.get(0);
	}

	/**
	 * 逐个扫描注解,如果相应的注解则在相应的位置赋值。
	 * 注解的处理是有顺序的,依次为RequiresRoles,RequiresPermissions,
	 * RequiresAuthentication,RequiresUser,RequiresGuest
	 *
	 * @param authzArray
	 * @param annotations
	 */
	private void scanAnnotation(List<AuthzHandler> authzArray,
			List<Annotation> annotations) {
		if (null == annotations || 0 == annotations.size()) {
			return;
		}
		for (Annotation a : annotations) {
			if (a instanceof RequiresRoles) {
				authzArray.set(0, new RoleAuthzHandler(a));
			} else if (a instanceof RequiresPermissions) {
				authzArray.set(1, new PermissionAuthzHandler(a));
			} else if (a instanceof RequiresAuthentication) {
				authzArray.set(2, AuthenticatedAuthzHandler.me());
			} else if (a instanceof RequiresUser) {
				authzArray.set(3, UserAuthzHandler.me());
			} else if (a instanceof RequiresGuest) {
				authzArray.set(4, GuestAuthzHandler.me());
			}
		}
	}

	/**
	 * 构建actionkey,参考ActionMapping中的实现。
	 *
	 * @param controllerClass
	 * @param method
	 * @param controllerKey
	 * @return
	 */
	private String createActionKey(Class<? extends Controller> controllerClass,
			Method method, String controllerKey) {
String methodName = method.getName();
 String actionKey = "";


 ActionKey ak = method.getAnnotation(ActionKey.class);
 if (ak != null) {
 actionKey = ak.value().trim();
 if ("".equals(actionKey))
 throw new IllegalArgumentException(controllerClass.getName() + "." + methodName + "(): The argument of ActionKey can not be blank.");
 if (!actionKey.startsWith(SLASH))
 actionKey = SLASH + actionKey;
 }
 else if (methodName.equals("index")) {
 actionKey = controllerKey;
 }
 else {
 actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName;
 }
 return actionKey;

	}

	/**
	 * 返回该方法的全部访问控制注解
	 *
	 * @param method
	 * @return
	 */
	private List<Annotation> getAuthzAnnotations(Method method) {
		List<Annotation> annotations = new ArrayList<Annotation>();
		for (Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES) {
			Annotation a = method.getAnnotation(annClass);
			if (a != null) {
				annotations.add(a);
			}
		}
		return annotations;
	}

	/**
	 * 返回该Controller的全部访问控制注解
	 *
	 * @param method
	 * @return
	 */
	private List<Annotation> getAuthzAnnotations(
			Class<? extends Controller> targetClass) {
		List<Annotation> annotations = new ArrayList<Annotation>();
		for (Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES) {
			Annotation a = targetClass.getAnnotation(annClass);
			if (a != null) {
				annotations.add(a);
			}
		}
		return annotations;
	}
	/**
	 * 该方法上是否有ClearShiro注解
	 * @param method
	 * @return
	 */
	private boolean isClearShiroAnnotationPresent(Method method) {
		Annotation a = method.getAnnotation(ClearShiro.class);
		if (a != null) {
			return true;
		}
		return false;
	}
}

8)实现Shiro拦截器。

package com.jfinal.ext.plugin.shiro;

import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthenticatedException;

import com.jfinal.aop.Interceptor;
import com.jfinal.core.ActionInvocation;

public class ShiroInterceptor implements Interceptor {

	public void intercept(Invocation ai){                 AuthzHandler  ah = ShiroKit.getAuthzHandler(ai.getActionKey());

		//存在访问控制处理器。
		if(ah != null){
	        try {
	        	//执行权限检查。
	        	ah.assertAuthorized();
			} catch (UnauthenticatedException lae) {
				//RequiresGuest,RequiresAuthentication,RequiresUser,未知足时,抛出未经受权的异常。
				//若是没有进行身份验证,返回HTTP401状态码
				ai.getController().renderError(401);
				return;
			} catch (AuthorizationException ae) {
				//RequiresRoles,RequiresPermissions受权异常
				//若是没有权限访问对应的资源,返回HTTP状态码403。
				ai.getController().renderError(403);
				return;
			} catch (Exception e) {
				ai.getController().renderError(401); return;
			}
        }
        //执行正常逻辑
        ai.invoke();
	}
}

9)构建一个ShiroKit辅助类

package com.jfinal.ext.plugin.shiro;

import java.util.concurrent.ConcurrentMap;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;


/**
 * 将全部Shiro指令封装成HTTL的函数。
 *
 * @author dafei
 */
public class ShiroKit {

	/**
	 * 用来记录那个action或者actionpath中是否有shiro认证注解。
	 */
	private static ConcurrentMap<String, AuthzHandler> authzMaps = null;


	private static final String NAMES_DELIMETER = ",";

	/**
	 * 禁止初始化
	 */
	private ShiroKit() {}

	static void init(ConcurrentMap<String, AuthzHandler> maps) {
		authzMaps = maps;
	}

	static AuthzHandler getAuthzHandler(String actionKey){
		return authzMaps.get(actionKey);
	}

	/**
	 * 获取 Subject
	 *
	 * @return Subject
	 */
	protected static Subject getSubject() {
		return SecurityUtils.getSubject();
	}

	/**
	 * 验证当前用户是否属于该角色?,使用时与lacksRole 搭配使用
	 *
	 * @param roleName
	 *            角色名
	 * @return 属于该角色:true,不然false
	 */
	public static boolean hasRole(String roleName) {
		return getSubject() != null && roleName != null
				&& roleName.length() > 0 && getSubject().hasRole(roleName);
	}

	/**
	 * 与hasRole标签逻辑相反,当用户不属于该角色时验证经过。
	 *
	 * @param roleName
	 *            角色名
	 * @return 不属于该角色:true,不然false
	 */
	public static boolean lacksRole(String roleName) {
		return !hasRole(roleName);
	}

	/**
	 * 验证当前用户是否属于如下任意一个角色。
	 *
	 * @param roleNames
	 *            角色列表
	 * @return 属于:true,不然false
	 */
	public static boolean hasAnyRoles(String roleNames) {
		boolean hasAnyRole = false;
		Subject subject = getSubject();
		if (subject != null && roleNames != null && roleNames.length() > 0) {
			// Iterate through roles and check to see if the user has one of the
			// roles
			for (String role : roleNames.split(NAMES_DELIMETER)) {
				if (subject.hasRole(role.trim())) {
					hasAnyRole = true;
					break;
				}
			}
		}
		return hasAnyRole;
	}

	/**
	 * 验证当前用户是否属于如下全部角色。
	 *
	 * @param roleNames
	 *            角色列表
	 * @return 属于:true,不然false
	 */
	public static boolean hasAllRoles(String roleNames) {
		boolean hasAllRole = true;
		Subject subject = getSubject();
		if (subject != null && roleNames != null && roleNames.length() > 0) {
			// Iterate through roles and check to see if the user has one of the
			// roles
			for (String role : roleNames.split(NAMES_DELIMETER)) {
				if (!subject.hasRole(role.trim())) {
					hasAllRole = false;
					break;
				}
			}
		}
		return hasAllRole;
	}

	/**
	 * 验证当前用户是否拥有指定权限,使用时与lacksPermission 搭配使用
	 *
	 * @param permission
	 *            权限名
	 * @return 拥有权限:true,不然false
	 */
	public static boolean hasPermission(String permission) {
		return getSubject() != null && permission != null
				&& permission.length() > 0
				&& getSubject().isPermitted(permission);
	}

	/**
	 * 与hasPermission标签逻辑相反,当前用户没有制定权限时,验证经过。
	 *
	 * @param permission
	 *            权限名
	 * @return 拥有权限:true,不然false
	 */
	public static boolean lacksPermission(String permission) {
		return !hasPermission(permission);
	}

	/**
	 * 已认证经过的用户。不包含已记住的用户,这是与user标签的区别所在。与notAuthenticated搭配使用
	 *
	 * @return 经过身份验证:true,不然false
	 */
	public static boolean authenticated() {
		return getSubject() != null && getSubject().isAuthenticated();
	}

	/**
	 * 未认证经过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。。
	 *
	 * @return 没有经过身份验证:true,不然false
	 */
	public static boolean notAuthenticated() {
		return !authenticated();
	}

	/**
	 * 认证经过或已记住的用户。与guset搭配使用。
	 *
	 * @return 用户:true,不然 false
	 */
	public static boolean user() {
		return getSubject() != null && getSubject().getPrincipal() != null;
	}

	/**
	 * 验证当前用户是否为“访客”,即未认证(包含未记住)的用户。用user搭配使用
	 *
	 * @return 访客:true,不然false
	 */
	public static boolean guest() {
		return !user();
	}

	/**
	 * 输出当前用户信息,一般为登陆账号信息。
	 * @return 当前用户信息
	 */
	public String principal(){
		if (getSubject() != null) {
            // Get the principal to print out
            Object principal = getSubject().getPrincipal();
            return principal.toString();
        }
		return "";
	}
}

实现完毕。

----------------------------------------------------------

玛雅牛

相关文章
相关标签/搜索