学习了shiro的一些身份验证和授权知识,那么现在主要学习大飞怎么把shiro结合到jfinal,怎么在jfinal里使用shiro的注解的。
一、首先,要先了解一下jfinal启动插件的流程,才能好的理解jfinal怎么使用shiro,jfinal使用其他框架都采用Plugins的形式,在启动jfinal的时候,会依次启动在configPlugin里配置的插件,这个插件要实现接口IPlugin就可以,如public class ShiroPlugin implements IPlugin。 jfinal启动继承了IPlugin的插件的流程如下:
1、启动jfinal,会将web.xml的内容都初始化并加载到web容器,即会通过反射给每个类创建一个实例,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>jfinal</filter-name>
<filter-class>com.jfinal.core.JFinalFilter</filter-class>
<init-param>
<param-name>configClass</param-name>
<param-value>com.learnging.system.LearngingConfig</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jfinal</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
这里它会生成JFinalFilter的实例,并调用JFinalFilter的init方法,这里init又委托给JFinal的init方法,如下:
void init(JFinalConfig jfinalConfig, ServletContext servletContext)
{
this.servletContext = servletContext;
this.contextPath = servletContext.getContextPath();
initPathKit();
Config.configJFinal(jfinalConfig); // start plugin, init log factory and init engine in this method
constants = Config.getConstants();
initActionMapping();
initHandler();
initRender();
initOreillyCos();
initTokenManager();
}
JFinal的init方法又调用Config.configJFinal()方法,如下:
static void configJFinal(JFinalConfig jfinalConfig)
{
jfinalConfig.configConstant(constants);
initLogFactory();
initEngine();
jfinalConfig.configRoute(routes);
jfinalConfig.configEngine(engine);
jfinalConfig.configPlugin(plugins);
startPlugins(); // very important!!!
jfinalConfig.configInterceptor(interceptors);
jfinalConfig.configHandler(handlers);
}
private static void startPlugins()
{
List<IPlugin> pluginList = plugins.getPluginList();
if (pluginList == null)
{
return ;
}
for (IPlugin plugin : pluginList)
{
try
{
// process ActiveRecordPlugin devMode
if (plugin instanceof com.jfinal.plugin.activerecord.ActiveRecordPlugin)
{
com.jfinal.plugin.activerecord.ActiveRecordPlugin arp = (com.jfinal.plugin.activerecord.ActiveRecordPlugin)plugin;
if (arp.getDevMode() == null)
{
arp.setDevMode(constants.getDevMode());
}
}
if (plugin.start() == false)
{
String message = "Plugin start error: " + plugin.getClass().getName();
log.error(message);
throw new RuntimeException(message);
}
}
catch (Exception e)
{
String message = "Plugin start error: " + plugin.getClass().getName() + ". \n" + e.getMessage();
log.error(message, e);
throw new RuntimeException(message, e);
}
}
}
至此,启动完所有插件,只要一个插件启动失败,就会都失败。
二接下来就是将一个框架做成一个插件,整合到jfinal。大飞写的ShiroPlugin插件最主要的start方法,如下所示:
public boolean start()
{
Set<String> excludedMethodName = buildExcludedMethodName();
ConcurrentMap<String, AuthzHandler> authzMaps = new ConcurrentHashMap<String, AuthzHandler>();
//逐个访问所有注册的Controller,解析Controller及action上的所有Shiro注解。
//并依据这些注解,actionKey提前构建好权限检查处理器。
for (Route route : routes.getRouteItemList())
{
Class <? extends Controller > controllerClass = route.getControllerClass();
String controllerKey = route.getControllerKey();
// 获取Controller的所有Shiro注解。
List<Annotation> 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<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);
/**
* 设定登录,登录成功,未授权等url地址
*/
ShiroKit.setLoginUrl(loginUrl);
ShiroKit.setSuccessUrl(successUrl);
ShiroKit.setUnauthorizedUrl(unauthorizedUrl);
ShiroKit.setExtName(extName);
return true;
}
1、buildExcludedMethodName()取得controller里没有参数的方法,即action方法。
2、authzMaps用于保存cotroller和action及其上面对应的注解。
3、逐个访问所有注册的Controller,解析Controller及action上的所有Shiro注解。并依据这些注解,actionKey提前构建好权限检查处理器。
4、用controllerAnnotations保存Controller的所有Shiro注解。
5、用methodAnnotations保存不是基类,又没有参数的,又没有ClearShiro的方法的所有Shiro注解。
6、调用方法createAuthzHandler依据Controller的注解和方法的注解来生成访问控制处理器。以上authzHandlers.add(null);是因为注解的处理是有顺序的,依次为RequiresRoles,RequiresPermissions, RequiresAuthentication,RequiresUser,RequiresGuest。
/**
* 依据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>(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<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);
}
7、生成访问控制处理器成功,构建ActionKey,参考ActionMapping中实现,添加映射,大飞实现如下:
/**
* 构建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;
}
本博客代码主要来源于jfinal源码和大飞的开源代码。主要目的是学习.
JFinalShiroPlugin下载地址:http://git.oschina.net/myaniu/jfinalshiroplugin