安全框架Shiro浅解

一.什么是shiro

Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能: 

  • 认证 - 用户身份识别,常被称为用户“登录”;

  • 授权 - 访问控制;

  • 密码加密 - 保护或隐藏数据防止被偷窥;

  • 会话管理 - 每用户相关的时间敏感的状态。

对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro要简单的多。 

具体功能如下:

(1)身份认证/登录,验证用户是不是拥有相应的身份; 

(2)授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限; 

(3)会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的; 

(4)加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储; 

(5)Web支持,可以非常容易的集成到Web环境; 

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率; 

(6)shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去; 

(7)提供测试支持; 

(8)允许一个用户假装为另一个用户(如果他们允许)的身份进行访问; 

(9)记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。


二.Shiro的架构介绍

三个核心组件:Subject, SecurityManager 和 Realms. 如下图:

Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。

Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。

  SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

  从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。

  Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

Shiro完整架构图:

除前文所讲Subject、SecurityManager 、Realm三个核心组件外,Shiro主要组件还包括: 

Authenticator :认证就是核实用户身份的过程。这个过程的常见例子是大家都熟悉的“用户/密码”组合。多数用户在登录软件系统时,通常提供自己的用户名(当事人)和支持他们的密码(证书)。如果存储在系统中的密码(或密码表示)与用户提供的匹配,他们就被认为通过认证。 

Authorizer :授权实质上就是访问控制 - 控制用户能够访问应用中的哪些内容,比如资源、Web页面等等。 

SessionManager :在安全框架领域,Apache Shiro提供了一些独特的东西:可在任何应用或架构层一致地使用Session API。即,Shiro为任何应用提供了一个会话编程范式 - 从小型后台独立应用到大型集群Web应用。这意味着,那些希望使用会话的应用开发者,不必被迫使用Servlet或EJB容器了。或者,如果正在使用这些容器,开发者现在也可以选择使用在任何层统一一致的会话API,取代Servlet或EJB机制。 

CacheManager :对Shiro的其他组件提供缓存支持。

SessionDAO:SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。 

Cryptography:Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。


三.Shiro实例详解

springmvc+spring+shiro

1.依赖的包

<!-- Spring 整合Shiro需要的依赖 -->  

<dependency>      

<groupId>org.apache.shiro</groupId>      

<artifactId>shiro-core</artifactId>      

<version>1.2.1</version>   

</dependency>   

<dependency>      

<groupId>org.apache.shiro</groupId>      

<artifactId>shiro-web</artifactId>      

<version>1.2.1</version>   </dependency>

<dependency>

  <groupId>org.apache.shiro</groupId>

  <artifactId>shiro-cas</artifactId>

  <version>1.2.1</version>

</dependency>   

<dependency>      

<groupId>org.apache.shiro</groupId>      

<artifactId>shiro-ehcache</artifactId>      

<version>1.2.1</version>   

</dependency>   

<dependency>      

<groupId>org.apache.shiro</groupId>      

<artifactId>shiro-spring</artifactId>      

<version>1.2.1</version>   

</dependency>  

2.自定义realm

继承AuthorizingRealm 重写  AuthorizationInfo(授权) 和  AuthenticationInfo(认证)

3.shiro配置文件

<?xml version="1.0" encoding="UTF-8"?>  <beans xmlns="http://www.springframework.org/schema/beans"      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xsi:schemaLocation="http://www.springframework.org/schema/beans                          http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"      default-lazy-init="true">      <description>Shiro Configuration</description>      <!-- 加载外部指定路径配置文件,并覆盖默认-->

  <context:property-placeholder location="file:${catalina.home}/webapps/config-web/*.properties" order="1" ignore-unresolvable="true" />

<!-- 加载配置属性文件 -->

<context:property-placeholder ignore-unresolvable="true" location="classpath*:application.properties" order="2"/>

  <!-- Shiro Filter 安全认证过滤器 -->    

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">          <property name="securityManager" ref="securityManager" />          

<property name="loginUrl" value="/login.jhtml" />          

<property name="successUrl" value="/loginsuccess.jhtml" />          

<property name="unauthorizedUrl" value="/error.jhtml" />          

<property name="filterChainDefinitions">              

<value>                  

/index.jhtml = authc                 

 /login.jhtml = anon                

/checkLogin.json = anon                  

