JFinal 3.0 Template engin 集成Shiro支持

最新版本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">&#xe608;</i>新增
    </a>
#end

以上还有其它标签就不一个个列出来了,自己以面例子调用就成了。

评论区

JFinal

2017-03-14 16:52

非常有价值的分享,好多 jfinal 小伙伴都问过这个问题,由于 jfinal 3.0 改变了 Routes 内部实现,所以 Shiro 扩展相应也要做点改变,感谢你的分享 ^_^

JFinal

2017-03-14 16:52

已收藏,下次有人再问我此问题,直接给到收藏中的链接就好

EATI001

2017-03-14 16:56

@JFinal 哈哈支持JFinal的发展,分享是应该的。

xialinlin

2017-03-14 22:45

下载链接多了一个,号。

zzhkiller

2017-03-15 11:01

给力 我也在写这个模块。发现你的赞

小飞象

2017-03-16 09:25

@EATI001 建议上传到maven中央库,这样会比较好用,目前基本都是maven项目。

小飞象

2017-03-16 09:26

@JFinal, 波总要不要搞个官方的独立插件,上传maven中央库,给大家做个样板。有利于jfinal生态建设。

JFinal

2017-03-16 10:34

@小飞象 搞生态在计划之中,当前还有些事没做完,资源也不够,搞生态是需要花钱的

zzhkiller

2017-03-16 15:38

@Override
public void configRoute(Routes routes) {
this.routes = routes;
routes.add("/", IndexController.class);
routes.add(new FrontRoutes());
routes.add(new AdminRoutes());

} 如果用这种方法 this.route = me; 就获取不到 new FrontRoutes()中的 action

linkzz

2017-03-16 15:51

@zzhkiller 放到下面是不是可以取到了?

zzhkiller

2017-03-16 21:22

/**
* Add Routes
*/
public Routes add(Routes routes) {
routes.config();
routesList.add(routes);
return this;
}

/**
* Add route
* @param controllerKey A key can find controller
* @param controllerClass Controller Class
* @param viewPath View path for this Controller
*/
public Routes add(String controllerKey, Class controllerClass, String viewPath) {
routeItemList.add(new Route(controllerKey, controllerClass, viewPath));
return this;
}@linkzz @zzhkiller @JFinal @EATI001 应该是这段话

zzhkiller

2017-03-17 09:49

昨天后来有事,忘了回复 已经解决, 改一下第一幅图 可供大家学习
for (Routes routes: Routes.getRoutesList()) {

这里面就是
//逐个访问所有注册的Controller,解析Controller及action上的所有Shiro注解。
//并依据这些注解,actionKey提前构建好权限检查处理器。
} 昨天谢谢大家一起分享学习

JFinal

2017-03-17 11:20

@zzhkiller 这几天的回贴中的内容是否可以改善进入原有的代码,如果可以,在本贴上直接修改,能帮助到后来者 @EATI001 非常感谢

zzhkiller

2017-03-17 11:23

@JFinal 已经在 @EATI001 回复他了 只要他修改一些他的分享, 就可以了。

威仔

2017-03-17 16:59

自带模板 可以用一些截取字符的函数吗?比如页面渲染值太长了,截断到多少个长度显示,不用css控制的@JFinal

JFinal

2017-03-18 09:54

@威仔 可以直接在对象上使用它的方法,例如 #(article.title.subString(1, 20)),还可以通过添加 sharedMethod,这么来玩:#(subString(article.title, 1, 20))

多种扩展方式,用起来很顺滑

EATI001

2017-03-19 13:05

重新调整了下,在start中加一了层:for (Routes routes : getRoutesList()) :
/**
* 启动插件
*/
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;
}

private List getRoutesList() {
List routesList = Routes.getRoutesList();
List ret = new ArrayList(routesList.size() + 1);
ret.add(routes);
ret.addAll(routesList);
return ret;
},经测试在configRoute中任意位置初始this.routes= me都可能实现注解如:
@RequiresRoles("superAdmin")或 @RequiresPermissions("/admin/user/add")进行拦截。

lantsui

2017-03-23 14:16

标签的拓展,我按你的方式去配,页面上没起效果呢,而是把那命令显示出来了

EATI001

2017-03-25 15:41

@lantsui,1、shrio配置不对shrio拦截器没有启动;2、就是配置后没有重启容器,配置没有生效,先看控制有什么错误提示。

yjjdick1990

2017-08-10 09:59

@EATI001 git链接失效了

EATI001

2017-09-18 15:29

Shydow

2018-01-09 18:41

遇到没有授权的时候默认跳转到401.jsp了,可是我在configConstant设置的是:
//RequiresGuest,RequiresAuthentication,RequiresUser验证异常,返回HTTP401状态码
me.setError401View("/html/admin/401.html");
//RequiresRoles,RequiresPermissions授权异常,返回HTTP403状态码
me.setError401View("/html/admin/403.html");
是我哪里没考虑到么么?

Shydow

2018-01-10 14:44

接上一条:查看jfinalshiroplugin源码发现,ShiorPlugin和ShiroKit中定义了private String unauthorizedUrl ="/401.jsp";我在jfinalConfig文件的configPlugin添加shiro插件的时候:
//配置shiro插件
ShiroPlugin shiroPlugin = new ShiroPlugin(route);
shiroPlugin.setUnauthorizedUrl("/html/admin/401.html");
me.add(shiroPlugin);
此时引用了我要的页面,但是该页面的模板元素没有被解析@JFinal

铂金蛋蛋

2018-01-17 21:06

热门分享

扫码入社