Shiro入门学习五

与Web集成

前面的学习都是在JavaSE下,Shiro提供了与Web集成的支持,其通过一ShiroFilter入口来拦截需要安全控制的URL,然后进行相应的控制,ShiroFilter 类似于如 Strut2/SpringMVC 这种 web 框架的前端控制器,其是安全控制的入口点,其负责读取配置(如 ini 配置文件),然后判断 URL 是否需要登录 / 权限等工作。

servlet环境配置

一. shiro1.1及以前版本配置方式

<filter>
    <filter-name>iniShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
    <init-param>
        <param-name>configPath</param-name>
        <param-value>classpath:shiro.ini</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>iniShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
  1. 使用IniShiroFilter作为Shiro安全控制的入口,通过url-pattern指定需要安全的URL;
  2. 通过 configPath 指定 ini 配置文件位置,默认是先从 /WEB-INF/shiro.ini 加载,如果没有就默认加载 classpath:shiro.ini;
  3. 也可以通过如下方式直接内嵌 ini 配置文件内容到 web.xml。
<init-param>
    <param-name>config</param-name>
    <param-value>
        ini配置文件贴在这
    </param-value>
</init-param>

二. shiro1.2及以后版本配置方式

shiro1.2开始引入了Environment/WebEnvironment概念,即由它们的实现提供相应的 SecurityManager 及其相应的依赖。ShiroFilter 会自动找到 Environment 然后获取相应的依赖。与spring配置很像

<!--通过EnvironmentLoaderListener来创建相应的WebEnvironment并自动绑定到 ServletContext,
  默认使用 IniWebEnvironment 实现-->
  <listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
  </listener>
  <!--通过配置修改默认实现及其加载的配置文件位置-->
  <context-param>
    <param-name>shiroEnviromentClass</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.ini</param-value>
  </context-param>
  <!--shiroFilter--> 
  <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>

web INI 配置

ini 配置部分和之前的相比将多出对 urls 部分的配置。

