add: 让shiro支持urls过滤

update: 顺便把shiro的demo搞通畅
fix: shiro的@PropDoc没生效
add: 设置登录地址/登出后的重定向地址/未授权时的跳转地址
This commit is contained in:
Wendal Chen 2018-01-03 22:23:21 +08:00
parent 295298c966
commit aa70cc1d8a
13 changed files with 221 additions and 133 deletions

View File

@ -183,12 +183,6 @@ public class NbApp extends Thread {
// 各种预备操作 // 各种预备操作
this.prepare(); this.prepare();
if (printProcDoc) {
PropDocReader docReader = new PropDocReader(ctx);
docReader.load();
Logs.get().info("Configure Manual:\r\n" + docReader.toMarkdown());
}
// 依次启动 // 依次启动
try { try {
ctx.init(); ctx.init();
@ -240,6 +234,13 @@ public class NbApp extends Thread {
// 加载各种starter // 加载各种starter
prepareStarterClassList(); prepareStarterClassList();
// 打印配置文档
if (printProcDoc) {
PropDocReader docReader = new PropDocReader();
docReader.load(starterClasses);
Logs.get().info("Configure Manual:\r\n" + docReader.toMarkdown());
}
// 创建Ioc容器 // 创建Ioc容器
prepareIoc(); prepareIoc();

View File

@ -6,6 +6,7 @@ import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.nutz.boot.AppContext; import org.nutz.boot.AppContext;
@ -28,27 +29,10 @@ public class PropDocReader {
* 文档列表 * 文档列表
*/ */
protected Map<String, PropDocBean> docs = new HashMap<>(); protected Map<String, PropDocBean> docs = new HashMap<>();
/** public void load(List<Class<?>> starterClasses) throws Exception {
* 创建一个空PropDocReader for (Class<?> klass : starterClasses) {
*/ addClass(klass);
public PropDocReader() {}
/**
* 创建一个PropDocReader,并给与AppContext
*
* @param appContext
*/
public PropDocReader(AppContext appContext) {
this.appContext = appContext;
}
/**
* 从AppContext读取Starter,并加载之
*/
public void load() throws Exception {
for (Object starter : appContext.getStarters()) {
addClass(starter.getClass());
} }
} }
@ -117,13 +101,4 @@ public class PropDocReader {
return docs; return docs;
} }
/**
* 设置全局上下文
*
* @param appContext
* 全局上下文
*/
public void setAppContext(AppContext appContext) {
this.appContext = appContext;
}
} }

View File