/loginsuccess.jhtml = anon                 

 /logout.json = anon                 

 /** = authc              

</value>          

</property>      

</bean>  

<!-- 定义Shiro安全管理配置 -->

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

<property name="realm" ref="systemAuthorizingRealm" />

<property name="sessionManager" ref="sessionManager" />

<property name="cacheManager" ref="shiroCacheManager" />

</bean>

<!-- 自定义会话管理配置 -->

<bean id="sessionManager" class="com.cci.community.service.security.shiro.session.SessionManager">

<property name="sessionDAO" ref="sessionDAO"/>

<!-- 会话超时时间,单位:毫秒  -->

<property name="globalSessionTimeout" value="${session.sessionTimeout}"/>

<!-- 定时清理失效会话, 清理用户直接关闭浏览器造成的孤立会话   -->

<property name="sessionValidationInterval" value="${session.sessionTimeoutClean}"/>

<!-- <property name="sessionValidationSchedulerEnabled" value="false"/> -->

<property name="sessionValidationSchedulerEnabled" value="true"/>

<property name="sessionIdCookie" ref="sessionIdCookie"/>

<property name="sessionIdCookieEnabled" value="true"/>

</bean>

  <!-- 缓存配置 -->

<bean id="cacheManager"           class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">

<property name="configLocation" value="classpath:${ehcache.configFile}" />

</bean>

<!-- 定义授权缓存管理器 -->

<bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">

<property name="cacheManager" ref="cacheManager"/>

</bean>    

 <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />      

<!-- AOP式方法级权限检查 -->      

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"          depends-on="lifecycleBeanPostProcessor">          

<property name="proxyTargetClass" value="true" />      </bean>      <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">          <property name="securityManager" ref="securityManager" />      </bean>  

</beans>  

URL过滤器配置说明: 

Shiro可以通过配置文件实现基于URL的授权验证。FilterChain定义格式: 

URL_Ant_Path_Expression = Path_Specific_Filter_Chain 

每个URL配置,表示匹配该URL的应用程序请求将由对应的过滤器进行验证。 

例如: 

[urls] 

/index.html = anon 

/user/create = anon 

/user/** = authc 

/admin/** = authc, roles[administrator] 

/rest/** = authc, rest 

/remoting/rpc/** = authc, perms["remote:invoke"] 

URL表达式说明 

1、URL目录是基于HttpServletRequest.getContextPath()此目录设置 

2、URL可使用通配符,**代表任意子目录 

3、Shiro验证URL时,URL匹配成功便不再继续匹配查找。所以要注意配置文件中的URL顺序,尤其在使用通配符时。 

Filter Chain定义说明 

1、一个URL可以配置多个Filter,使用逗号分隔 

2、当设置多个过滤器时,全部验证通过,才视为通过 

3、部分过滤器可指定参数,如perms,roles 

Shiro内置的FilterChain 

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

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.authc.UserFilter

4.登陆代码

shiro中AuthenticatingFilter过滤器会根据配置的loginUrl去拦截生成token,然后登陆

当然我们也可以自己通过controller登陆

退出登陆

Subject currentUser = SecurityUtils.getSubject();       currentUser.logout();


四.Shiro认证

身份认证流程

流程如下:

1.首先调用 Subject.login(token) 进行登录,其会自动委托给 Security Manager,调用之前必须通过 SecurityUtils.setSecurityManager() 设置;

2.SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;

3.Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;

4.Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;

5.Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。


五.Shiro授权

Shiro 支持三种方式的授权:

编程式:通过写 if/else 授权代码块完成:

Subject subject = SecurityUtils.getSubject(); if(subject.hasRole(“admin”)) {    //有权限 } else {    //无权限 }&nbsp;

注解式:通过在执行的 Java 方法上放置相应的注解完成:

@RequiresRoles("admin") public void hello() {    //有权限 }&nbsp;

没有权限将抛出相应的异常;

JSP/GSP 标签:在 JSP/GSP 页面通过相应的标签完成:

导入标签库

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

<shiro:hasRole name="admin"> <!— 有权限 —></shiro:hasRole>&nbsp;

授权流程

流程如下:

1.首先调用 Subject.isPermitted*/hasRole*接口,其会委托给 SecurityManager,而 SecurityManager 接着会委托给 Authorizer;

2.Authorizer 是真正的授权者,如果我们调用如 isPermitted(“user:view”),其首先会通过 PermissionResolver 把字符串转换成相应的 Permission 实例;

3.在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限;

4.Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个 Realm,会委托给 ModularRealmAuthorizer 进行循环判断,如果匹配如 isPermitted*/hasRole* 会返回 true,否则返回 false 表示授权失败。

ModularRealmAuthorizer 进行多 Realm 匹配流程:

  • 首先检查相应的 Realm 是否实现了实现了 Authorizer;

  • 如果实现了 Authorizer,那么接着调用其相应的 isPermitted*/hasRole* 接口进行匹配;

  • 如果有一个 Realm 匹配那么将返回 true,否则返回 false。

