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

View File

@ -6,6 +6,7 @@ import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.nutz.boot.AppContext;
@ -28,27 +29,10 @@ public class PropDocReader {
* 文档列表
*/
protected Map<String, PropDocBean> docs = new HashMap<>();
/**
* 创建一个空PropDocReader
*/
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());
public void load(List<Class<?>> starterClasses) throws Exception {
for (Class<?> klass : starterClasses) {
addClass(klass);
}
}
@ -117,13 +101,4 @@ public class PropDocReader {
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.dao.Dao;
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.IocBean;
import org.nutz.lang.random.R;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Fail;
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;
@ -41,23 +37,6 @@ public class MainLauncher {
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() {
Daos.createTablesInPackage(dao, User.class, false);
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
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>
<head>
<meta charset="UTF-8">
<title>Hello, So NB!</title>
<title>Shiro测试页</title>
</head>
<body>
Hello, So NB!
<h2><a href="home.html?_=1">点我,访问一个未授权路径,会跳转到登录页面</a></h2>
</body>
</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;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.shiro.ShiroException;
import org.apache.shiro.config.Ini;
import org.apache.shiro.util.Destroyable;
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.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.mgt.WebSecurityManager;
import org.nutz.boot.AppContext;
import org.nutz.integration.shiro.NutShiro;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.lang.Strings;
import org.nutz.log.Log;
import org.nutz.log.Logs;
public class NbResourceBasedWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable {
protected AppContext appContext;
protected Ioc ioc;
protected PropertiesProxy conf;
public void init() throws ShiroException {
appContext = ShiroUtil.appContext;
ioc = appContext.getIoc();
conf = appContext.getConfigureLoader().get();
configure();
}
protected void configure() {
this.objects.clear();
private static final Log log = Logs.get();
protected AppContext appContext;
protected Ioc ioc;
protected PropertiesProxy conf;
public void init() throws ShiroException {
appContext = AppContext.getDefault();
ioc = appContext.getIoc();
conf = appContext.getConfigureLoader().get();
configure();
}
protected void configure() {
this.objects.clear();
WebSecurityManager securityManager = createWebSecurityManager();
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();
if (resolver != null) {
setFilterChainResolver(resolver);
}
for (String objectName : Strings.splitIgnoreBlank(conf.get("shiro.objects", ""))) {
objects.put(objectName, ioc.get(null, objectName));
}
}
NutShiro.DefaultLoginURL = loginUrl;
NutShiro.DefaultNoAuthURL = unauthorizedUrl;
}
public FilterChainResolver createFilterChainResolver() {
return ioc.get(FilterChainResolver.class, "shiroFilterChainResolver");
}
public FilterChainResolver createFilterChainResolver() {
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() {
return ioc.get(WebSecurityManager.class, "shiroWebSecurityManager");
}
protected WebSecurityManager createWebSecurityManager() {
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.EnvironmentLoaderListener;
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.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;
@ -48,21 +46,32 @@ public class ShiroEnvStarter implements WebEventListenerFace {
protected PropertiesProxy conf;
@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")
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")
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")
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")
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")
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")
public static final String SHIRO_SESSION_CACHE_REDIS_DEBUG= "shiro.session.cache.redis.debug";
@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_DEBUG= "shiro.session.cache.redis.debug";
@PropDoc(value = "redis缓存的过期时间", defaultValue = "-1")
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
protected AppContext appContext;
@ -110,7 +119,7 @@ public class ShiroEnvStarter implements WebEventListenerFace {
};
// Shiro Session相关
if (conf.getBoolean(SHIRO_SESSION_ENABLE, true)) {
if (conf.getBoolean(PROP_SESSION_ENABLE, true)) {
webSecurityManager.setSessionManager(ioc.get(WebSessionManager.class, "shiroWebSessionManager"));
}
List<Realm> realms = new ArrayList<>();
@ -123,12 +132,7 @@ public class ShiroEnvStarter implements WebEventListenerFace {
return webSecurityManager;
}
@IocBean(name = "shiroFilterChainResolver")
public FilterChainResolver getFilterChainResolver() {
PathMatchingFilterChainResolver filterChainResolver = new PathMatchingFilterChainResolver();
return filterChainResolver;
}
@IocBean(name = "shiroWebSessionManager")
public WebSessionManager getWebSessionManager() {
DefaultWebSessionManager webSessionManager = conf.make(DefaultWebSessionManager.class, "shiro.session.manager.");
@ -139,9 +143,9 @@ public class ShiroEnvStarter implements WebEventListenerFace {
webSessionManager.setSessionDAO(sessionDAO);
// cookie
conf.putIfAbsent(SHIRO_SESSION_COOKIE_NAME,"sid");
conf.putIfAbsent(SHIRO_SESSION_COOKIE_MAXAGE,"946080000");
conf.putIfAbsent(SHIRO_SESSION_COOKIE_HTTPONLY,"true");
conf.putIfAbsent(PROP_SESSION_COOKIE_NAME,"sid");
conf.putIfAbsent(PROP_SESSION_COOKIE_MAXAGE,"946080000");
conf.putIfAbsent(PROP_SESSION_COOKIE_HTTPONLY,"true");
SimpleCookie cookie = conf.make(SimpleCookie.class, "shiro.session.cookie.");
webSessionManager.setSessionIdCookie(cookie);
@ -155,7 +159,7 @@ public class ShiroEnvStarter implements WebEventListenerFace {
@IocBean(name="shiroCacheManager")
public CacheManager getCacheManager() {
switch (conf.get(SHIRO_SESSION_CACHE_TYPE, "memory")) {
switch (conf.get(PROP_SESSION_CACHE_TYPE, "memory")) {
case "ehcache":
return ioc.get(CacheManager.class, "shiroEhcacheCacheManager");
case "redis":
@ -194,9 +198,9 @@ public class ShiroEnvStarter implements WebEventListenerFace {
@IocBean(name="shiroRedisCacheManager")
public CacheManager getRedisLcacheCacheManager() {
conf.putIfAbsent(SHIRO_SESSION_CACHE_REDIS_MODE,"kv");
conf.putIfAbsent(SHIRO_SESSION_CACHE_REDIS_DEBUG,"false");
conf.putIfAbsent(SHIRO_SESSION_CACHE_REDIS_TTL,"-1");
conf.putIfAbsent(PROP_SESSION_CACHE_REDIS_MODE,"kv");
conf.putIfAbsent(PROP_SESSION_CACHE_REDIS_DEBUG,"false");
conf.putIfAbsent(PROP_SESSION_CACHE_REDIS_TTL,"-1");
RedisCacheManager cacheManager = conf.make(RedisCacheManager.class, "shiro.session.cache.redis.");
return cacheManager;
}

View File

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