@ -10,15 +10,11 @@ import org.apache.shiro.subject.Subject;
import org.nutz.boot.NbApp; import org.nutz.boot.NbApp;
import org.nutz.dao.Dao; import org.nutz.dao.Dao;
import org.nutz.dao.util.Daos; import org.nutz.dao.util.Daos;
import org.nutz.integration.shiro.SimpleShiroToken;
import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.random.R; import org.nutz.lang.random.R;
import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Fail;
import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.POST;
import org.nutz.mvc.annotation.Param;
import io.nutz.demo.simple.bean.User; import io.nutz.demo.simple.bean.User;
@ -41,23 +37,6 @@ public class MainLauncher {
return subject.isAuthenticated(); return subject.isAuthenticated();
} }
@Ok("json")
@Fail("http:500")
@POST
@At("/user/login")
public boolean login(@Param("username")String username, @Param("password")String password, HttpSession session) {
User user = dao.fetch(User.class, username);
if (user == null)
return false;
Sha256Hash hash = new Sha256Hash(password, user.getSalt());
if (!hash.toHex().equals(user.getPassword())) {
return false;
}
Subject subject = SecurityUtils.getSubject();
subject.login(new SimpleShiroToken(user.getId()));
return true;
}
public void init() { public void init() {
Daos.createTablesInPackage(dao, User.class, false); Daos.createTablesInPackage(dao, User.class, false);
dao.insert(newUser("admin", "123456")); dao.insert(newUser("admin", "123456"));

View File

@ -0,0 +1,58 @@
package io.nutz.demo.simple.module;
import javax.servlet.http.HttpSession;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.subject.Subject;
import org.nutz.dao.Dao;
import org.nutz.integration.shiro.SimpleShiroToken;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Fail;
import org.nutz.mvc.annotation.GET;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.POST;
import org.nutz.mvc.annotation.Param;
import io.nutz.demo.simple.bean.User;
@At("/user")
@IocBean
public class UserModule {
@Inject
Dao dao;
@GET
@At("/login")
@Ok(">>:/login.html")
public void loginPage() {}
@Ok("json")
@Fail("http:500")
@POST
@At("/login")
public boolean login(@Param("username")String username, @Param("password")String password, HttpSession session) {
User user = dao.fetch(User.class, username);
if (user == null)
return false;
Sha256Hash hash = new Sha256Hash(password, user.getSalt());
if (!hash.toHex().equals(user.getPassword())) {
return false;
}
Subject subject = SecurityUtils.getSubject();
subject.login(new SimpleShiroToken(user.getId()));
return true;
}
@Ok(">>:/index.html")
@At
public void logout() {
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated())
subject.logout();
}
}

View File

@ -2,4 +2,8 @@ jetty.port=8080
jetty.host=127.0.0.1 jetty.host=127.0.0.1
jdbc.url=jdbc:h2:mem:~ jdbc.url=jdbc:h2:mem:~
shiro.ini.urls:
/rs = anon
/home.html = authc
#end

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户个人主页</title>
</head>
<body>
<h2>登录成功才能看到我,<a href="user/logout">现在登出</a></h2>
</body>
</html>

View File

@ -2,9 +2,9 @@
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Hello, So NB!</title> <title>Shiro测试页</title>
</head> </head>
<body> <body>
Hello, So NB! <h2><a href="home.html?_=1">点我,访问一个未授权路径,会跳转到登录页面</a></h2>
</body> </body>
</html> </html>

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>这是登录页面</title>
</head>
<body>
<form action="user/login" id="user_login_form">
账号<input name="username" value="admin">
密码<input name="password" type="password" value="123456">
<input type="submit" id="user_login">
</form>
</body>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
<script type="text/javascript">
$(function() {
$("#user_login").click(function() {
$.ajax({
type : "POST",
url : "user/login",
data : $("#user_login_form").serialize(),
dataType : "json",
success : function(re) {
if (re) {
window.location = "/home.html";
}
else {
alert("登录失败,用户名应该是admin或者wendal,密码是123456");
}
}
});
return false;
});
});
</script>
</html>

View File

@ -1,50 +1,88 @@
package org.nutz.boot.starter.shiro; package org.nutz.boot.starter.shiro;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.shiro.ShiroException; import org.apache.shiro.ShiroException;
import org.apache.shiro.config.Ini;
import org.apache.shiro.util.Destroyable; import org.apache.shiro.util.Destroyable;
import org.apache.shiro.util.Initializable; import org.apache.shiro.util.Initializable;
import org.apache.shiro.web.config.IniFilterChainResolverFactory;
import org.apache.shiro.web.env.ResourceBasedWebEnvironment; import org.apache.shiro.web.env.ResourceBasedWebEnvironment;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.filter.mgt.DefaultFilter;
import org.apache.shiro.web.filter.mgt.FilterChainResolver; import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.mgt.WebSecurityManager; import org.apache.shiro.web.mgt.WebSecurityManager;
import org.nutz.boot.AppContext; import org.nutz.boot.AppContext;
import org.nutz.integration.shiro.NutShiro;
import org.nutz.ioc.Ioc; import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy; import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.lang.Strings; import org.nutz.lang.Strings;
import org.nutz.log.Log;
import org.nutz.log.Logs;
public class NbResourceBasedWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable { public class NbResourceBasedWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable {
protected AppContext appContext; private static final Log log = Logs.get();
protected Ioc ioc;
protected PropertiesProxy conf; protected AppContext appContext;
protected Ioc ioc;
public void init() throws ShiroException { protected PropertiesProxy conf;
appContext = ShiroUtil.appContext;
ioc = appContext.getIoc(); public void init() throws ShiroException {
conf = appContext.getConfigureLoader().get(); appContext = AppContext.getDefault();
configure(); ioc = appContext.getIoc();
} conf = appContext.getConfigureLoader().get();
configure();
protected void configure() { }
this.objects.clear();
protected void configure() {
this.objects.clear();
WebSecurityManager securityManager = createWebSecurityManager(); WebSecurityManager securityManager = createWebSecurityManager();
setWebSecurityManager(securityManager); setWebSecurityManager(securityManager);
String loginUrl = conf.get(ShiroEnvStarter.PROP_URL_LOGIN, "/user/login");
String unauthorizedUrl = conf.get(ShiroEnvStarter.PROP_URL_UNAUTH, "/user/login");
String logoutUrl = conf.get(ShiroEnvStarter.PROP_URL_LOGOUT_REDIRECT, "/");
for (Map.Entry<String, Filter> en : DefaultFilter.createInstanceMap(null).entrySet()) {
Filter filter = en.getValue();
if (filter instanceof LogoutFilter) {
((LogoutFilter)filter).setRedirectUrl(logoutUrl);
}
else if (filter instanceof AuthenticatingFilter) {
((AuthenticatingFilter)filter).setLoginUrl(loginUrl);
}
else if (filter instanceof AccessControlFilter) {
((AccessControlFilter)filter).setLoginUrl(unauthorizedUrl);
}
objects.put(en.getKey(), en.getValue());
}
for (String objectName : Strings.splitIgnoreBlank(conf.get("shiro.objects", ""))) {
objects.put(objectName, ioc.get(null, objectName));
}
FilterChainResolver resolver = createFilterChainResolver(); FilterChainResolver resolver = createFilterChainResolver();
if (resolver != null) { if (resolver != null) {
setFilterChainResolver(resolver); setFilterChainResolver(resolver);
} }
for (String objectName : Strings.splitIgnoreBlank(conf.get("shiro.objects", ""))) { NutShiro.DefaultLoginURL = loginUrl;
objects.put(objectName, ioc.get(null, objectName)); NutShiro.DefaultNoAuthURL = unauthorizedUrl;
} }
}
public FilterChainResolver createFilterChainResolver() { public FilterChainResolver createFilterChainResolver() {
return ioc.get(FilterChainResolver.class, "shiroFilterChainResolver"); String iniUrls = "[urls]\r\n" + conf.get(ShiroEnvStarter.PROP_INIT_URLS, "").trim();
} log.debug("shiro ini urls ---> \r\n" + iniUrls);
Ini ini = new Ini();
ini.load(iniUrls);
IniFilterChainResolverFactory resolverFactory = new IniFilterChainResolverFactory(ini, objects);
return resolverFactory.getInstance();
}
protected WebSecurityManager createWebSecurityManager() { protected WebSecurityManager createWebSecurityManager() {
return ioc.get(WebSecurityManager.class, "shiroWebSecurityManager"); return ioc.get(WebSecurityManager.class, "shiroWebSecurityManager");
} }
} }

View File

@ -17,8 +17,6 @@ import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.env.DefaultWebEnvironment; import org.apache.shiro.web.env.DefaultWebEnvironment;
import org.apache.shiro.web.env.EnvironmentLoaderListener; import org.apache.shiro.web.env.EnvironmentLoaderListener;
import org.apache.shiro.web.env.WebEnvironment; import org.apache.shiro.web.env.WebEnvironment;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager; import org.apache.shiro.web.mgt.WebSecurityManager;
@ -48,21 +46,32 @@ public class ShiroEnvStarter implements WebEventListenerFace {
protected PropertiesProxy conf; protected PropertiesProxy conf;
@PropDoc(value = "是否启用Shiro的Session管理", defaultValue = "true") @PropDoc(value = "是否启用Shiro的Session管理", defaultValue = "true")
public static final String SHIRO_SESSION_ENABLE = "shiro.session.enable"; public static final String PROP_SESSION_ENABLE = "shiro.session.enable";
@PropDoc(value = "Cookie的name", defaultValue = "sid") @PropDoc(value = "Cookie的name", defaultValue = "sid")
public static final String SHIRO_SESSION_COOKIE_NAME = "shiro.session.cookie.name"; public static final String PROP_SESSION_COOKIE_NAME = "shiro.session.cookie.name";
@PropDoc(value = "Cookie的过期时间,单位:毫秒", defaultValue = "946080000") @PropDoc(value = "Cookie的过期时间,单位:毫秒", defaultValue = "946080000")
public static final String SHIRO_SESSION_COOKIE_MAXAGE = "shiro.session.cookie.maxAge"; public static final String PROP_SESSION_COOKIE_MAXAGE = "shiro.session.cookie.maxAge";
@PropDoc(value = "Cookie是否只读", defaultValue = "true") @PropDoc(value = "Cookie是否只读", defaultValue = "true")
public static final String SHIRO_SESSION_COOKIE_HTTPONLY = "shiro.session.cookie.httpOnly"; public static final String PROP_SESSION_COOKIE_HTTPONLY = "shiro.session.cookie.httpOnly";
@PropDoc(value = "设置使用的缓存类型", defaultValue = "memory") @PropDoc(value = "设置使用的缓存类型", defaultValue = "memory")
public static final String SHIRO_SESSION_CACHE_TYPE = "shiro.session.cache.type"; public static final String PROP_SESSION_CACHE_TYPE = "shiro.session.cache.type";
@PropDoc(value = "设置redis缓存的模式", defaultValue = "kv") @PropDoc(value = "设置redis缓存的模式", defaultValue = "kv")
public static final String SHIRO_SESSION_CACHE_REDIS_MODE= "shiro.session.cache.redis.mode"; public static final String PROP_SESSION_CACHE_REDIS_MODE= "shiro.session.cache.redis.mode";
@PropDoc(value = "session持久化时redis的debug模式", defaultValue = "false") @PropDoc(value = "session持久化时redis的debug模式", defaultValue = "false")
public static final String SHIRO_SESSION_CACHE_REDIS_DEBUG= "shiro.session.cache.redis.debug"; public static final String PROP_SESSION_CACHE_REDIS_DEBUG= "shiro.session.cache.redis.debug";
@PropDoc(value = "redis缓存的过期时间", defaultValue = "-1") @PropDoc(value = "redis缓存的过期时间", defaultValue = "-1")
public static final String SHIRO_SESSION_CACHE_REDIS_TTL= "shiro.session.cache.redis.ttl"; public static final String PROP_SESSION_CACHE_REDIS_TTL= "shiro.session.cache.redis.ttl";
@PropDoc(value = "urls过滤清单")
public static final String PROP_INIT_URLS= "shiro.ini.urls";
@PropDoc(value = "shiro.ini的路径,如果shiro.ini存在,就会使用它,否则走NB的内部逻辑")
public static final String PROP_INIT_PATH= "shiro.ini.path";
@PropDoc(value = "默认登录路径", defaultValue="/user/login")
public static final String PROP_URL_LOGIN= "shiro.url.login";
@PropDoc(value = "退出登录后的重定向路径", defaultValue="/")
public static final String PROP_URL_LOGOUT_REDIRECT= "shiro.url.logout_redirect";
@PropDoc(value = "访问未授权页面后的重定向路径", defaultValue="/user/login")
public static final String PROP_URL_UNAUTH= "shiro.url.unauth";
@Inject @Inject
protected AppContext appContext; protected AppContext appContext;
@ -110,7 +119,7 @@ public class ShiroEnvStarter implements WebEventListenerFace {
}; };
// Shiro Session相关 // Shiro Session相关
if (conf.getBoolean(SHIRO_SESSION_ENABLE, true)) { if (conf.getBoolean(PROP_SESSION_ENABLE, true)) {
webSecurityManager.setSessionManager(ioc.get(WebSessionManager.class, "shiroWebSessionManager")); webSecurityManager.setSessionManager(ioc.get(WebSessionManager.class, "shiroWebSessionManager"));
} }
List<Realm> realms = new ArrayList<>(); List<Realm> realms = new ArrayList<>();
@ -123,12 +132,7 @@ public class ShiroEnvStarter implements WebEventListenerFace {
return webSecurityManager; return webSecurityManager;
} }
@IocBean(name = "shiroFilterChainResolver")
public FilterChainResolver getFilterChainResolver() {
PathMatchingFilterChainResolver filterChainResolver = new PathMatchingFilterChainResolver();
return filterChainResolver;
}
@IocBean(name = "shiroWebSessionManager") @IocBean(name = "shiroWebSessionManager")
public WebSessionManager getWebSessionManager() { public WebSessionManager getWebSessionManager() {
DefaultWebSessionManager webSessionManager = conf.make(DefaultWebSessionManager.class, "shiro.session.manager."); DefaultWebSessionManager webSessionManager = conf.make(DefaultWebSessionManager.class, "shiro.session.manager.");
@ -139,9 +143,9 @@ public class ShiroEnvStarter implements WebEventListenerFace {
webSessionManager.setSessionDAO(sessionDAO); webSessionManager.setSessionDAO(sessionDAO);
// cookie // cookie
conf.putIfAbsent(SHIRO_SESSION_COOKIE_NAME,"sid"); conf.putIfAbsent(PROP_SESSION_COOKIE_NAME,"sid");
conf.putIfAbsent(SHIRO_SESSION_COOKIE_MAXAGE,"946080000"); conf.putIfAbsent(PROP_SESSION_COOKIE_MAXAGE,"946080000");
conf.putIfAbsent(SHIRO_SESSION_COOKIE_HTTPONLY,"true"); conf.putIfAbsent(PROP_SESSION_COOKIE_HTTPONLY,"true");
SimpleCookie cookie = conf.make(SimpleCookie.class, "shiro.session.cookie."); SimpleCookie cookie = conf.make(SimpleCookie.class, "shiro.session.cookie.");
webSessionManager.setSessionIdCookie(cookie); webSessionManager.setSessionIdCookie(cookie);
@ -155,7 +159,7 @@ public class ShiroEnvStarter implements WebEventListenerFace {
@IocBean(name="shiroCacheManager") @IocBean(name="shiroCacheManager")
public CacheManager getCacheManager() { public CacheManager getCacheManager() {
switch (conf.get(SHIRO_SESSION_CACHE_TYPE, "memory")) { switch (conf.get(PROP_SESSION_CACHE_TYPE, "memory")) {
case "ehcache": case "ehcache":
return ioc.get(CacheManager.class, "shiroEhcacheCacheManager"); return ioc.get(CacheManager.class, "shiroEhcacheCacheManager");
case "redis": case "redis":
@ -194,9 +198,9 @@ public class ShiroEnvStarter implements WebEventListenerFace {
@IocBean(name="shiroRedisCacheManager") @IocBean(name="shiroRedisCacheManager")
public CacheManager getRedisLcacheCacheManager() { public CacheManager getRedisLcacheCacheManager() {
conf.putIfAbsent(SHIRO_SESSION_CACHE_REDIS_MODE,"kv"); conf.putIfAbsent(PROP_SESSION_CACHE_REDIS_MODE,"kv");
conf.putIfAbsent(SHIRO_SESSION_CACHE_REDIS_DEBUG,"false"); conf.putIfAbsent(PROP_SESSION_CACHE_REDIS_DEBUG,"false");
conf.putIfAbsent(SHIRO_SESSION_CACHE_REDIS_TTL,"-1"); conf.putIfAbsent(PROP_SESSION_CACHE_REDIS_TTL,"-1");
RedisCacheManager cacheManager = conf.make(RedisCacheManager.class, "shiro.session.cache.redis."); RedisCacheManager cacheManager = conf.make(RedisCacheManager.class, "shiro.session.cache.redis.");
return cacheManager; return cacheManager;
} }

View File

@ -8,7 +8,6 @@ import javax.servlet.DispatcherType;
import javax.servlet.Filter; import javax.servlet.Filter;
import org.apache.shiro.web.servlet.ShiroFilter; import org.apache.shiro.web.servlet.ShiroFilter;
import org.nutz.boot.AppContext;
import org.nutz.boot.starter.WebFilterFace; import org.nutz.boot.starter.WebFilterFace;
import org.nutz.integration.shiro.ShiroFilter2; import org.nutz.integration.shiro.ShiroFilter2;
import org.nutz.ioc.Ioc; import org.nutz.ioc.Ioc;
@ -18,20 +17,12 @@ import org.nutz.ioc.loader.annotation.IocBean;
@IocBean @IocBean
public class ShiroFilterStarter implements WebFilterFace { public class ShiroFilterStarter implements WebFilterFace {
@Inject("refer:$ioc")
protected Ioc ioc;
@Inject
protected PropertiesProxy conf;
@Inject
protected AppContext appContext;
public void setAppContext(AppContext appContext) { @Inject("refer:$ioc")
this.appContext = appContext; protected Ioc ioc;
ShiroUtil.appContext = appContext;
} @Inject
protected PropertiesProxy conf;
public String getName() { public String getName() {
return "shiro"; return "shiro";
@ -44,10 +35,10 @@ public class ShiroFilterStarter implements WebFilterFace {
public EnumSet<DispatcherType> getDispatches() { public EnumSet<DispatcherType> getDispatches() {
return EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ERROR, DispatcherType.ASYNC); return EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ERROR, DispatcherType.ASYNC);
} }
@IocBean(name="shiroFilter") @IocBean(name = "shiroFilter")
public ShiroFilter createShiroFilter() { public ShiroFilter createShiroFilter() {
return new ShiroFilter2(); return new ShiroFilter2();
} }
public Filter getFilter() { public Filter getFilter() {

View File

@ -1,8 +0,0 @@
package org.nutz.boot.starter.shiro;
import org.nutz.boot.AppContext;
public class ShiroUtil {
public static AppContext appContext;
}

View File

@ -1,2 +1,2 @@
org.nutz.boot.starter.shiro.ShiroFilterStarter org.nutz.boot.starter.shiro.ShiroFilterStarter
org.nutz.boot.starter.shiro.ShiroEnvStarter org.nutz.boot.starter.shiro.ShiroEnvStarter