Shiro - web应用
先不谈Spring,首先试着用最简易的方式将Shiro集成到web应用。 即使用一些Servlet ContextListener、Filter、ini这些简单的配置完成与web应用的集成。
web.xml:
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<context-param>
<param-name>shiroEnvironmentClass</param-name>
<param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
</context-param>
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>classpath:shiro_web.ini</param-value>
</context-param>
上面的配置中我注册了一个Listener——org.apache.shiro.web.env.EnvironmentLoaderListener。
该类的意义主要是为了实现ServletContextListener,将WebEnvironment随着ServletContext事件进行创建和销毁。
对WebEnvironment的处理逻辑全部在其父类——EnvironmentLoader中。
WebEnvironment的类关系图:
如果想获取WebEnvironment则可以试试以下方法:
WebUtils.getRequiredWebEnvironment(servletContext);
上面的配置中用到了两个参数(事实上EnvironmentLoader也只有这两个参数)。
- shiroEnvironmentClass
- shiroConfigLocations
shiroEnvironmentClass用于指定使用的WebEnvironment实现类,缺省值为org.apache.shiro.web.env.IniWebEnvironment。
IniWebEnvironment根据设置的.ini配置文件的路径创建ini实例,如果无法获得.ini配置文件则抛出ConfigurationException。
当然,如果有需要(比如换个配置格式、解析方法什么的...),我们也可以自己实现一个WebEnvirontment,并通过shiroEnvironmentClass属性来进行注册。
而shiroConfigLocations则是指定.ini配置文件的路径的参数。
如果没有进行手动指定,他会尝试在以下两个路径中寻找:
public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
public static final String DEFAULT_INI_RESOURCE_PATH = "classpath:shiro.ini";
顺便记录,IniWebEnvironment查找.ini配置时使用ResourceUtils,见:
private Ini convertPathToIni(String path, boolean required) {
//TODO - this logic is ugly - it'd be ideal if we had a Resource API to polymorphically encaspulate this behavior
Ini ini = null;
if (StringUtils.hasText(path)) {
InputStream is = null;
//SHIRO-178: Check for servlet context resource and not only resource paths:
if (!ResourceUtils.hasResourcePrefix(path)) {
is = getServletContextResourceStream(path);
} else {
try {
is = ResourceUtils.getInputStreamForPath(path);
} catch (IOException e) {
if (required) {
throw new ConfigurationException(e);
} else {
if (log.isDebugEnabled()) {
log.debug("Unable to load optional path '" + path + "'.", e);
}
}
}
}
if (is != null) {
ini = new Ini();
ini.load(is);
} else {
if (required) {
throw new ConfigurationException("Unable to load resource path '" + path + "'");
}
}
}
return ini;
}
该方法首先调用ResourceUtils.hasResourcePrefix(path)检查路径前缀是否符合以下三种之一:
public static final String CLASSPATH_PREFIX = "classpath:";
public static final String URL_PREFIX = "url:";
public static final String FILE_PREFIX = "file:";
如果不符合这三种前缀则在Servlet Context进行查找。
如果符合三种前缀之一,则调用ResourceUtils.getInputStreamForPath(path),根据path及其不同的前缀以不同的方式获取输入流。
对于classpath,调用ClassUtils.getResourceAsStream(path);,通过ClassLoader实例调用getResourceAsStream(name);
对于url,则是返回url.openStream();
对于file,返回new FileInputStream(path);
继续配置web.xml,这次添加一个Filter:
<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>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
这是基于当前的WebEnvironment实例配置的Filter,即单独存在没什么意义。
ShiroFilter用WebEnvironment实例对所有被过滤的请求进行安全处理。
Shiro提供的一些Filter实现:
暂且不论AdviceFilter,我们使用的ShiroFilter在AbstractShiroFilter下。
其中IniShiroFilter从1.2开始已deprecated了,但这个东西用起来还是有点意思的,只不过没什么意义。
IniShiroFilter不需要同时配置EnvironmentLoaderListener,也就是说这里面没有WebEnvironment对象,他本身就是一个简易的Environment。
有意思的地方就是这点,他可以把.ini中的配置直接写到web.xml,比如这样:
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
<init-param>
<param-name>config</param-name>
<param-value>
[urls]
/main/logout = logout
/main/loginPage = anon
/** = user
[main]
user.loginUrl = /main/login
authc.successUrl = /main/welcome
myRealm=pac.king.common.security.realm.MainRealm
securityManager.realms=$myRealm
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
有意思,但没什么意义。
Shiro也建议用户们不要这样配置,对此他们给出了几个理由:
- 安全配置可能会经常变化,而我们不想总是修改web.xml。
- 安全配置可能会越来越庞大,这会影响web.xml的可读性。
- 我们尽量保证安全配置不会散落在各个地方。
无论如何,这取决于用户和项目。
另外说说web应用相关的ini配置。
之前几篇中用过[main]、[users]、[roles]等片段,在web应用中我们可以试试[urls]。
[urls]也是Shiro的一大卖点(文档提供人说根本没见过其他web framework也能做到这点)。
就是为每个URL配置专有的filter chain!!
[urls]的格式如下:
URL_Ant_Path_Expression = Path_Specific_Filter_Chain[optional_config]
左侧使用Ant风格的表达式描述URL;
右侧则是用逗号分隔的过滤器链;
最后的optional_config则是一些附加属性,比如描述对用户资源有删除操作的权限perms["user:delete"]。
配置[urls],官网上的例子:
[urls]
/index.html = anon
/user/create = anon
/user/** = authc
/admin/** = authc, roles[administrator]
/rest/** = authc, rest
/remoting/rpc/** = authc, perms["remote:invoke"]
URL是相对路径,即使部署的时候换了个域名也没有问题。
注意!URL配置的顺序对filter chain是有影响的!他是FIRST MATCH WINS。
比如下面的例子中,第二行配置就不会生效。
/user/** = authc
/user/list = anon
默认的Filter,比如anon,authc,users等等,他们是由哪些类来实现的?
Filter Name | Class |
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
logout | org.apache.shiro.web.filter.authc.LogoutFilter |
noSessionCreation | org.apache.shiro.web.filter.session.NoSessionCreationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port | org.apache.shiro.web.filter.authz.PortFilter |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilterv |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl | org.apache.shiro.web.filter.authz.SslFilter |
user | org.apache.shiro.web.filter.authc.UserFilter |
应用启动时,将默认Filter全部加载。
默认Filter的定义见enum类DefaultFilter。
pool of Filters则定义在DefaultFilterChainManager中,用LinkedHashMap维护。DefaultFilterChainManager在constructor中调用void addDefaultFilters(boolean init)将Filters放入Map中。
随着应用做得越来越大,这些URL会变得越来越难以管理。
当然,我们也可以把这些URL放在数据库里管理,但总是有个别的URL需要特殊配置Filter。
就以我现在工作中的应用为例,我们将大多数URL放到数据库管理,并全部采用authc+perms过滤器,perms的options也是该URL。
即有该URL权限的用户可以访问该URL。
但总有那么些例外的、奇怪的东西,除了数据库里的URL,我又在.ini中写了差不多30个URL,再配上各种各样的Filter,当然还要注意顺序的影响。
随着开发、测试、生产环境的切换,这些filters也需要可以进行启用/禁用。
我总不把filter能一个个删掉再一个个写回去...
见OncePerRequestFilter有个field:
private boolean enabled = true;
而且所有的default filters都继承了OncePerRequestFilter!!
于是我可以直接在ini文件中直接进行启用/禁用,比如这样:
user.enabled=false
当然,我们也可以试着自定义一个Filter(比如根据判断具体的请求或者路径,动态将所有filter启用/禁用),并在[main]注册。
另外,上面的类关系图中AccessControlFilter有一个field为loginUrl,其默认值为:
public static final String DEFAULT_LOGIN_URL = "/login.jsp";
我们经常使用的filter中的authc(FormAuthenticationFilter)中存在以下属性:
public static final String DEFAULT_USERNAME_PARAM = "username";
public static final String DEFAULT_PASSWORD_PARAM = "password";
public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe";
private String usernameParam = DEFAULT_USERNAME_PARAM;
private String passwordParam = DEFAULT_PASSWORD_PARAM;
private String rememberMeParam = DEFAULT_REMEMBER_ME_PARAM;
我们可以在表单中使用这些属性,让其进行认证+remember me。
当然,这些值也是可以改变的,比如:
[main]authc.loginUrl = /main/loginauthc.usernameParam = userNameauthc.passwordParam = pwdauthc.rememberMeParam = rememberCookie
说到remember me,其实现是有RememberMeManager提供,默认实现是基于Cookie的。
比如DefaultWebSecurityManager的constructor中将CookieRememberMeManager设为默认(field定义于其父类DefaultSecurityManager):
public DefaultWebSecurityManager() {
super();
((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
this.sessionMode = HTTP_SESSION_MODE;
setSubjectFactory(new DefaultWebSubjectFactory());
setRememberMeManager(new CookieRememberMeManager());
setSessionManager(new ServletContainerSessionManager());
}
看起来不错,那我就一步步detect看看RememberMeManager是怎么manage的。
用户登录时我们调用Subject.login(token)
以DelegaingSubject为例,第一步直接将验证工作委托给securityManager。
工作中一步步进行委托,securityManager -> authenticator -> realm...
验证通过后将AuthenticationInfo结果返回到securityManager,securityManager将结果传递给RememberMeManager,委托rememberMe的工作。
参考AbstractRememberMeManager中的method:
public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
//always clear any previous identity:
forgetIdentity(subject);
//now save the new identity:
if (isRememberMe(token)) {
rememberIdentity(subject, token, info);
} else {
if (log.isDebugEnabled()) {
log.debug("AuthenticationToken did not indicate RememberMe is requested. " +
"RememberMe functionality will not be executed for corresponding account.");
}
}
}
第一步:先将Cookie移除,Shiro默认使用的Cookie是自己的SimpleCookie,调用其removeFrom方法将Cookie"移除"。
第二步:检查token是否是RememberMeAuthenticationToken的实例并是否设置了rememberMe=true。
第三步:进行rememberMe的具体工作,这个工作由AbstractRememberMeManager的子类进行。
以CookieRememberMeManager为例:
protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
if (!WebUtils.isHttp(subject)) {
if (log.isDebugEnabled()) {
String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " +
"request and response in order to set the rememberMe cookie. Returning immediately and " +
"ignoring rememberMe operation.";
log.debug(msg);
}
return;
}
HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response = WebUtils.getHttpResponse(subject);
//base 64 encode it and store as a cookie:
String base64 = Base64.encodeToString(serialized);
Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
Cookie cookie = new SimpleCookie(template);
cookie.setValue(base64);
cookie.saveTo(request, response);
}
代码非常简单,接着转到SimpleCookie:
public void saveTo(HttpServletRequest request, HttpServletResponse response) {
String name = getName();
String value = getValue();
String comment = getComment();
String domain = getDomain();
String path = calculatePath(request);
int maxAge = getMaxAge();
int version = getVersion();
boolean secure = isSecure();
boolean httpOnly = isHttpOnly();
addCookieHeader(response, name, value, comment, domain, path, maxAge, version, secure, httpOnly);
}
private void addCookieHeader(HttpServletResponse response, String name, String value, String comment,
String domain, String path, int maxAge, int version,
boolean secure, boolean httpOnly) {
String headerValue = buildHeaderValue(name, value, comment, domain, path, maxAge, version, secure, httpOnly);
response.addHeader(COOKIE_HEADER_NAME, headerValue);
if (log.isDebugEnabled()) {
log.debug("Added HttpServletResponse Cookie [{}]", headerValue);
}
}
但毕竟很多人不喜欢cookie...我们也可以自己去实现RememberMeManager,并进行注册(仍然是注入到securityManger):
rememberMeManager = com.my.impl.RememberMeManager
securityManager.rememberMeManager = $rememberMeManager
我们使用的UsernamePasswordToken继承的RememberMeAuthenticationToken提供rememberMe特性。
boolean isRememberMe();
比如我们可以Realm的验证方法中这样使用:
UsernamePasswordToken uToken = (UsernamePasswordToken)token;
uToken.setRememberMe(true);
Shiro - web应用的更多相关文章
- shiro web 集成
集成方法 shiro与web集成,主要是通过配置一个ShiroFilter拦截所有URL,其中ShiroFilter类似于SpringMVC的前端控制器,是所有请求入口点,负责根据配置(如ini配置文 ...
- Apache Shiro学习-2-Apache Shiro Web Support
Apache Shiro Web Support 1. 配置 将 Shiro 整合到 Web 应用中的最简单方式是在 web.xml 的 Servlet ContextListener 和 Fil ...
- Shiro Shiro Web Support and EnvironmentLoaderListener
Shiro Shiro Web Support 主要参考: http://shiro.apache.org/web.html 还有涛哥的 作为资源控制访问的事情,主要使用在网络后台方面,所以了解了本地 ...
- Shiro Web集成及拦截器机制(四)
Shiro与 Web 集成 Shiro 提供了与 Web 集成的支持,其通过一个 ShiroFilter 入口来拦截需要安全控制的 URL,然后进行相应的控制,ShiroFilter 类似于如 Str ...
- shiro web环境初始化过程
在web工程中使用shiro的时候需要配置一个shiro的listenser(EnvironmentLoaderListener)和一个shiro的filter(ShiroFilter). liste ...
- org.apache.shiro.web.servlet.ShiroHttpServletRequest cannot be cast to org.springframwork.web.mult..
在用dwz框架+spring MVC时上传文件报的错 详细错误例如以下: 事实上就是一个类型转换错误,但却研究了好长时间,怎么都不知道哪里错了.由于前面卸过一个文件上传的和这个差点儿相同,那个就没有问 ...
- 基于Spring + Spring MVC + Mybatis + shiro 高性能web构建
一直想写这篇文章,前段时间 痴迷于JavaScript.NodeJs.AngularJS,做了大量的研究,对前后端交互有了更深层次的认识. 今天抽个时间写这篇文章,我有预感,这将是一篇很详细的文章,详 ...
- [转]在 Web 项目中应用 Apache Shiro
目录[-] 用户权限模型 图 1. 用户权限模型 认证与授权 Shiro 认证与授权处理过程 Shiro Realm 清单 1. 实现自己的 JDBC Realm 为何对 Shiro 情有独钟 与 S ...
- 在 Web 项目中应用 Apache Shiro
Apache Shiro 是功能强大并且容易集成的开源权限框架,它能够完成认证.授权.加密.会话管理等功能.认证和授权为权限控制的核心,简单来说,"认证"就是证明你是谁? Web ...
随机推荐
- MongoDB基础知识记录
MongoDB基础知识记录 一.概念: 讲mongdb就必须提一下nosql,因为mongdb是nosql的代表作: NoSQL(Not Only SQL ),意即“不仅仅是SQL” ,指的是非关系型 ...
- 对Java意义重大的7个性能指标
本文中,小编搜集了7个最有影响的衡量标注,让你可以不依赖日志文件来了解应用程序现在,让我们看看这些性能指标,并了解如何查看并收集它们.: 一,响应时间和吞吐量 根据应用程序的响应时间可以知道程序完 ...
- “全栈2019”Java第四十二章:静态代码块与初始化顺序
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- 木马APP的简单分析(Android Killer分析)
本文作者:三星s7edge 一.此贴目的:分析一个木马APP样本的行为.—————————————————————————————————————————————————-二.分析步骤及结果: 文件名 ...
- redis删除指定前缀的缓存
redis作为缓存服务器为MySQL数据库提供较高的防御性,对于一些数据的查询可以直接从缓存中可以进行查询. 但是,某些情况下,我们需要清除缓存. 以下场景: 公司经常做活动,每个活动都存在大量的数据 ...
- 使用git提交代码简单说明
一. 图形化git 1.首先下载msysgit,和 tortoisegit, 先装msysgit再装小乌龟 2.运行git按照github上说明生成秘钥对 ssh-keygen -t rsa ...
- uC/OS-II 函数之信号量相关函数
上文主要介绍了时间相关的函数,本文介绍信号量相关的函数:OSSemCreate()建立信号量函数,OSSemPend()取得使用权函数,OSSemPost()使用权递加函数 信号量介绍 如果我们想对一 ...
- springboot 项目中 使用 原型 模式 实现每一次 都获取不同的实例
直接 上代码:
- Django上传文件的那些参数
# ################## 默认文件上传配置 ######################## from django.core.files.uploadhandler import M ...
- 修改ZuulHandlerMapping私有变量pathMatcher,重写match方法,使url匹配兼容正则表达式。
一.起源 在mocksever中引入了spring cloud zuul做代理转发,如果请求的url和配置的mock规则匹配(精确匹配和模糊匹配),忽略,不做转发.如果mock配置的url和请求url ...