[main]
#默认是/login.jsp
authc.loginUrl=/login
roles.unauthorizedUrl=/unauthorized
perms.unauthorizedUrl=/unauthorized
logout.redirectUrl=/login
[users]
zhang=123,admin
wang=123
[roles]
admin=user:*,menu:*
[urls]
/login=anon
/unauthorized=anon
/static/**=anon
/authenticated=authc
/role=authc,roles[admin]
/permission=authc,perms["user:create"]

authc.loginUrl:登录URL
roles.unauthorizedUrl:角色未授权重定向URL
perms.unauthorizedUrl:权限未授权重定向URL
logout.redirectUrl:登出时重定向URL
anon:org.apache.shiro.web.filter.authc.AnonymousFilter拦截器表示可以匿名访问
authc:org.apache.shiro.web.filter.authc.FormAuthenticationFilter拦截器表示需要身份认证通过后才能访问
roles[admin]:org.apache.shiro.web.filter.authz.RolesAuthorizationFilter拦截器表示需要有admin角色授权才能访问
perms[“user:create”]:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter拦截器表示需要user:create权限才能访问

url 模式匹配顺序

url 模式匹配顺序是按照在配置中的声明顺序匹配,即从头开始使用第一个匹配的 url 模式对应的拦截器链。

在实际项目中比如支付时如果没有登录将跳转到登录页面,登录成功后再跳回到支付页面;对于这种功能大家可以在登录时把当前请求保存下来,然后登录成功后再重定向到该请求即可。Shiro内置了登录(身份验证)的实现:基于表单的和基于Basic的验证,其通过拦截器实现。

基于Basic的拦截器身份验证

[main]
authcBasic.applicationName=please login
[users]
zhang=123,admin
[urls]
/list=authcBasic,roles[admin]

authcBasic 是 org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter 类型的实例,其用于实现基于 Basic 的身份验证;applicationName 用于弹出的登录框提示信息。

基于表单的拦截器身份验证

[main]
authc.loginUrl=/formfilterlogin
authc.usernameParam=username
authc.passwordParam=password
authc.successUrl=/
authc.failureKeyAttribute=shiroLoginFailure
[urls]
/list=authc,roles[admin]

authc 是 org.apache.shiro.web.filter.authc.FormAuthenticationFilter 类型的实例,其用于实现基于表单的身份验证。usernameParam指定登录表单提交的用户名参数名;passwordParam 指定登录表单提交的密码参数名;successUrl 指定登录成功后重定向的默认地址(默认是 “/”)(如果有上一个地址会自动重定向到该地址);failureKeyAttribute 指定登录失败时的 request 属性 key(默认 shiroLoginFailure);这样可以在登录表单得到该错误 key 显示相应的错误消息;

Shiro jsp标签

Shiro 提供了 JSTL 标签用于在 JSP/GSP 页面进行权限控制,如根据登录用户显示相应的页面按钮。

导入标签库

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

标签库定义在 shiro jar 包下的 META-INF/shiro.tld 中定义。

    <!-- 用户没有登录时显示 -->
    <shiro:guest>
        欢迎游客访问,<a href="/login.jsp">登录</a>
    </shiro:guest>

    <!-- 用户通过身份验证/记住我登录时显示 -->
    <shiro:user>
        欢迎[<shiro:principal/>]登录,<a href="shiro/logout">点击退出</a><br/>   
    </shiro:user>

    <!-- 用户通过身份验证时显示 -->
    <shiro:authenticated>
         用户[<shiro:principal/>]已身份验证通过<br/>
    </shiro:authenticated>&nbsp;

    <!-- 用户通过记住我登录时显示 -->
    <shiro:notAuthenticated>
        未身份验证(包括记住我)
    </shiro:notAuthenticated> 

    <!-- 拥有admin角色时显示 -->
    <shiro:hasRole name="admin">
        用户[<shiro:principal/>]拥有角色admin<br/>
    </shiro:hasRole>

    <!-- 拥有任意一角色时显示 -->
    <shiro:hasAnyRoles name="admin,user">
         用户[<shiro:principal/>]拥有角色admin或user<br/>
    </shiro:hasAnyRoles>

    <!-- 没有guest角色时显示 -->
    <shiro:lacksRole name="guest">
        用户[<shiro:principal/>]没有角色guest<br/>
    </shiro:lacksRole>

    <!-- 拥有user:create权限时显示 -->
    <shiro:hasPermission name="user:create">
        用户[<shiro:principal/>]拥有权限user:create<br/>
    </shiro:hasPermission>

    <!-- 没有org:create权限时显示 -->
    <shiro:lacksPermission name="org:create">
        用户[<shiro:principal/>]没有权限org:create<br/>
    </shiro:lacksPermission>

    <!--默认调用 Subject.getPrincipal() 获取,即 Primary Principal  -->
    <shiro:principal/>
    <!-- 相当于 Subject.getPrincipals().oneByType(String.class) -->
    <shiro:principal type="java.lang.String"/>
    <!-- 相当于 ((User)Subject.getPrincipals()).getUsername() -->
    <shiro:principal property="username"/>

会话管理

Shiro 提供了完整的企业级会话管理功能,不依赖于底层容器(如 web 容器 tomcat),不管 JavaSE 还是 JavaEE 环境都可以使用,提供了会话管理、会话事件监听、会话存储 / 持久化、容器无关的集群、失效 / 过期支持、对 Web 的透明支持、SSO 单点登录的支持等特性。即直接使用 Shiro 的会话管理可以直接替换如 Web 容器的会话管理。

SessionAPI

        //获取主体
        Subject subject = SecurityUtils.getSubject();
        //获取Session
        Session session = subject.getSession();
        //获取当前会话的唯一标识
        session.getId();
        //获取当前Subject的主机地址
        session.getHost();
        //设置/获取当前Session过期时间
        session.setTimeout(100);
        session.getTimeout();
        //获取会话的启动时间及最后访问时间,JavaSE自己定期调用session.touch更新最后访问时间
        session.getStartTimestamp();
        session.getLastAccessTime();
        //更新会话最后访问时间及销毁会话
        session.touch();
        session.stop();
        //设置/获取/删除会话属性及获取属性Key的集合
        session.setAttribute("key", "value");
        session.getAttribute("");
        session.removeAttribute("key");
        session.getAttributeKeys();

会话管理器

Session接口

Shiro提供了三种默认实现:
DefaultSessionManager:DefaultSecurityManager 使用的默认实现,用于 JavaSE 环境;
ServletContainerSessionManager:DefaultWebSecurityManager 使用的默认实现,用于 Web 环境,其直接使用 Servlet 容器的会话;
DefaultWebSessionManager:用于 Web 环境的实现,可以替代 ServletContainerSessionManager,自己维护着会话,直接废弃了 Servlet 容器的会话管理。

INI配置:

[main]
#JavaSE环境下
sessionManager=org.apache.shiro.session.mgt.DefaultSessionManager
securityManager.sessionManager=$sessionManager

#JavaWeb环境下
sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager
sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie
sessionIdCookie.name=sid
\#sessionIdCookie.domain=
\#sessionIdCookie.path=
sessionIdCookie.maxAge=1800
sessionIdCookie.httpOnly=true
sessionManager.sessionIdCookie=$sessionIdCookie
sessionManager.sessionIdCookieEnabled=true
sessionManager.globalSessionTimeout=1800000
securityManager.sessionManager=$sessionManager
securityManager.sessionManager=$sessionManager
  • sessionIdCookie 是 sessionManager 创建会话 Cookie 的模板:
  • sessionIdCookie.name:设置 Cookie 名字,默认为 JSESSIONID;
  • sessionIdCookie.domain:设置 Cookie 的域名,默认空,即当前访问的域名;
  • sessionIdCookie.path:设置 Cookie 的路径,默认空,即存储在域名根下;
  • sessionIdCookie.maxAge:设置 Cookie 的过期时间,秒为单位,默认 - 1 表示关闭浏览器时过期 Cookie;
  • sessionIdCookie.httpOnly:如果设置为 true,则客户端不会暴露给客户端脚本代码,使用 HttpOnly cookie 有助于减少某些类型的跨站点脚本攻击;此特性需要实现了 Servlet 2.5 MR6 及以上版本的规范的 Servlet容器支持;
  • sessionManager.sessionIdCookieEnabled:是否启用 / 禁用 Session Id Cookie,默认是启用的;如果禁用后将不会设置 Session Id Cookie,即默认使用了 Servlet 容器的JSESSIONID,且通过 URL 重写(URL 中的 “;JSESSIONID=id” 部分)保存 Session Id;
  • sessionManager.globalSessionTimeout:设置会话的全局过期时间。

对于使用ServletContainerSessionManager进行会话管理,Session的超时依赖于底层Servlet容器的超时时间,可以在web.xml中配置

<session-config>
    <!--30分钟超时-->
    <session-timeout>30</session-timeout>
</session-config>

会话监听器

会话监听器用于监听会话的创建、过期及停止事件,有二种方式:
实现SessionListener接口

package com.shiro.listener;
public class MySessionListener1 implements SessionListener {
    @Override
    public void onStart(Session session) {//会话创建时触发
        System.out.println("会话创建:" + session.getId());
    }
    @Override
    public void onExpiration(Session session) {//会话过期时触发
        System.out.println("会话过期:" + session.getId());
    }
    @Override
    public void onStop(Session session) {//退出/会话过期时触发
        System.out.println("会话停止:" + session.getId());
    }  
}

如果只想监听某一个事件,可以继承SessionListenerAdapter,重写其中的某一个方法。

public class MySessionListener2 extends SessionListenerAdapter {
    @Override
    public void onStart(Session session) {
        System.out.println("会话创建:" + session.getId());
    }
}

在INI配置文件中配置

sessionListener1=com.shiro.listener.MySessionListener1
sessionListener2=com.shiro.listener.MySessionListener2
SessionManager.sessionListeners=$sessionListener1,$sessionListener2

会话存储/持久化

Shiro 提供 SessionDAO 用于会话的 CRUD,即 DAO(Data Access Object)模式实现:

SessionDAO

AbstractSessionDAO 提供了 SessionDAO 的基础实现,如生成会话 ID 等;CachingSessionDAO 提供了对开发者透明的会话缓存的功能,只需要设置相应的 CacheManager 即可;MemorySessionDAO 直接在内存中进行会话维护;而 EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用 MapCache 实现,内部使用 ConcurrentHashMap 保存缓存的会话。

使用EhCache缓存进行会话存储

sessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
#设置Session缓存名字,默认就是shiro-activeSessionCache
sessionDAO.activeSessionsCacheName=shiro-activeSessionCache
#设置生成会话ID,默认是JavaUuidSessionIdGenerator,使用java.util.UUID生成
sessionIdGenerator=org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator
sessionDAO.sessionIdGenerator=$sessionIdGenerator
sessionManager.sessionDAO=$sessionDAO
#缓存管理器
cacheManager=org.apache.shiro.cache.ehcache.EhCacheManager
#设置ehcache的配置文件
cacheManager.cacheManagerConfigFile=classpath:ehcache.xml
#SecurityManager的cacheManager会自动设置到实现了CacheManagerAware接口的相应对象
securityManager.cacheManager=$cacheManager

ehcache.xml

<cache name="shiro-activeSessionCache"
       maxEntriesLocalHeap="10000"
       overflowToDisk="false"
       eternal="false"
       diskPersistent="false"
       timeToLiveSeconds="0"
       timeToIdleSeconds="0"
       statistics="true"/>

org.apache.shiro.session.mgt.eis.AbstractSessionDAO中实现了SessionID的实现。

//生成SessionID的方法
protected Serializable generateSessionId(Session session) {
        if (this.sessionIdGenerator == null) {
            String msg = "sessionIdGenerator attribute has not been configured.";
            throw new IllegalStateException(msg);
        }
        return this.sessionIdGenerator.generateId(session);
    }

//将生成的sessionID放入Session中
protected void assignSessionId(Session session, Serializable sessionId) {
        ((SimpleSession) session).setId(sessionId);
}

会话验证

Shiro 提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话;出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的;但是如在 web 环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro 提供了会话验证调度器 SessionValidationScheduler 来做这件事情。

sessionValidationScheduler=org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler
sessionValidationScheduler.interval = 3600000
sessionValidationScheduler.sessionManager=$sessionManager
sessionManager.globalSessionTimeout=1800000
sessionManager.sessionValidationSchedulerEnabled=true
sessionManager.sessionValidationScheduler=$sessionValidationScheduler
  • sessionValidationScheduler:会话验证调度器,sessionManager 默认就是使用
    ExecutorServiceSessionValidationScheduler,其使用 JDK 的
    ScheduledExecutorService 进行定期调度并验证会话是否过期;
  • sessionValidationScheduler.interval:设置调度时间间隔,单位毫秒,默认就是 1 小时;
  • sessionValidationScheduler.sessionManager:设置会话验证调度器进行会话验证时的会话管理器;
  • sessionManager.globalSessionTimeout:设置全局会话超时时间,默认 30 分钟,即如果 30分钟内没有访问会话将过期;
  • sessionManager.sessionValidationSchedulerEnabled:是否开启会话验证器,默认是开启的;
  • sessionManager.sessionValidationScheduler:设置会话验证调度器,默认就是使用
    ExecutorServiceSessionValidationScheduler。

缓存机制

Shiro 提供了类似于 Spring 的 Cache 抽象,即 Shiro 本身不实现 Cache,但是对 Cache 进行了又抽象,方便更换不同的底层 Cache 实现。Shiro提供了Cache和CacheManager接口,还提供了CacheManagerAware用于注入CacheManager,Shiro内部响应的组件(DefaultSecurityManager)会自动检测相应的对象(如Realm、SessionManager、SessionDAO)是否实现了CacheManagerAware并自动注入相应的CacheManager。
Realm缓存
Shiro 提供了 CachingRealm,其实现了 CacheManagerAware 接口,提供了缓存的一些基础实现;另外 AuthenticatingRealm 及 AuthorizingRealm 分别提供了对 AuthenticationInfo 和 AuthorizationInfo 信息的缓存。

INI配置

userRealm=com.shiro.realm.UserRealm
#启用缓存,默认false
userRealm.cachingEnabled=true
#启用身份验证缓存,缓存名为authenticationCache
userRealm.authenticationCachingEnabled=true
userRealm.authenticationCacheName=authenticationCache
#启用授权缓存,缓存名为authorizationCache
userRealm.authorizationCachingEnabled=true
userRealm.authorizationCacheName=authorizationCache
securityManager.realms=$userRealm
cacheManager=org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager.cacheManagerConfigFile=classpath:shiro-ehcache.xml
securityManager.cacheManager=$cacheManager

猜你喜欢

转载自blog.csdn.net/yutao_struggle/article/details/78429593