2018-07-27 00:38
/**
* Copyright (c) 2011-2017, dafei 李飞 (myaniu AT gmail DOT com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.ext.plugin.shiro;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.config.Routes.Route;
import com.jfinal.core.ActionKey;
import com.jfinal.core.Controller;
import com.jfinal.plugin.IPlugin;
/**
* Shiro插件,启动时加载所有Shiro访问控制注解。
*
* @author dafei
*
*/
@SuppressWarnings("unchecked")
public class ShiroPlugin3 implements IPlugin {
/**
* 登录成功时所用的页面。
*/
private String successUrl = "/";
/**
* 登录成功时所用的页面。
*/
private String loginUrl = "/";
/**
* action的扩展名,比如.action或者 .do
*/
private String extName = "";
/**
* 登录成功时所用的页面。
*/
private String unauthorizedUrl = "/401.jsp";
private final String SLASH = "/";
/**
* Shiro的几种访问控制注解
*/
private static final Class[] AUTHZ_ANNOTATION_CLASSES = new Class[] {
RequiresPermissions.class, RequiresRoles.class, RequiresUser.class, RequiresGuest.class,
RequiresAuthentication.class };
/**
* 路由设定
*/
private final Routes routes;
/**
* 构造函数
*
* @param routes
* 路由设定
*/
public ShiroPlugin3(Routes routes) {
this.routes = routes;
}
/**
* 停止插件
*/
public boolean stop() {
return true;
}
/**
* 启动插件
*/
public boolean start() {
Set excludedMethodName = buildExcludedMethodName();
ConcurrentMap authzMaps = new ConcurrentHashMap();
//逐个访问所有注册的Controller,解析Controller及action上的所有Shiro注解。
//并依据这些注解,actionKey提前构建好权限检查处理器。
for (Routes routes : getRoutesList()) {
for (Routes.Route route : routes.getRouteItemList()) {
Class controllerClass = route.getControllerClass();
String controllerKey = route.getControllerKey();
// 获取Controller的所有Shiro注解。
List controllerAnnotations = getAuthzAnnotations(controllerClass);
// 逐个遍历方法。
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 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);
// ShiroKit.init(jdbcAuthzService, authzMaps, false);
/**
* 设定登录,登录成功,未授权等url地址
*/
ShiroKit.setLoginUrl(loginUrl);
ShiroKit.setSuccessUrl(successUrl);
ShiroKit.setUnauthorizedUrl(unauthorizedUrl);
return true;
}
/**
* 获取所有配置的Route
* @return
*/
private List getRoutesList() {
List routesList = Routes.getRoutesList();
List ret = new ArrayList(routesList.size() + 1);
ret.add(routes);
ret.addAll(routesList);
return ret;
}
/**
* 从Controller方法中构建出需要排除的方法列表
*
* @return
*/
private Set buildExcludedMethodName() {
Set excludedMethodName = new HashSet();
Method[] methods = Controller.class.getMethods();
for (Method m : methods) {
if(Modifier.isPublic(m.getModifiers())) {
excludedMethodName.add(m.getName());
}
}
return excludedMethodName;
}
/**
* 依据Controller的注解和方法的注解来生成访问控制处理器。
*
* @param controllerAnnotations
* Controller的注解
* @param methodAnnotations
* 方法的注解
* @return 访问控制处理器
*/
private AuthzHandler createAuthzHandler(List controllerAnnotations,
List methodAnnotations) {
// 没有注解
if (controllerAnnotations.size() == 0 && methodAnnotations.size() == 0) {
return null;
}
// 至少有一个注解
List authzHandlers = new ArrayList(AUTHZ_ANNOTATION_CLASSES.length);
for (int index = 0; index < AUTHZ_ANNOTATION_CLASSES.length; index++) {
authzHandlers.add(null);
}
// 逐个扫描注解,若是相应的注解则在相应的位置赋值。
scanAnnotation(authzHandlers, controllerAnnotations);
// 逐个扫描注解,若是相应的注解则在相应的位置赋值。函数的注解优先级高于Controller
scanAnnotation(authzHandlers, methodAnnotations);
// 去除空值
List finalAuthzHandlers = new ArrayList();
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 authzArray, List 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 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 getAuthzAnnotations(Method method) {
List annotations = new ArrayList();
for (Class annClass : AUTHZ_ANNOTATION_CLASSES) {
Annotation a = method.getAnnotation(annClass);
if (a != null) {
annotations.add(a);
}
}
return annotations;
}
/**
* 返回该Controller的所有访问控制注解
*
* @param method
* @return
*/
private List getAuthzAnnotations(Class targetClass) {
List annotations = new ArrayList();
for (Class 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;
}
/**
* 登录成功连接
*
* @param successUrl
*/
public final void setSuccessUrl(String successUrl) {
this.successUrl = successUrl;
}
/**
* 登录连接
*
* @param loginUrl
*/
public final void setLoginUrl(String loginUrl) {
this.loginUrl = loginUrl;
}
/**
* 未授权连接
*
* @param unauthorizedUrl
*/
public final void setUnauthorizedUrl(String unauthorizedUrl) {
this.unauthorizedUrl = unauthorizedUrl;
}
/**
* 扩展名,比如.action/.do
*
* @param extName
*/
public final void setExtName(String extName) {
this.extName = extName;
}
}
这样在这个类中,由于在构造函数中传入的routes=null,所以在start函数中,由于routes为null,异常退出。导致启动shiro插件失败。这个问题该如何解决?