最新版本JFinal实现更加灵活路由这块进行了增加设计,原jfinal shiro plugin不支持JFinal 3.0的问题上有很多人也已分享了,只是大多数人只分享了如何将这个插件改造,未对咱们JFinal Template engine 模板引擎支持Shiro标签进行分享,在这我就简单说下,方便有需要的朋友。
1、下载JFinal Shiro 插件:http://git.oschina.net/myaniu/jfinalshiroplugin
(1)、修改ShiroPlugin插件下start方法将:
for (Entry>entry : routes.getEntrySet()) {
Class controllerClass = entry.getValue();
String controllerKey = entry.getKey();
修改为:
for (Routes routes : getRoutesList()) {
for (Routes.Route route : routes.getRouteItemList()) {
Class controllerClass = route.getControllerClass();
String controllerKey = route.getControllerKey();
(2)、完整效果如下:
/** * 启动插件 */ public boolean start() { Set<String> excludedMethodName = buildExcludedMethodName(); ConcurrentMap<String, AuthzHandler> authzMaps = new ConcurrentHashMap<String, AuthzHandler>(); //逐个访问所有注册的Controller,解析Controller及action上的所有Shiro注解。 //并依据这些注解,actionKey提前构建好权限检查处理器。 for (Routes routes : getRoutesList()) { for (Routes.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); // ShiroKit.init(jdbcAuthzService, authzMaps, false); /** * 设定登录,登录成功,未授权等url地址 */ ShiroKit.setLoginUrl(loginUrl); ShiroKit.setSuccessUrl(successUrl); ShiroKit.setUnauthorizedUrl(unauthorizedUrl); return true; } private List<Routes> getRoutesList() { List<Routes> routesList = Routes.getRoutesList(); List<Routes> ret = new ArrayList<Routes>(routesList.size() + 1); ret.add(routes); ret.addAll(routesList); return ret; }
(3)、插件配置:
>web.xml配置支持shiro
<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>
>在yourConfig类声明一个route变量如:
private Routes route = null;
>在yourConfig找到configRoute接收路由参数方法配置如下:
@Override public void configRoute(Routes me) { this.route = me; }
>>补充:如果路由分片如:AdminRoutes、FrontRouts时,记得把 route声明为:private static Routes route = null;
然后在这样传值:
@Override public void configRoute(Routes me) { this.route = new AdminRoutes(); me.add(route); //管理后台路由 me.add(new FrontRoutes());//前台会员路由 }
这样就可以把AdminRoutes()映射的路由给传过去了,当然有人再问题,如果两个路由块一起传进去怎么拿?声明一个集合变量如:private static List<Rotues> routeList = null;
然后在yourConfig的configRoute中是这样传值:
@Override public void configRoute(Routes me) { routesList.add(new AdminRoutes()); routesList.add(new FrontRoutes()); me.add(new AdminRoutes()); //管理后台路由 me.add(new FrontRoutes());//前台会员路由 }
这时在ShiroPlugin再稍加修改下,把原插件传单个对象调整为传集合进去遍历下就可以了。
>在yourConfig找到configPlugin方法启动插件配置如下:
@Override public void configPlugin(Plugins me) { me.add(new ShiroPlugin(route)); }
>在yourConfig找到configInterceptor方法开启shiro拦截器配置如下:
@Override public void configInterceptor(Interceptors me) { //启动Shiro拦截器 me.add(new ShiroInterceptor()); }
>shrio.ini文件这个文件需要放在classpath目录下,如果是maven项目的话就放到resource下就好了
[main] #cookie sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie sessionIdCookie.name=wxhsopId sessionIdCookie.domain=.test.com sessionIdCookie.path=/ sessionIdCookie.maxAge=7 * 24 * 60 * 60 sessionIdCookie.httpOnly=true #session #sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO #sessionDAO = com.wxshop.common.auth.SessionDAO #sessionDAO.activeSessionsCacheName = shiro-activeSessionCache #sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager #sessionManager.sessionDAO = $sessionDAO #sessionManager.globalSessionTimeout = 1800000 #sessionManager.sessionIdCookieEnabled = false #securityManager.sessionManager = $sessionManager #CredentialsMatcher #credentialsMatcher=com.wxshop.common.auth.RetryLimitHashedCredentialsMatcher #credentialsMatcher.hashAlgorithmName=md5 #credentialsMatcher.hashIterations=2 #credentialsMatcher.storedCredentialsHexEncoded=true #realm myRealm = com.wxshop.common.auth.ShiroDbRealm #myRealm.credentialsMatcher=$credentialsMatcher securityManager.realms = $myRealm shiro.loginUrl=login [urls] /login = anon /index/img = anon /index/user/logout = anon /index = authc /index/** = authc /** = anon /druid/** = authc,roles[admin] /monitoring/** = authc,roles[admin]
>shiro.ini文件中myRealm =com.wxshop.comon.auth.ShiroDbRealm参考实现:
public class ShiroDbRealm extends AuthorizingRealm { @Override public void setCacheManager(CacheManager cacheManager) { super.setCacheManager(cacheManager); ShiroCache.setCacheManager(cacheManager); } /** * 身份证认证 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken; String username = usernamePasswordToken.getUsername(); Record user = UserService.me.login(username); SimpleAuthenticationInfo authenticationInfo = null; if (!sl.isEmpty(user)) { String name = user.get("name"); Record shiroUser = new Record(); shiroUser.set("id", user.getInt("id")); shiroUser.set("name", name); authenticationInfo = new SimpleAuthenticationInfo(shiroUser, user.getStr("pass"),ByteSource.Util.bytes(user.getStr("salt")), getName()); // authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(name + user.get("salt"))); } return authenticationInfo; } /** * 权限认证 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { Record user = (Record) principalCollection.fromRealm(getName()).iterator().next(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); if (sl.isEmpty(user)) { return info; } int id = user.get("id"); authRole(id, info); return info; } /** * 角色授权 * @param id * @param info */ protected void authRole(int id, SimpleAuthorizationInfo info) { List<Record> roleList = RoleService.me.byUid(id); if (!sl.isEmpty(roleList)) { for (Record role : roleList) { info.addRole(role.get("name")); authUrl(role.get("id"), info); } } } /** * 资源授权 * @param id * @param info */ protected void authUrl(int id, SimpleAuthorizationInfo info) { List<Record> resourceList = ResourceService.me.byRid(id); if (!sl.isEmpty(resourceList)) { for (Record resource : resourceList) { info.addStringPermission(resource.get("url")); } } } public void clearCacheAuth(Object principal) { SimplePrincipalCollection info = new SimplePrincipalCollection(principal, getName()); clearCachedAuthenticationInfo(info); } public void clearAllCacheAuth() { Cache<Object, AuthenticationInfo> cache = getAuthenticationCache(); if (null != cache) { for (Object key : cache.keys()) { cache.remove(key); } } }
>另外在不想一个个controler 手工@RequiresPermissions("/index/xx/xx")的话就创建一个全局拦截器,统一处理如:
/** * 让 shiro基于url拦截 * * 主要 数据库中也用url 保存权限 */ public class UrlShiroInterceptor implements Interceptor{ private static ShiroExt ext = new ShiroExt(); /** * 获取全部 需要控制的权限 */ private static List<String> urls; public static void updateUrls() { urls = ResourceService.me.urls(); } public void intercept(Invocation ai) { if (urls == null) { urls = ResourceService.me.urls(); } String url = ai.getActionKey(); try { if (url.contains("add")){ // LogService.me.add(ai.getController(), LogService..EVENT_ADD); } else if (url.contains("delete")) { // LogService.me.add(ai.getController(), Log.EVENT_DELETE); } else if (url.contains("update")) { // LogService.me.add(ai.getController(), Log.EVENT_UPDATE); } else if (url.contains("index")) { // LogService.me.add(ai.getController(), Log.EVENT_QUERY); } else if (url.contains("goto")) { } if (urls.contains(url) && !ext.hasPermission(url)) { ai.getController().renderError(403); } } catch (Exception e) { ai.getController().renderError(403); } ai.invoke(); } }
2、即然JFinal 已具备Shiro扩展,怎么放过标签的应用的,如果没有标签的应用,改造成只能算是完成了一半,因为JFinal Template Engine模板引擎的极速特点,以下扩展拿来就可以实现JFinal Template Engine 的shrio标签扩展
(1)、标签代码:
public class ShiroTag { /* The guest tag * * @return */ public boolean isGuest() { return getSubject() == null || getSubject().getPrincipal() == null; } /** * The user tag * * @return */ public boolean isUser() { return getSubject() != null && getSubject().getPrincipal() != null; } /** * The authenticated tag * * @return */ public boolean isAuthenticated() { return getSubject() != null && getSubject().isAuthenticated(); } public boolean isNotAuthenticated() { return !isAuthenticated(); } /** * The principal tag * * @param map * @return */ public String principal(Map map) { String strValue = null; if (getSubject() != null) { // Get the principal to print out Object principal; String type = map != null ? (String) map.get("type") : null; if (type == null) { principal = getSubject().getPrincipal(); } else { principal = getPrincipalFromClassName(type); } String property = map != null ? (String) map.get("property") : null; // Get the string value of the principal if (principal != null) { if (property == null) { strValue = principal.toString(); } else { strValue = getPrincipalProperty(principal, property); } } } if (strValue != null) { return strValue; } else { return null; } } /** * The hasRole tag * * @param roleName * @return */ public boolean hasRole(String roleName) { return getSubject() != null && getSubject().hasRole(roleName); } /** * The lacksRole tag * * @param roleName * @return */ public boolean lacksRole(String roleName) { boolean hasRole = getSubject() != null && getSubject().hasRole(roleName); return !hasRole; } /** * The hasAnyRole tag * * @param roleNames * @return */ public boolean hasAnyRole(String roleNames) { boolean hasAnyRole = false; Subject subject = getSubject(); if (subject != null) { // Iterate through roles and check to see if the user has one of the // roles for (String role : roleNames.split(",")) { if (subject.hasRole(role.trim())) { hasAnyRole = true; break; } } } return hasAnyRole; } /** * The hasPermission tag * * @param p * @return */ public boolean hasPermission(String p) { return getSubject() != null && getSubject().isPermitted(p); } /** * The lacksPermission tag * * @param p * @return */ public boolean lacksPermission(String p) { return !hasPermission(p); } @SuppressWarnings({"unchecked"}) private Object getPrincipalFromClassName(String type) { Object principal = null; try { Class cls = Class.forName(type); principal = getSubject().getPrincipals().oneByType(cls); } catch (ClassNotFoundException e) { } return principal; } private String getPrincipalProperty(Object principal, String property) { String strValue = null; try { BeanInfo bi = Introspector.getBeanInfo(principal.getClass()); // Loop through the properties to get the string value of the // specified property boolean foundProperty = false; for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { if (pd.getName().equals(property)) { Object value = pd.getReadMethod().invoke(principal, (Object[]) null); strValue = String.valueOf(value); foundProperty = true; break; } } if (!foundProperty) { final String message = "Property [" + property + "] not found in principal of type [" + principal.getClass().getName() + "]"; throw new RuntimeException(message); } } catch (Exception e) { final String message = "Error reading property [" + property + "] from principal of type [" + principal.getClass().getName() + "]"; throw new RuntimeException(message, e); } return strValue; } protected Subject getSubject() { return SecurityUtils.getSubject(); } }
(2)、在yourConfig找到configEngine方法配置如下:
@Override public void configEngine(Engine me) { me.addSharedObject("shiro", new ShiroTag()); }
(3)、UI界面使用效果如:
#if(shiro.hasPermission("/home/resource/add")) <a data-for="/home/resource/add" id="add" class="btn btn-small"> <i class="icon"></i>新增 </a> #end
以上还有其它标签就不一个个列出来了,自己以面例子调用就成了。