如果 Realm 进行授权的话,应该继承 AuthorizingRealm,其流程是:

  • 如果调用 hasRole*,则直接获取 AuthorizationInfo.getRoles() 与传入的角色比较即可;首先如果调用如 isPermitted(“user:view”),首先通过 PermissionResolver 将权限字符串转换成相应的 Permission 实例,默认使用 WildcardPermissionResolver,即转换为通配符的 WildcardPermission;

  • 通过 AuthorizationInfo.getObjectPermissions() 得到 Permission 实例集合;通过 AuthorizationInfo.getStringPermissions() 得到字符串集合并通过 PermissionResolver 解析为 Permission 实例;然后获取用户的角色,并通过 RolePermissionResolver 解析角色对应的权限集合(默认没有实现,可以自己提供);

  • 接着调用 Permission.implies(Permission p) 逐个与传入的权限比较,如果有匹配的则返回 true,否则 false。


六.Shiro过滤器机制

1、NameableFilter

NameableFilter 给 Filter 起个名字,如果没有设置默认就是 FilterName;还记得之前的如 authc 吗?当我们组装拦截器链时会根据这个名字找到相应的拦截器实例;

2、OncePerRequestFilter

OncePerRequestFilter 用于防止多次执行 Filter 的;也就是说一次请求只会走一次拦截器链;另外提供 enabled 属性,表示是否开启该拦截器实例,默认 enabled=true 表示开启,如果不想让某个拦截器工作,可以设置为 false 即可。

3、ShiroFilter

ShiroFilter 是整个 Shiro 的入口点,用于拦截需要安全控制的请求进行处理,这个之前已经用过了。

4、AdviceFilter

AdviceFilter 提供了 AOP 风格的支持,类似于 SpringMVC 中的 Interceptor:

boolean preHandle(ServletRequest request, ServletResponse response) throws Exceptionvoid postHandle(ServletRequest request, ServletResponse response) throws Exceptionvoid afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception;&nbsp;

  • preHandler:类似于 AOP 中的前置增强;在拦截器链执行之前执行;如果返回 true 则继续拦截器链;否则中断后续的拦截器链的执行直接返回;进行预处理(如基于表单的身份验证、授权)

  • postHandle:类似于 AOP 中的后置返回增强;在拦截器链执行完成后执行;进行后处理(如记录执行时间之类的);

  • afterCompletion:类似于 AOP 中的后置最终增强;即不管有没有异常都会执行;可以进行清理资源(如接触 Subject 与线程的绑定之类的);

5、PathMatchingFilter

PathMatchingFilter 提供了基于 Ant 风格的请求路径匹配功能及拦截器参数解析的功能,如“roles[admin,user]”自动根据“,”分割解析到一个路径参数配置并绑定到相应的路径:

boolean pathsMatch(String path, ServletRequest request)boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception&nbsp;

pathsMatch:该方法用于 path 与请求路径进行匹配的方法;如果匹配返回 true;

onPreHandle:在 preHandle 中,当 pathsMatch 匹配一个路径后,会调用 opPreHandler 方法并将路径绑定参数配置传给 mappedValue;然后可以在这个方法中进行一些验证(如角色授权),如果验证失败可以返回 false 中断流程;默认返回 true;也就是说子类可以只实现 onPreHandle 即可,无须实现 preHandle。如果没有 path 与请求路径匹配,默认是通过的(即 preHandle 返回 true)。

6、AccessControlFilter

AccessControlFilter 提供了访问控制的基础功能;比如是否允许访问/当访问拒绝时如何处理等:

abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;&nbsp;

isAccessAllowed:表示是否允许访问;mappedValue 就是[urls]配置中拦截器参数部分,如果允许访问返回 true,否则 false;

onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回 true 表示需要继续处理;如果返回 false 表示该拦截器实例已经处理了,将直接返回即可。

onPreHandle 会自动调用这两个方法决定是否继续处理:

boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {    return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue); }&nbsp;

另外 AccessControlFilter 还提供了如下方法用于处理如登录成功后/重定向到上一个请求:

void setLoginUrl(String loginUrl) //身份验证时使用,默认/login.jspString getLoginUrl() Subject getSubject(ServletRequest request, ServletResponse response) //获取Subject 实例boolean isLoginRequest(ServletRequest request, ServletResponse response)//当前请求是否是登录请求void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException //将当前请求保存起来并重定向到登录页面void saveRequest(ServletRequest request) //将请求保存起来,如登录成功后再重定向回该请求void redirectToLogin(ServletRequest request, ServletResponse response) //重定向到登录页面&nbsp;

比如基于表单的身份验证就需要使用这些功能。

到此基本的拦截器就完事了,如果我们想进行访问访问的控制就可以继承 AccessControlFilter;如果我们要添加一些通用数据我们可以直接继承 PathMatchingFilter。

参考博客:https://blog.csdn.net/xiaoliuliu2050/article/details/54911480

https://blog.csdn.net/u013142781/article/details/50629708/


猜你喜欢

转载自blog.csdn.net/qq_39533847/article/details/80750164