Apache Shiro 提供的Web支持--官网

Apache Shiro 的Web支持

配置

将Shiro集成到web项目中最简单的方式就是在web.xml中配置一个ServletContextListener和过滤器,让Web项目知道如何读取Shiro的INI配置文件。关于INI配置文件的内容,在前面有描述,这里将会讲述一些Web相关的小节

如果使用了Spring框架,将不需要考虑这一步,详情请看Spring集成Shiro的相关文档

web.xml

Shiro1.2及以后

Shiro1.2及之后的版本中,标准的Web应用程序集成Shiro,就是将下列的xml内容添加到web.xml中:


    org.apache.shiro.web.env.EnvironmentLoaderListener


...


    ShiroFilter
    org.apache.shiro.web.servlet.ShiroFilter



    ShiroFilter
    /*
    REQUEST
    FORWARD
    INCLUDE
    ERROR

如果没有显式配置INI配置文件的位置,Shiro将会按照下面的顺序找配置文件,使用第一个找到的配置文件:

  1. /WEB-INF/shiro.ini
  2. 类路径的根路径下的shiro.ini

以上的配置其实是做了以下的事:

  • EnvironmentLoaderListener会初始化一个Shiro的WebEnvironment实例(该实例包含了Shiro需要的所有东西,包括SecurityManager),并将这个实例设置到了ServletContext中使其可用。如果需要获取这个WebEnvironment实例,可以通过WebUtils.getRequiredWebEnvironment(servletContext)获得
  • ShiroFilter将会通过WebEnvironment来执行所有的安全相关操作
  • 最后,filter-mapping定义确保了所有的请求都能经过ShiroFilter的过滤

通常需要把ShiroFilterfilter-mapping定义在所有的filter-mapping之前,确保Shiro也能作用在其他过滤器之中

ShiroFilter是一个标准的servlet过滤器,它的默认编码是ISO-8859-1,然而客户端可以通过使用请求头的Content-Type头部的charset属性来指定编码

自定义 WebEnvironment 类

EnvironmentLoaderListener将默认创建的是IniWebEnvironment实例,顾名思义是通过INI配置Shiro。如果有需要,可以在web.xml中为ServletContext指定一个context-param参数,来指定WebEnvironment


    shiroEnvironmentClass
    com.foo.bar.shiro.MyWebEnvironment

这种方式可以让我们自定义Shiro配置的格式,并将其解析为一个WebEnvironment实例,可以继承IniWebEnvironment来继续使用ini的配置格式,或者完全自定义配置格式

配置文件路径

IniWebEnvironment类需要读取INI配置文件才能配置Shiro,默认情况下,这个类将会自动的从以下两个位置顺序的寻找配置文件,只要找到了配置文件就会使用它(如果同时在两个位置都有配置文件,则会使用第一个/WEB-INF/shiro.ini

  1. /WEB-INF/shiro.ini
  2. classpath:shiro.ini

可以在web.xml中配置一个context-param来指定配置文件的位置


    shiroConfigLocations
    YOUR_RESOURCE_LOCATION_HERE

默认情况下,这个参数值会按照ServletContext.getResource()方法来解析,如"/WEB-INF/some/path/shiro.ini"。Shiro的ResourceUtils类也支持"url:""file:"这样的资源路径前缀来指定路径,下面的路径也是可以使用的

  • file:/home/foobar/myapp/shiro.ini
  • classpath:com/foo/bar/shiro.ini
  • url:http://confighost.mycompany.com/myapp/shiro.ini

Shiro1.1及之前

Web应用集成Shiro1.1及之前版本,最简单的就是定义IniShiroFilter及其相关filter-mapping


    ShiroFilter
    org.apache.shiro.web.servlet.IniShiroFilter


...





    ShiroFilter
    /*
    REQUEST
    FORWARD
    INCLUDE
    ERROR

以上定义要求INI配置,且INI配置文件在类路径的根路径下,即classpath:shiro.ini。注意这里放在/WEB-INF/下面无效

配置文件路径

如果不想让INI配置文件放在classpath:shiro.ini,也可以指定一个资源文件路径。需要为ShiroFilter添加一个configPath参数


    ShiroFilter
    org.apache.shiro.web.servlet.IniShiroFilter
    
        configPath
        classpath:anotherFile.ini
    


...

请注意,ServletContext 资源路径是在Shiro1.2及之后可用的,在1.1及之前的版本中,所有的configPath定义都需要指定classpath:file:或者url:前缀,也就是说,这里不能写/WEB-INF/shiro.ini,就算写了也会没有效果。官网写的有点模糊,试了下,应该是这样

在web.xml文件中嵌入Shiro配置

可以使用名为configinit-param的参数设置,将INI配置嵌入到web.xml中,这样就不需要使用额外的ini配置文件了


    ShiroFilter
    org.apache.shiro.web.servlet.IniShiroFilter
    config

    # INI Config Here

    

...

这种配置方式比较适用于小的,简单的应用,但出于以下原因,将其放到专门的shiro.ini文件中通常更为方便:

  • 你可能会频繁修改安全的配置,这会为web.xml增加一个版本控制噪声
  • 你可能想要将安全相关的配置从web.xml中分离出来
  • 你可能需要写很多Shiro的相关配置,如果写了太多,反而会让web.xml文件过于臃肿
  • 相同的Shiro配置需要用在多个部分时

Web相关的INI配置项

[main][users][roles]小节前面已经讲述过了,这里重点介绍Web项目特有的[urls]小节

# [main], [users] and [roles] above here
...
[urls]
...

[urls]小节允许你为你的应用程序中任何匹配的URL路径定义专门的过滤器链的能力,这比在web.xml中定义过滤器链的方式更加灵活、强大和简洁,即使只使用这个特性,它也值得一用

[urls]

URL路径表达式

[urls] 小节的每一行遵从以下格式:

_URL_Ant_Path_Expression_ = _Path_Specific_Filter_Chain_

例如:

...
[urls]

/index.html = anon
/user/create = anon
/user/** = authc
/admin/** = authc, roles[administrator]
/rest/** = authc, rest
/remoting/rpc/** = authc, perms["remote:invoke"]

等号左边的是一个Ant风格的路径表达式,注意,所有的路径表达式都是从应用程序环境中的根目录开始匹配的,也就是说,如果你的部署从www.somehost.com/myapp换成了www.anotherhost.com,配置不需要更改,依然有效。所有的路径都是相对于HttpServletRequest.getContextPath()的值

到来的请求会按照urls定义的顺序来匹配,第一个匹配的配置项则为该路径需要通过的过滤器链。假如有如下配置

/account/** = ssl, authc
/account/signup = anon

则请求路径/account/signup将永远不会被正确的处理(匿名可访问),应为第一条配置项/account/**已经匹配了这个请求,后面的就没用了

过滤器链定义

等号右边就是以逗号风格的过滤器链,它的格式如下

filter1[optional_config1], filter2[optional_config2], ..., filterN[optional_configN]
  • filterNFilter[main]小节中定义的名字
  • [optional_config]是一个带括号的可选字符串,它只对部分路径的部分过滤器有意义,如果过滤器不需要这个特定的配置,可以放弃方括号,这样filter[]就变成了filter

请求通过过滤链的顺序就是它们在配置文件中定义的顺序。

响应也会通过这个过滤器链,如果没有满足必要条件(例如执行重定向、响应带有HTTP错误代码、直接呈现等),每个过滤器都可以自由地处理响应。否则,它将允许请求继续通过链到达最终目标视图

通过[optional_configN]能够对特定路径配置做出反应,这是Shiro的独特特性,如果你想要创建一个能做到这些功能的javax.servlet.Filter实现,需要确保其继承了org.apache.shiro.web.filter.PathMatchingFilter

可用的过滤器

[main]小节中可以定义需要使用的过滤器,其中定义的名字就是在过滤器链的定义中需要使用的名字,如下所示

[main]
...
myFilter = com.company.web.some.FilterImplementation
myFilter.property1 = value1
...

[urls]
...
/some/path/** = myFilter

默认过滤器

当运行一个web应用的时候,Shiro默认会创建一些有用的过滤器实例,我们可以在[main]小节中直接使用这些实例,也可以在[main]小节中对它们进行配置,就和其他定义的bean一样

[main]
...
# 我们并没有定义FormAuthenticationFilter ('authc'),这是Shiro自动定义好的
authc.loginUrl = /login.jsp
...

[urls]
...
# 如果用户没有经过认证,则防止这个URL时将会重定向到authc.loginUrl配置的路径,之后如果用户通过了认证,则将会回到原来的页面
/account/** = authc
...

以下是Shiro自动定义好的过滤器及其在main中可用的名字

名字
anno org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
authcBearer org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter
invalidRequest org.apache.shiro.web.filter.InvalidRequestFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.zuthc.UserFilter

开启和关闭过滤器

在过滤器链中开启或关闭一个过滤器最直接的方式,就是通过将其从过滤器链定义中添加或删除其名字。但是在Shiro1.2之后新增加了一个特性,可以不用通过前面那种方式就能开启或关闭过滤器,可以通过一个配置的属性来判断是否开启过滤器,甚至也可以通过每一个请求来判断

Shiro提供了一个OncePerRequestFilter的抽象类,所有的继承它的过滤器都可以在不将其从过滤器链中删除的情况下控制其是否生效,我们也可以继承这个类来实现这个功能

一般的开启和关闭

OncePerRequestFilter的开关可以对所有的请求生效,也可以对基于某一个请求生效。设置enabled属性为true或false就可以设置它的开关,该属性的默认值为true,因此大多数过滤器都是开启状态的

[main]
...
# 配置ssl过滤器失效
ssl.enabled = false

[urls]
...
/some/path = ssl, authc
/another/path = ssl, roles[admin]
...

绝大多数情况下,URL都是配置了SSL的,如果在开发时还开启了SSL,那是很难受的,所以可以先将其关闭,在生产环境下再把它开起来

根据请求

OncePerRequestFilter还可以通过它的isEnable(request, response) 方法来对每一个请求进行检查,判断是否需要经过该过滤器过滤。该方法的默认实现是直接返回enable属性,也就是说,本质上OncePerRequestFilter就是通过isEnable方法来判断是否开启该过滤器的。我们可以通过重写这个isEnable(request, response)来进行更细致的检查

根据路径

Shiro的PathMatchingFilterOncePerRequestFilter的子类)拥有对特定路径的请求进行过滤的能力,也就是说,可以对一个请求的路径来判断是否开启过滤器。如果你的过滤器需要这种能力,则可以重写PathMatchingFilterisEnabled(request, response, path, pathConfig)方法

全局过滤器

Shiro1.6开始,可以定义一个一条全局过滤器链,它将会作用于所有的路由,包括哪些没有配置过滤器链的路由。默认情况下,这个全局过滤器链包含了invalidRequest过滤器(上面的表格中有),这个过滤器是负责过滤掉无效请求的,可以阻挡一些恶意攻击

全局过滤器链的配置和关闭如下

[main]
...
# 关闭全局过滤器链
filterChainResolver.globalFilters = null

定义一条全局过滤器链

[main]
...
filterChainResolver.globalFilters = invalidRequest, port

invalidRequest过滤器会阻塞使用非ascii字符、分号和反斜杠的请求,可以单独禁用其中的每一个,以便于向后兼容

[main]
...
invalidRequest.blockBackslash = true
invalidRequest.blockSemicolon = true
invalidRequest.blockNonAscii = true
...

如果你的应用程序允许在URL中重写jsessionid,那么必须将blockSemicolon设置为false

HSTS

SslFilter及其所有的子类,支持HSTS的概念,我们可以开启或关闭HSTS

[main]
...
# 配置ssl过滤器开启HSTS
ssl.enabled = true
ssl.hsts.enabled = true
ssl.hsts.includeSubDomains = true

[urls]
...
/some/path = ssl, authc
/another/path = ssl, roles[admin]
...

会话管理

Servlet容器会话

在Web环境下,Shiro的默认会话管理器是ServletContainerSessionManager,Shiro会把所有的会话管理都委托给Servlet容器(包括会话集群,如果Servlet容器支持的话)。这是一个Shiro的SessionAPIServlet容器的桥梁

这种方式的好处就是,应用程序可以使用Servlet容器的配置,如会话超时,容器的集群设置等等。缺点就是应用程序和Servlet容器绑定了,比如如果开发环境使用的是Jetty,生产环境使用Tomcat,那么一些特定于容器的配置将会需要更改

Servlet容器会话的超时时间

如果使用Servlet容器来管理会话的话,则可以在web.xml中配置会话超时时间


    
    30

原生会话

如果想要会话的配置能够跨Servlet容器生效的话,应该使用Shiro的原生会话管理。“原生”意味着使用Shiro自己的企业会话管理,这样可以完全地支持SubjectHttpServletRequest会话,完全绕过Servlet容器。请放心,Shiro直接实现了Servlet规范的相关部分,因此任何现有的Web/HTTP相关代码都可以按预期工作,对于它们来说,Shiro是透明的

DefaultWebSessionManager

在web应用中开启Shiro的原生会话管理,只需要为SecurityManager配置一个DefaultWebSessionManager实例即可,如下

[main]
...
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
# 如果需要的话,在这里配置属性

# 使用配置好的原生会话管理器
securityManager.sessionManager = $sessionManager

可以为DefaultWebSessionManager实例配置所有原生会话的配置选项,如超时时间和集群配置等

原生会话的超时时间

在配置了DefaultWebSessionManager实例后,可以像前面介绍会话管理那样设置会话超时时间

会话Cookie

DefaultWebSessionManager有两个Web相关的配置属性:

  • sessionIdCookieEnabled——一个布尔属性
  • sessionIdCookie,一个Cookie实例

sessionIdCookie属性是一个Cookie实例,它作为一个模板,这个模板会被用来在运行时使用适当的会话ID值设置实际的HTTP ‘Cookie '头。

会话Cookie配置

DefaultWebSessionManagersessionIdCookie的默认是一个SimpleCookie实例,它是一个JavaBean的形式来设置相关属性值

[main]
...
securityManager.sessionManager.sessionIdCookie.domain = foo.com

查看SimpleCookie的Java文档查看更多信息

根据Servlet规范,cookie的默认名是JSESSIONID,Shiro还支持HttpOnly和SameSite标识,为了安全性,sessionIdCookie默认将会设置HttpOnlytrue,设置SameSiteLAX

关闭会话的Cookie

如果不想使用会话Cookie,可以通过sessionIdCookieEnabled属性值,将其设置为false

[main]
...
securityManager.sessionManager.sessionidCookieEnabled = false

“记住我”功能

如果AuthenticationToken实现了org.apache.shiro.authc.RememberMeAuthenticationToken接口,那Shiro将会根据接口的方法来判断是否提供”记住我“服务

boolean isRememberMe();

如果这个方法返回true,那Shiro将会跨会话的记住终端用户的标识

UsernamePasswordToken 和 记住我

最常用的UsernamePasswordToken已经实现了RememberMeAuthenticationToken接口,支持”记住我“服务

编程式配置

可以通过将登陆时使用的RememberMeAuthenticationToken或其子类的rememberMe属性值设置为true来开启”记住我“服务,如下

UsernamePasswordToken token = new UsernamePasswordToken(username, password);

token.setRememberMe(true);

SecurityUtils.getSubject().login(token);
...

基于表单的登录

在Web应用程序中,authc过滤器默认是一个FormAuthenticationFilter,它能够读取请求参数中的rememberMe参数,默认情况下参数名就是rememberMe

[main]
authc.loginUrl = /login.jsp

[urls]
# 登陆页面
login.jsp = authc
# 这样设置真的不会陷入死循环吗?

在你的Web表单中,添加一个名为rememberMecheckbox

Username:
Password: ... Remember Me? ...

默认情况下,FormAuthenticationFilter将会寻找名为usernamepasswordrememberMe的请求参数,可以配置FormAuthenticationFilter来自定义参数名字

[main]
...
authc.loginUrl=/whatever.jsp
authc.usernameParam=somethingOtherThanUsername
authc.passwordParam=somethingOtherThanPassword
authc.rememberMeParam=somethingOtherThanRememberMe
...

Cookie配置

还可以设置rememberMecookie如何生效

[main]
...
securityManager.rememberMeManager.cookie.name = foo
securityManager.rememberMeManager.cookie.maxAge = blah
...

详情可以看CookieRememberMemanagerSimpleCookieJavaDoc

自定义RememberMeManager

如果默认的RememberMeManager实现不能满足你的需求,也可以自定义

[main]
...
rememberMeManager = com.my.impl.RememberMeManager
securityManager.rememberMeManager = $rememberMeManager
...

JSP/GSP标签库

Apache Shiro提供了一个Subject可以感知的JSP/GSP标记库,允许根据当前Subject状态控制JSP,JSTL或GSP页面输出。 对于基于当前用户查看网页的特定用户的身份和授权状态,这对于个性化视图非常有用

标签库配置

JSP/GSP的标签库描述文件(TLD)是在shiro-web.jar中的META-INF/shiro.tld的,在JSP页面中添加下面这一行来使用标签

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

我们将会使用shiro前缀最为Shiro标签库的命名空间,当然可以使用自己喜欢的前缀

下面来介绍一些标签

guest

guest包裹的内容,只会展示给那些未登录的用户


    这里是未登录的游客才能看到的东西

guest标签的对立面即为user标签

user

guest相反,user标签包裹的内容只会展示给那些登录的用户,其中包括在之前的会话中被记住的用户,注意这个标签和authenticated标签是不一样的,后者更为严格


    这里是展示给登录的用户的

该标签和guest标签相反

authenticated

只有当前Subject是已认证的,才会展示该标签包裹的内容。注意它和user标签的区别,具体参考之前描述的已认证和已记住两个状态


    这里是已认证的用户才能看到的

该标签和notAuthenticated是相反的

notAuthenticated

该标签包裹的内容只会展示给那些未认证的Subject


    这里是未认证的用户才能看到的

该标签和authenticated相反

principal

principal 标签将会输出Subjectprincipal信息(即用户的身份信息)或者其属性。如果没有指定输出哪个标签属性,该标签将会输出principaltoString()方法,如


和下面的输出结果是一样的

<%= SecurityUtils.getSubject().getPrincipal().toString() %>

根据类型选择principal

principal标签默认是输出subject.getPrincipal()值,如果想要输出的不是primary principal,而是Subject的其他principal,可以通过principal的类型来选择输出


上面的标签和下面的输出是一样的

<%= SecurityUtils.getSubject().getPrincipals().oneByType(Integer.class).toString() %>

Principal的属性

如果principal是一个复杂类型的数据,而不是一个简单的字符串,我们还可以通过property属性来指定输出principal的哪个属性值


下面是它的等效结果

<%= SecurityUtils.getSubject().getPrincipal().getFirstName().toString() %>

还可以结合Principal的类型进行输出


它和下面的输出是一样的

<%= SecurityUtils.getSubject().getPrincipals().oneByType(com.foo.User.class).getFirstName().toString() %>

hasRole

该标签包裹的内容只会展示给具有特定角色的用户


    只有拥有administrator角色的用户才能看到

该标签和lacksRole标签相反

lacksRole

该标签包裹的内容只会展示给不具有特定角色的用户


    只有没有administrator角色的用户才能看到

该标签与hasRole标签相反

hasAnyRole

该标签包裹的内容,只有那些具有指定角色集合的其中一个角色才能看到


    需要具有developer,project manager,administrator至少一个角色的用户的才能看到

该标签没有逻辑上相反的标签

hasPermission

参考hasRole


    内容

lacksPermission

参考lacksRole


    内容

你可能感兴趣的:(Apache Shiro 提供的Web支持--官网)