Spring+Shiro案例分析

  1. 云栖社区>
  2. 博客>
  3. 正文

Spring+Shiro案例分析

程序界小强 2015-08-05 21:58:20 浏览174 评论0

摘要: 在 Web 项目中应用 Apache Shiro Apache Shiro 是功能强大并且容易集成的开源权限框架,它能够完成认证、授权、加密、会话管理等功能。

在 Web 项目中应用 Apache Shiro

Apache Shiro 是功能强大并且容易集成的开源权限框架,它能够完成认证、授权、加密、会话管理等功能。认证和授权为权限控制的核心,简单来说,“认证”就是证明你是谁? Web 应用程序一般做法通过表单提交用户名及密码达到认证目的。“授权”即是否允许已认证用户访问受保护资源。关于 Shiro 的一系列特征及优点,很多文章已有列举,这里不再逐一赘述,本文重点介绍 Shiro 在 Web Application 中如何实现验证码认证以及如何实现单点登录。



用户权限模型

在揭开 Shiro 面纱之前,我们需要认知用户权限模型。本文所提到用户权限模型,指的是用来表达用户信息及用户权限信息的数据模型。即能证明“你是谁?”、“你能访问多少受保护资源?”。为实现一个较为灵活的用户权限数据模型,通常把用户信息单独用一个实体表示,用户权限信息用两个实体表示。

  1. 用户信息用 LoginAccount 表示,最简单的用户信息可能只包含用户名 loginName 及密码 password 两个属性。实际应用中可能会包含用户是否被禁用,用户信息是否过期等信息。
  2. 用户权限信息用 Role 与 Permission 表示,Role 与 Permission 之间构成多对多关系。Permission 可以理解为对一个资源的操作,Role 可以简单理解为 Permission 的集合。
  3. 用户信息与 Role 之间构成多对多关系。表示同一个用户可以拥有多个 Role,一个 Role 可以被多个用户所拥有。
图 1. 用户权限模型
图 1. 用户权限模型


认证与授权

Shiro 认证与授权处理过程

  • 被 Shiro 保护的资源,才会经过认证与授权过程。使用 Shiro 对 URL 进行保护可以参见“与 Spring 集成”章节。
  • 用户访问受 Shiro 保护的 URL;例如 http://host/security/action.do。
  • Shiro 首先检查用户是否已经通过认证,如果未通过认证检查,则跳转到登录页面,否则进行授权检查。认证过程需要通过 Realm 来获取用户及密码信息,通常情况我们实现 JDBC Realm,此时用户认证所需要的信息从数据库获取。如果使用了缓存,除第一次外用户信息从缓存获取。
  • 认证通过后接受 Shiro 授权检查,授权检查同样需要通过 Realm 获取用户权限信息。Shiro 需要的用户权限信息包括 Role 或 Permission,可以是其中任何一种或同时两者,具体取决于受保护资源的配置。如果用户权限信息未包含 Shiro 需要的 Role 或 Permission,授权不通过。只有授权通过,才可以访问受保护 URL 对应的资源,否则跳转到“未经授权页面”。

Shiro Realm

在 Shiro 认证与授权处理过程中,提及到 Realm。Realm 可以理解为读取用户信息、角色及权限的 DAO。由于大多 Web 应用程序使用了关系数据库,因此实现 JDBC Realm 是常用的做法,后面会提到 CAS Realm,另一个 Realm 的实现。

清单 1. 实现自己的 JDBC Realm
public class MyShiroRealm extends AuthorizingRealm{ 
   
   // 用于获取用户信息及用户权限信息的业务接口
   private BusinessManager businessManager; 
    
   // 获取授权信息
   protected AuthorizationInfo doGetAuthorizationInfo( 
      PrincipalCollection principals) { 
      String username = (String) principals.fromRealm( 
         getName()).iterator().next(); 
      
      if( username != null ){ 
      // 查询用户授权信息
         Collection<String> pers=businessManager.queryPermissions(username); 
         if( pers != null && !pers.isEmpty() ){ 
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 
            for( String each:pers ) 
               info.addStringPermissions( each ); 
            
            return info; 
         } 
      } 
      
      return null; 
   } 
   
   // 获取认证信息
   protected AuthenticationInfo doGetAuthenticationInfo( 
      AuthenticationToken authcToken ) throws AuthenticationException { 
      UsernamePasswordToken token = (UsernamePasswordToken) authcToken; 
      // 通过表单接收的用户名
      String username = token.getUsername(); 
      
      if( username != null && !"".equals(username) ){ 
         LoginAccount account = businessManager.get( username ); 
         
         if( account != null ){ 
            return new SimpleAuthenticationInfo( 
               account.getLoginName(),account.getPassword(),getName() ); 
         } 
      } 
      
      return null; 
   } 
 }

代码说明:

  1. businessManager 表示从数据库获取用户信息及用户权限信息的业务类,实际情况中可能因用户权限模型设计不同或持久化框架选择不同,这里没给出示例代码。
  2. doGetAuthenticationInfo 方法,取用户信息。对照用户权限模型来说,就是取 LoginAccount 实体。最终我们需要为 Shiro 提供 AuthenticationInfo 对象。
  3. doGetAuthorizationInfo 方法,获取用户权限信息。代码给出了获取用户 Permission 的示例,获取用户 Role 的代码类似。为 Shiro 提供的用户权限信息以 AuthorizationInfo 对象形式返回。

为何对 Shiro 情有独钟

或许有人要问,我一直在使用 Spring,应用程序的安全组件早已选择了 Spring Security,为什么还需要 Shiro ?当然,不可否认 Spring Security 也是一款优秀的安全控制组件。本文的初衷不是让您必须选择 Shiro 以及必须放弃 Spring Security,秉承客观的态度,下面对两者略微比较:

  1. 简单性,Shiro 在使用上较 Spring Security 更简单,更容易理解。
  2. 灵活性,Shiro 可运行在 Web、EJB、IoC、Google App Engine 等任何应用环境,却不依赖这些环境。而 Spring Security 只能与 Spring 一起集成使用。
  3. 可插拔,Shiro 干净的 API 和设计模式使它可以方便地与许多的其它框架和应用进行集成。Shiro 可以与诸如 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 这类第三方框架无缝集成。Spring Security 在这方面就显得有些捉衿见肘。

与 Spring 集成

在 Java Web Application 开发中,Spring 得到了广泛使用;与 EJB 相比较,可以说 Spring 是主流。Shiro 自身提供了与 Spring 的良好支持,在应用程序中集成 Spring 十分容易。

有了前面提到的用户权限数据模型,并且实现了自己的 Realm,我们就可以开始集成 Shiro 为应用程序服务了。

Shiro 的安装

Shiro 的安装非常简单,在 Shiro 官网下载 shiro-all-1.2.0.jar、shiro-cas-1.2.0.jar(单点登录需要),及 SLF4J 官网下载 Shiro 依赖的日志组件 slf4j-api-1.6.1.jar。Spring 相关的 JAR 包这里不作列举。这些 JAR 包需要放置到 Web 工程 /WEB-INF/lib/ 目录。至此,剩下的就是配置了。

配置过滤器

首先,配置过滤器让请求资源经过 Shiro 的过滤处理,这与其它过滤器的使用类似。

清单 2. web.xml 配置
 <filter> 
   <filter-name>shiroFilter</filter-name> 
   <filter-class> 
      org.springframework.web.filter.DelegatingFilterProxy 
   </filter-class> 
 </filter> 
 <filter-mapping> 
   <filter-name>shiroFilter</filter-name> 
   <url-pattern>/*</url-pattern> 
 </filter-mapping>

Spring 配置

接下来仅仅配置一系列由 Spring 容器管理的 Bean,集成大功告成。各个 Bean 的功能见代码说明。

清单 3. Spring 配置
 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 
   <property name="securityManager" ref="securityManager"/> 
   <property name="loginUrl" value="/login.do"/> 
   <property name="successUrl" value="/welcome.do"/> 
   <property name="unauthorizedUrl" value="/403.do"/> 
   <property name="filters"> 
      <util:map> 
         <entry key="authc" value-ref="formAuthenticationFilter"/> 
      </util:map> 
   </property> 
   <property name="filterChainDefinitions"> 
      <value> 
         /=anon 
         /login.do*=authc 
         /logout.do*=anon 
         
         # 权限配置示例
         /security/account/view.do=authc,perms[SECURITY_ACCOUNT_VIEW] 
         
         /** = authc 
      </value> 
   </property> 
 </bean> 

 <bean id="securityManager" 
   class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> 
   <property name="realm" ref="myShiroRealm"/> 
 </bean> 

 <bean id="myShiroRealm" class="xxx.packagename.MyShiroRealm"> 
   <!-- businessManager 用来实现用户名密码的查询 --> 
   <property name="businessManager" ref="businessManager"/> 
   <property name="cacheManager" ref="shiroCacheManager"/> 
 </bean> 

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

 <bean id="shiroCacheManager" 
   class="org.apache.shiro.cache.ehcache.EhCacheManager"> 
   <property name="cacheManager" ref="cacheManager"/> 
 </bean> 

 <bean id="formAuthenticationFilter" 
   class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"/>

代码说明:

  1. shiroFilter 中 loginUrl 为登录页面地址,successUrl 为登录成功页面地址(如果首先访问受保护 URL 登录成功,则跳转到实际访问页面),unauthorizedUrl 认证未通过访问的页面(前面提到的“未经授权页面”)。
  2. shiroFilter 中 filters 属性,formAuthenticationFilter 配置为基于表单认证的过滤器。
  3. shiroFilter 中 filterChainDefinitions 属性,anon 表示匿名访问(不需要认证与授权),authc 表示需要认证,perms[SECURITY_ACCOUNT_VIEW] 表示用户需要提供值为“SECURITY_ACCOUNT_VIEW”Permission 信息。由此可见,连接地址配置为 authc 或 perms[XXX] 表示为受保护资源。
  4. securityManager 中 realm 属性,配置为我们自己实现的 Realm。关于 Realm,参见前面“Shiro Realm”章节。
  5. myShiroRealm 为我们自己需要实现的 Realm 类,为了减小数据库压力,添加了缓存机制。
  6. shiroCacheManager 是 Shiro 对缓存框架 EhCache 的配置。

实现验证码认证

验证码是有效防止暴力破解的一种手段,常用做法是在服务端产生一串随机字符串与当前用户会话关联(我们通常说的放入 Session),然后向终端用户展现一张经过“扰乱”的图片,只有当用户输入的内容与服务端产生的内容相同时才允许进行下一步操作。

产生验证码

作为演示,我们选择开源的验证码组件 kaptcha。这样,我们只需要简单配置一个 Servlet,页面通过 IMG 标签就可以展现图形验证码。

清单 4. web.xml 配置
 <!-- captcha servlet--> 
 <servlet> 
   <servlet-name>kaptcha</servlet-name> 
   <servlet-class> 
      com.google.code.kaptcha.servlet.KaptchaServlet 
   </servlet-class> 
 </servlet> 
 <servlet-mapping> 
 <servlet-name>kaptcha</servlet-name> 
 <url-pattern>/images/kaptcha.jpg</url-pattern> 
 </servlet-mapping>

扩展 UsernamePasswordToken

Shiro 表单认证,页面提交的用户名密码等信息,用 UsernamePasswordToken 类来接收,很容易想到,要接收页面验证码的输入,我们需要扩展此类:

清单 5. CaptchaUsernamePasswordToken
 public class CaptchaUsernamePasswordToken extends UsernamePasswordToken{ 

 private String captcha; 

 // 省略 getter 和 setter 方法

 public CaptchaUsernamePasswordToken(String username, char[] password, 
 boolean rememberMe, String host,String captcha) { 
 super(username, password, rememberMe, host); 
 this.captcha = captcha; 
 } 
 }

扩展 FormAuthenticationFilter

接下来我们扩展 FormAuthenticationFilter 类,首先覆盖 createToken 方法,以便获取 CaptchaUsernamePasswordToken 实例;然后增加验证码校验方法 doCaptchaValidate;最后覆盖 Shiro 的认证方法 executeLogin,在原表单认证逻辑处理之前进行验证码校验。

清单 6. CaptchaUsernamePasswordToken
public class CaptchaFormAuthenticationFilter extends FormAuthenticationFilter{ 

   public static final String DEFAULT_CAPTCHA_PARAM = "captcha"; 
   
   private String captchaParam = DEFAULT_CAPTCHA_PARAM; 
   
   public String getCaptchaParam() { 
      return captchaParam; 
   } 
   
   public void setCaptchaParam(String captchaParam) { 
      this.captchaParam = captchaParam; 
   } 
   
   protected String getCaptcha(ServletRequest request) { 
      return WebUtils.getCleanParam(request, getCaptchaParam()); 
   } 
   
   // 创建 Token 
   protected CaptchaUsernamePasswordToken createToken( 
      ServletRequest request, ServletResponse response) { 
   
      String username = getUsername(request); 
      String password = getPassword(request); 
      String captcha = getCaptcha(request); 
      boolean rememberMe = isRememberMe(request); 
      String host = getHost(request); 
                   
      return new CaptchaUsernamePasswordToken( 
         username, password, rememberMe, host,captcha); 
   } 
   
   // 验证码校验
   protected void doCaptchaValidate( HttpServletRequest request 
      ,CaptchaUsernamePasswordToken token ){ 

      String captcha = (String)request.getSession().getAttribute( 
         com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY); 
      
      if( captcha!=null && 
         !captcha.equalsIgnoreCase(token.getCaptcha()) ){ 
         throw new IncorrectCaptchaException ("验证码错误!"); 
      } 
   } 
    
   // 认证
   protected boolean executeLogin(ServletRequest request, 
      ServletResponse response) throws Exception { 
      CaptchaUsernamePasswordToken token = createToken(request, response); 
       
      try { 
         doCaptchaValidate( (HttpServletRequest)request,token ); 
                 
         Subject subject = getSubject(request, response); 
         subject.login(token); 
                     
         return onLoginSuccess(token, subject, request, response); 
      } catch (AuthenticationException e) { 
         return onLoginFailure(token, e, request, response); 
      } 
   } 

 }

代码说明:

  1. 添加 captchaParam 变量,为的是页面表单提交验证码的参数名可以进行灵活配置。
  2. doCaptchaValidate 方法中,验证码校验使用了框架 KAPTCHA 所提供的 API。

添加 IncorrectCaptchaException

前面验证码校验不通过,我们抛出一个异常 IncorrectCaptchaException,此类继承 AuthenticationException,之所以需要扩展一个新的异常类,为的是在页面能更精准显示错误提示信息。

清单 7. IncorrectCaptchaException
public class IncorrectCaptchaException extends AuthenticationException{ 
   
   public IncorrectCaptchaException() { 
      super(); 
   } 
   
   public IncorrectCaptchaException(String message, Throwable cause) { 
      super(message, cause); 
   } 
   
   public IncorrectCaptchaException(String message) { 
      super(message); 
   } 
   
   public IncorrectCaptchaException(Throwable cause) { 
      super(cause); 
   } 
 }

页面展现验证码错误提示信息

清单 8. 页面认证错误信息展示
 Object obj=request.getAttribute( 
   org.apache.shiro.web.filter.authc.FormAuthenticationFilter 
      .DEFAULT_ERROR_KEY_ATTRIBUTE_NAME); 
 AuthenticationException authExp = (AuthenticationException)obj; 
 if( authExp != null ){ 
   String expMsg=""; 
   
   if(authExp instanceof UnknownAccountException || 
      authExp instanceof IncorrectCredentialsException){ 
      expMsg="错误的用户账号或密码!"; 
   }else if( authExp instanceof IncorrectCaptchaException){ 
      expMsg="验证码错误!"; 
   }else{ 
      expMsg="登录异常 :"+authExp.getMessage() ; 
   } 
    
   out.print("<div class=\"error\">"+expMsg+"</div>"); 
 }


实现单点登录

前面章节,我们认识了 Shiro 的认证与授权,并结合 Spring 作了集成实现。现实中,有这样一个场景,我们拥有很多业务系统,按照前面的思路,如果访问每个业务系统,都要进行认证,这样是否有点难让人授受。有没有一种机制,让我们只认证一次,就可以任意访问目标系统呢?

上面的场景,就是我们常提到的单点登录 SSO。Shiro 从 1.2 版本开始对 CAS 进行支持,CAS 就是单点登录的一种实现。

Shiro CAS 认证流程

  • 用户首次访问受保护的资源;例如 http://casclient/security/view.do
  • 由于未通过认证,Shiro 首先把请求地址(http://casclient/security/view.do)缓存起来。
  • 然后跳转到 CAS 服务器进行登录认证,在 CAS 服务端认证完成后需要返回到请求的 CAS 客户端,因此在请求时,必须在参数中添加返回地址 ( 在 Shiro 中名为 CAS Service)。 例如 http://casserver/login?service=http://casclient/shiro-cas
  • 由 CAS 服务器认证通过后,CAS 服务器为返回地址添加 ticket。例如 http://casclient/shiro-cas?ticket=ST-4-BWMEnXfpxfVD2jrkVaLl-cas
  • 接下来,Shiro 会校验 ticket 是否有效。由于 CAS 客户端不提供直接认证,所以 Shiro 会向 CAS 服务端发起 ticket 校验检查,只有服务端返回成功时,Shiro 才认为认证通过。
  • 认证通过,进入授权检查。Shiro 授权检查与前面提到的相同。
  • 最后授权检查通过,用户正常访问到 http://casclient/security/view.do。

CAS Realm

Shiro 提供了一个名为 CasRealm 的类,与前面提到的 JDBC Realm 相似,该类同样包括认证和授权两部分功能。认证就是校验从 CAS 服务端返回的 ticket 是否有效;授权还是获取用户权限信息。

实现单点登录功能,需要扩展 CasRealm 类。

清单 9. Shiro CAS Realm
public class MyCasRealm extends CasRealm{ 
   
   // 获取授权信息
   protected AuthorizationInfo doGetAuthorizationInfo( 
      PrincipalCollection principals) { 
      //... 与前面 MyShiroRealm 相同
   } 
   
    public String getCasServerUrlPrefix() { 
      return "http://casserver/login"; 
   } 
   
   public String getCasService() { 
      return "http://casclient/shiro-cas"; 
   } 
   16 
 }

代码说明:

  1. doGetAuthorizationInfo 获取授权信息与前面章节“实现自己的 JDBC Realm”相同。
  2. 认证功能由 Shiro 自身提供的 CasRealm 实现。
  3. getCasServerUrlPrefix 方法返回 CAS 服务器地址,实际使用一般通过参数进行配置。
  4. getCasService 方法返回 CAS 客户端处理地址,实际使用一般通过参数进行配置。
  5. 认证过程需 keystore,否则会出现异常。可以通过设置系统属性的方式来指定,例如 System.setProperty("javax.net.ssl.trustStore","keystore-file");

CAS Spring 配置

实现单点登录的 Spring 配置与前面类似,不同之处参见代码说明。

清单 10. Shiro CAS Spring 配置
<bean id="shiroFilter" 
   class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 
   <property name="securityManager" ref="securityManager"/> 
   <property name="loginUrl" 
      value="http://casserver/login?service=http://casclient/shiro-cas"/> 
   <property name="successUrl" value="/welcome.do"/> 
   <property name="unauthorizedUrl" value="/403.do"/> 
   <property name="filters"> 
      <util:map> 
         <entry key="authc" value-ref="formAuthenticationFilter"/> 
         <entry key="cas" value-ref="casFilter"/> 
      </util:map> 
   </property> 
   <property name="filterChainDefinitions"> 
      <value> 
         /shiro-cas*=cas 
         /logout.do*=anon 
         /casticketerror.do*=anon 
          
         # 权限配置示例
         /security/account/view.do=authc,perms[SECURITY_ACCOUNT_VIEW] 
          
         /** = authc 
      </value> 
   </property> 
 </bean> 
 
 <bean id="securityManager" 
   class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> 
   <property name="realm" ref="myShiroRealm"/> 
 </bean> 

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

 <!-- CAS Realm --> 
 <bean id="myShiroRealm" class="xxx.packagename.MyCasRealm"> 
   <property name="cacheManager" ref="shiroCacheManager"/> 
 </bean> 

 <bean id="shiroCacheManager" 
   class="org.apache.shiro.cache.ehcache.EhCacheManager"> 
   <property name="cacheManager" ref="cacheManager"/> 
 </bean> 

 <bean id="formAuthenticationFilter" 
   class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"/> 

 <!-- CAS Filter --> 
 <bean id="casFilter" class="org.apache.shiro.cas.CasFilter"> 
   <property name="failureUrl" value="casticketerror.do"/> 
 </bean>

代码说明:

  1. shiroFilter 中 loginUrl 属性,为登录 CAS 服务端地址,参数 service 为服务端的返回地址。
  2. myShiroRealm 为上一节提到的 CAS Realm。
  3. casFilter 中 failureUrl 属性,为 Ticket 校验不通过时展示的错误页面。

Spring + Shiro 案例详解

web.xml关键配置

<context-param> 
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/classes/applicationContext.xml
/WEB-INF/classes/spring-shiro.xml
</param-value> 
</context-param>

spring-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"  
xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
    http://www.springframework.org/schema/util 
    http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<description>Shiro 配置</description>

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login.jsp" />
<property name="successUrl" value="/login.jsp" />
<property name="unauthorizedUrl" value="/error/noperms.jsp" />

<property name="filterChainDefinitions">
<value>
/login.jsp* = anon
/login.do* = anon
/index.jsp*= anon
/error/noperms.jsp*= anon
/*.jsp* = authc
/*.do* = authc
</value>
</property>
</bean>

<bean id="monitorRealm" class="com.shiro.security.MonitorRealm">
  <property name="credentialsMatcher">
           <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
               <property name="hashAlgorithmName" value="MD5"/>
             <!--   true means hex encoded, false means base64 encoded -->
               <property name="storedCredentialsHexEncoded" value="true"/>
               <!-- 迭代次数 -->
               <property name="hashIterations" value="2" />
           </bean>
          </property> 
</bean>

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 基于ehCache来缓存用户认证信息和授权信息的实现 -->
 <property name="cacheManager" ref="cacheManager"/>
<!-- sessionMode参数设置为native时,那么shrio就将用户的基本认证信息保存到缺省名称为shiro-activeSessionCache 的Cache中 -->
        <property name="sessionMode" value="native" />
<!--设置自定义realm -->
<property name="realm" ref="monitorRealm" />
</bean>

    
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManager" ref="ehCacheManager"/>
   <property name="cacheManagerConfigFile" value="classpath:shiro_ehcache.xml"/> 
    </bean>


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




<!-- securityManager -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" />
<property name="arguments" ref="securityManager" />
</bean>

<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

<!-- AOP式方法级权限检查  -->
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" />

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

</beans>

MonitorRealm.java

package com.shiro.security;


import java.util.ArrayList;
import java.util.List;


import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;


import com.shiro.mapper.RoleMapper;
import com.shiro.mapper.UserMapper;
import com.shiro.model.Resc;
import com.shiro.model.Role;


public class MonitorRealm extends AuthorizingRealm {


@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;


public MonitorRealm() {
super();
setAuthenticationTokenClass(UsernamePasswordToken.class);
          System.out.println("monitorRealm");
}


    /**
     * 验证
     */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
 
System.out.println("monitorRealm-验证");
/* 这里编写认证代码 */
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        String username = token.getUsername();
        if (username == null) {
            throw new AccountException("Null usernames are not allowed by this realm.");
        }
        String password = userMapper.getPassword(username).getPassword();
        if (password == null) {
            throw new UnknownAccountException("No account found for user [" + username + "]");
        }
        
        SimpleAuthenticationInfo saInfo = new SimpleAuthenticationInfo(username, password, getName());
        //用用户名填盐
        saInfo.setCredentialsSalt(ByteSource.Util.bytes(username));
return saInfo;
}


/**
     * 授权
     */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  System.out.println("monitorRealm-授权");
  String username = (String) principals.fromRealm( getName() ).iterator().next();
 // System.out.println(username);
  
  if( username != null ){
  SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
  info.addRole(username);
  info.addStringPermissions(   this.Topermissions( userMapper.selectUserRole(username).getRoles()) );
  
//   for(String str :   this.Topermissions( userMapper.selectUserRole(username).getRoles()) ){
//   System.out.println(str );
//   }
   return info;
  }
      return null;
}
   
   /**
    * 遍历角色得到资源
    */
   public List<String> Topermissions(List<Role> roles ){
  List<String>  permissions= new ArrayList<String>();
  for( Role role : roles ){
       permissions.addAll(   this.ToResc(    roleMapper.selectRoleResc(role.getRoleId()).getRescs()      )    );
  }
  return permissions;
   }

   /**
    * 遍历某个角色的资源
    */
public List<String > ToResc( List<Resc> rescs  ){
List<String > resStrings = new ArrayList<String>();
for ( Resc resc :rescs   ){
resStrings.add(resc.getResString());
}
return resStrings;
}

/**
 * 更新用户授权信息缓存.
 */
public void clearCachedAuthorizationInfo(String principal) {
 SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
 clearCachedAuthorizationInfo(principals);
}
 
/**
* 清除所有用户授权信息缓存.
*/
public void clearAllCachedAuthorizationInfo() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache != null) {
for (Object key : cache.keys()) {
cache.remove(key);
}
}
}
 
 
 
 
public UserMapper getUserMapper() {
return userMapper;
}


public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}


public RoleMapper getRoleMapper() {
return roleMapper;
}


public void setRoleMapper(RoleMapper roleMapper) {
this.roleMapper = roleMapper;
}

}

UserController.java

package com.shiro.controller;


import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;


import com.shiro.model.User;
import com.shiro.service.UserService;


@Controller
@RequestMapping("/user")
public class UserController extends MultiActionController {


@Autowired
private UserService userService;


@RequestMapping("/login")
public  ModelAndView login( User user )  {

ModelAndView mv = new ModelAndView();

Subject currentUser = SecurityUtils.getSubject();

       
//前台传过来时应该用JS加密一次
String  minwen=new Md5Hash( user.getPassword() ).toHex();
System.out.println( minwen);

UsernamePasswordToken token = new UsernamePasswordToken( user.getUserName() , minwen);

//记住我功能不是记住密码而是在整个会话过程记住会话ID,对未登录用户时用购物车有点用
/*
if( rememberMe != null ){
if( rememberMe ){
token.setRememberMe(true);
}
}
*/
try {
  currentUser.login(token);
} catch (UnknownAccountException uae ) { 
mv.addObject("info", "用户名不存在系统!");
mv.setViewName("/erro");
} catch (IncorrectCredentialsException ice ) { 
mv.addObject("info", "密码错误!");
mv.setViewName("/erro");
} catch (LockedAccountException lae ) { 
mv.addObject("info", "用户已经被锁定不能登录,请与管理员联系!");
mv.setViewName("/erro");
} catch (ExcessiveAttemptsException eae ) { 
mv.addObject("info", "错误次数过多!");
mv.setViewName("/erro");
} catch (AuthenticationException ae ) {
mv.addObject("info", "其他的登录错误!");
mv.setViewName("/erro");
}
//验证是否成功登录的方法
if(currentUser.isAuthenticated()){
//在session生命周期内有效
System.out.println("进入isAuthenticated");
mv.setViewName("/main");
}   
return mv;

}

@RequestMapping("/do")
public  ModelAndView do_(  )  {
ModelAndView mv = new ModelAndView();

Subject currentUser = SecurityUtils.getSubject();

if( currentUser.hasRole("超级管理员")){
mv.addObject("info", "没有这个角色!");
mv.setViewName("/lognTest");
}else{
if(currentUser.isPermitted("/user/do")){
mv.addObject("info", "do!");
mv.setViewName("/success");
}else{
mv.addObject("info", "do!");
mv.setViewName("/lognTest");
}
}
return mv;
}

@RequestMapping("/out")
public  ModelAndView out(  )  {
ModelAndView mv = new ModelAndView();
Subject currentUser = SecurityUtils.getSubject();
if( currentUser.hasRole("超级管理员")){
mv.addObject("info", "没有这个角色!");
mv.setViewName("/lognTest");
}else{
if(currentUser.isPermitted("/user/out")){
mv.addObject("info", "do!");
mv.setViewName("/success");
}else{
mv.addObject("info", "out!");
mv.setViewName("/lognTest");
}
}
return mv;
}

@RequestMapping("/test")
public  ModelAndView test(  )  {
ModelAndView mv = new ModelAndView();
Subject currentUser = SecurityUtils.getSubject();
if( currentUser.hasRole("超级管理员")){
mv.addObject("info", "没有这个角色!");
mv.setViewName("/lognTest");
}else{
if(currentUser.isPermitted("/user/test")){
mv.addObject("info", "test!");
mv.setViewName("/success");
}else{
mv.addObject("info", "test!");
mv.setViewName("/lognTest");
}
}

return mv;
}


public UserService getUserService() {
return userService;
}


public void setUserService(UserService userService) {
this.userService = userService;
}

}

login.jsp

<form id=loginform method=post name=loginform action="<%=path %>/user/login">
用户名:<input type=text id=userName name=userName class="input_border" maxlength=16/> <p/>
密码:<input type=password id=password name=password class="input_border"/>
<br>
<input  type="submit"  value="登录" />

</form>



Spring+Shiro小例子一


主要对象SecurityManager

    <!-- Define the realm you want to use to connect to your back-end security datasource: -->  
    <bean id="myRealm" class="...">  
    ...  
    </bean>  
      
    <bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">  
        <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->  
        <property name="realm" ref="myRealm"/>  
    </bean>  
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>  
      
    <!-- For simplest integration, so that all SecurityUtils.* methods work in all cases, -->  
    <!-- make the securityManager bean a static singleton.  DO NOT do this in web         -->  
    <!-- applications - see the 'Web Applications' section below instead.                 -->  
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">  
        <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>  
        <property name="arguments" ref="securityManager"/>  
    </bean>  

web.xml

 <!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml -->  
<filter>  
    <filter-name>shiroFilter</filter-name>  
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
    <init-param>  
        <param-name>targetFilterLifecycle</param-name>  
        <param-value>true</param-value>  
    </init-param>  
</filter>  
  
...  
  
<!-- Make sure any request you want accessible to Shiro is filtered. /* catches all -->  
<!-- requests.  Usually this filter mapping is defined first (before all others) to -->  
<!-- ensure that Shiro works in subsequent filters in the filter chain:             -->  
<filter-mapping>  
    <filter-name>shiroFilter</filter-name>  
    <url-pattern>/*</url-pattern>  
</filter-mapping>

基本概念 comprehensive solution to authentication, authorization, cryptography, and session management
 
 authenticate 认证
 
 Authorization  授权

 Subject currentUser = SecurityUtils.getSubject();  
  
   
 if ( !currentUser.isAuthenticated() ) {  
    //collect user principals and credentials in a gui specific manner   
    //such as username/password html form, X509 certificate, OpenID, etc.  
    //We'll use the username/password example here since it is the most common.  
    //(do you know what movie this is from? ;)  
    UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");  
    //this is all you have to do to support 'remember me' (no config - built in!):  
    token.setRememberMe(true);  
    currentUser.login(token);  
}  
  
  
try {  
    currentUser.login( token );  
    //if no exception, that's it, we're done!  
} catch ( UnknownAccountException uae ) {  
    //username wasn't in the system, show them an error message?  
} catch ( IncorrectCredentialsException ice ) {  
    //password didn't match, try again?  
} catch ( LockedAccountException lae ) {  
    //account for that username is locked - can't login.  Show them a message?  
}   
    ... more types exceptions to check if you want ...  
} catch ( AuthenticationException ae ) {  
    //unexpected condition - error?  
}  
  
   
  
if ( currentUser.hasRole( "schwartz" ) ) {  
    log.info("May the Schwartz be with you!" );  
} else {  
    log.info( "Hello, mere mortal." );  
}  
  
   
  
if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {  
    log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'.  " +  
                "Here are the keys - have fun!");  
} else {  
    log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");  
} 

实例展示SpringMVC+Shiro登录和退出

    @Controller  
    @RequestMapping(value = "/roll")  
    public class RollLoginController {  
      
        @RequestMapping(value = "/login", method = RequestMethod.POST)  
        @ResponseBody  
        public String login(@RequestParam("username") String username,  
                @RequestParam("password") String password) {  
      
            Subject currentUser = SecurityUtils.getSubject();  
            String result = "login";  
            if (!currentUser.isAuthenticated()) {  
                result = login(currentUser,username,password);  
            }else{//重复登录  
                ShiroUser shiroUser = (ShiroUser) currentUser.getPrincipal();  
                if(!shiroUser.getLoginName().equalsIgnoreCase(username)){//如果登录名不同  
                    currentUser.logout();  
                    result = login(currentUser,username,password);  
                }  
            }  
            return result;  
        }  
          
        private String login(Subject currentUser,String username,String password){  
            String result = "login";  
            UsernamePasswordToken token = new UsernamePasswordToken(username,  
                    password);  
            token.setRememberMe(false);  
            try {  
                currentUser.login(token);  
                result = "success";  
            } catch (UnknownAccountException uae) {  
                result = "failure";  
            } catch (IncorrectCredentialsException ice) {  
                result = "failure";  
            } catch (LockedAccountException lae) {  
                result = "failure";  
            } catch (AuthenticationException ae) {  
                result = "failure";  
            }  
            return result;  
        }  
      
        @RequestMapping(value = "/logout", method = RequestMethod.POST)  
        @ResponseBody  
        public String logout() {  
      
            Subject currentUser = SecurityUtils.getSubject();  
            String result = "logout";  
            currentUser.logout();  
            return result;  
        }  
      
        @RequestMapping(value = "/chklogin", method = RequestMethod.POST)  
        @ResponseBody  
        public String chkLogin() {  
      
            Subject currentUser = SecurityUtils.getSubject();  
      
            if (!currentUser.isAuthenticated()) {  
                return "false";  
            }  
      
            return "true";  
        }  
      
    }  

applicationContext-shiro.xml

    <?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.1.xsd"  
        default-lazy-init="true">  
      
        <description>Shiro Configuration</description>  
      
        <!-- Shiro's main business-tier object for web-enabled applications -->  
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" depends-on="userDao,groupDao">  
            <property name="realm" ref="shiroDbRealm" />  
            <property name="cacheManager" ref="cacheManager" />  
        </bean>  
      
        <!-- 項目自定义的Realm -->  
        <bean id="shiroDbRealm" class="org.springside.examples.miniweb.service.account.ShiroDbRealm">  
            <property name="accountManager" ref="accountManager"/>  
        </bean>  
      
        <!-- Shiro Filter -->  
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
            <property name="securityManager" ref="securityManager" />  
            <property name="loginUrl" value="/login" />  
            <property name="successUrl" value="/account/user/" />  
            <property name="filterChainDefinitions">  
                <value>  
                    /rollindex = anon  
                    /index.html = anon  
                    /login = authc  
                    /logout = logout  
                    /static/** = anon  
                    /admin/** = user   
                    /account/** = user  
                </value>  
            </property>  
        </bean>  
      
        <!-- 用户授权信息Cache -->  
        <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />  
          
        <!-- 保证实现了Shiro内部lifecycle函数的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>  

ShiroDbRealm.java 
 public class ShiroDbRealm extends AuthorizingRealm {  
  
    private static Logger logger = LoggerFactory.getLogger(ShiroDbRealm.class);  
  
    private static final String ALGORITHM = "MD5";  
    private AccountManager accountManager;  
  
    /** 
     * 认证回调函数, 登录时调用. 
     */  
    @Override  
    protected AuthenticationInfo doGetAuthenticationInfo(  
            AuthenticationToken authcToken) throws AuthenticationException {  
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;  
        User user = accountManager.findUserByLoginName(token.getUsername());  
        if (user != null) {  
            return new SimpleAuthenticationInfo(new ShiroUser(  
                    user.getName(), user.getRealName()), user.getPassword(),  
                    getName());  
        } else {  
            return null;  
        }  
    }  
  
    /** 
     * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用. 
     */  
    @Override  
    protected AuthorizationInfo doGetAuthorizationInfo(  
            PrincipalCollection principals) {  
        ShiroUser shiroUser = (ShiroUser) principals.fromRealm(getName())  
                .iterator().next();  
        User user = accountManager  
                .findUserByLoginName(shiroUser.getLoginName());  
        //基于Permission的权限信息  
        if (user != null) {  
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();  
             for (Role role : user.getRoles()) {  
                 for(Permission permission: role.getPermisssions()){  
                     info.addStringPermission(permission.getValue());  
                 }  
             }  
            return info;  
        } else {  
            return null;  
        }  
    }  
  
    /** 
     * 更新用户授权信息缓存. 
     */  
    public void clearCachedAuthorizationInfo(String principal) {  
        SimplePrincipalCollection principals = new SimplePrincipalCollection(  
                principal, getName());  
        clearCachedAuthorizationInfo(principals);  
    }  
  
    /** 
     * 清除所有用户授权信息缓存. 
     */  
    public void clearAllCachedAuthorizationInfo() {  
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();  
        if (cache != null) {  
            for (Object key : cache.keys()) {  
                cache.remove(key);  
            }  
        }  
    }  
  
    @Autowired  
    public void setAccountManager(AccountManager accountManager) {  
        this.accountManager = accountManager;  
    }  
  
    public String encrypt(String plainText) {  
        String result = "";  
        byte[] hashPassword = null;  
        try {  
            hashPassword = Digests.md5(new ByteArrayInputStream(plainText  
                    .getBytes()));  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
        result = Encodes.encodeHex(hashPassword);  
        return result;  
  
    }  
  
    @PostConstruct  
    public void initCredentialsMatcher() {//MD5加密  
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(  
                ALGORITHM);  
        setCredentialsMatcher(matcher);  
    }  
  
    /** 
     * 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息. 
     */  
    public static class ShiroUser implements Serializable {  
  
        private static final long serialVersionUID = -1748602382963711884L;  
        private String loginName;  
        private String name;  
  
        public ShiroUser(String loginName, String name) {  
            this.loginName = loginName;  
            this.name = name;  
        }  
  
        public String getLoginName() {  
            return loginName;  
        }  
  
        /** 
         * 本函数输出将作为默认的<shiro:principal/>输出. 
         */  
        @Override  
        public String toString() {  
            return loginName;  
        }  
  
        public String getName() {  
            return name;  
        }  
    }  
}  





Shiro+Spring 小示例:


简介: Shiro 是一个 Apache Incubator 项目,旨在简化身份验证和授权。是一个很不错的安全框架。 
下面记录一下shiro和Spring整合的过程的一个小示例: 
Web.xml配置

 <context-param>  
        <param-name>contextConfigLocation</param-name>      <param-value>classpath:applicationContext.xml,classpath:spring-shiro.xml</param-value>  
    </context-param>  
<!-- apache shiro权限 -->  
    <filter>    
        <filter-name>shiroFilter</filter-name>    
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>    
        <init-param>    
            <param-name>targetFilterLifecycle</param-name>    
            <param-value>true</param-value>    
        </init-param>    
    </filter>    
    
    <filter-mapping>    
        <filter-name>shiroFilter</filter-name>    
        <url-pattern>*.do</url-pattern>    
    </filter-mapping>    
    <filter-mapping>    
        <filter-name>shiroFilter</filter-name>    
        <url-pattern>*.jsp</url-pattern>    
    </filter-mapping>   

第一部分是将shiro的配置文件引入到web.xml中,我这里是spring-shiro.xml; 
下面的是一个过滤器,过滤.do,.jsp的请求,引入shiro web.xml就配这么多。 
spring-shiro.xml配置文件

    <?xml version="1.0" encoding="UTF-8"?>  
    <beans xmlns="http://www.springframework.org/schema/beans"  
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"  
        xsi:schemaLocation="http://www.springframework.org/schema/beans   
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd     
        http://www.springframework.org/schema/util   
        http://www.springframework.org/schema/util/spring-util-3.0.xsd">  
        <description>Shiro 配置</description>  
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
            <property name="securityManager" ref="securityManager" />  
            <property name="loginUrl" value="/login.jsp" />  
            <property name="successUrl" value="/login.jsp" />  
            <property name="unauthorizedUrl" value="/error/noperms.jsp" />  
            <property name="filterChainDefinitions">  
                <value>  
                    /login.jsp* = anon  
                    /login.do* = anon  
                    /index.jsp*= anon  
                    /error/noperms.jsp*= anon  
                    /*.jsp* = authc  
                    /*.do* = authc  
                </value>  
            </property>  
        </bean>  
      
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
            <!--设置自定义realm -->  
            <property name="realm" ref="monitorRealm" />  
        </bean>  
      
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />  
      
        <!--自定义Realm 继承自AuthorizingRealm -->  
        <bean id="monitorRealm" class="com.springmvc.service.MonitorRealm"></bean>  
        <!-- securityManager -->  
        <bean  
            class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">  
            <property name="staticMethod"  
                value="org.apache.shiro.SecurityUtils.setSecurityManager" />  
            <property name="arguments" ref="securityManager" />  
        </bean>  
      
        <!-- Enable Shiro Annotations for Spring-configured beans. Only run after -->  
        <!-- the lifecycleBeanProcessor has run: -->  
        <bean  
            class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"  
            depends-on="lifecycleBeanPostProcessor" />  
        <bean  
            class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">  
            <property name="securityManager" ref="securityManager" />  
      
        </bean>  
      
    </beans>  

这里不想说太多,我这里没什么特别的,很多都是用的shiro自己的原有的,只有一个realm是我自己定义的,可能自己定义的会更好用点吧,如果不用自己定义的就用shiro的jdbcrealm,我自定义的也是跟jdbcrealm差不多的,后面我们再说我的自定义realm。另外就是给页面指定过滤器 
/login.jsp* = anon /login.do* = anon /index.jsp*= anon /error/noperms.jsp*= anon /*.jsp* = authc /*.do* = authc
Anon:不指定过滤器,不错是这个过滤器是空的,什么都没做,跟没有一样。 
Authc:验证,这些页面必须验证后才能访问,也就是我们说的登录后才能访问。 
这里还有其他的过滤器,我没用,比如说授权,这个比较重要,但是这个过滤器有个不好的地方,就是要带一个参数,所以如果配在这里就不是很合适,因为每个页面,或是.do的权限不一样,而我们也没法事先知道他需要什么权限。所以这里不配,我们在代码中再授权。这里.do和.jsp后面的*表示参数,比如login.jsp?main这种,是为了匹配这种。好行了,继续往下吧。 
验证: 
验证我们就弄一个登录页面,然后提交到后台的action
 @RequestMapping(params = "main")  
    public ModelAndView login(User user,HttpSession session, HttpServletRequest request) {  
  
        ModelAndView modelView = new ModelAndView();  
        Subject currentUser = SecurityUtils.getSubject();  
        UsernamePasswordToken token = new UsernamePasswordToken(  
                user.getUsercode(), EncryptUtils.encryptMD5(user.getPassword()));  
        token.setRememberMe(true);  
        try {  
            currentUser.login(token);  
        } catch (AuthenticationException e) {  
            modelView.addObject("message", "login errors");  
            modelView.setViewName("/login");  
            e.printStackTrace();  
              
        }  
        if(currentUser.isAuthenticated()){  
              
            session.setAttribute("userinfo", user);  
            modelView.setViewName("/main");  
        }else{  
            modelView.addObject("message", "login errors");  
            modelView.setViewName("/login");  
        }  
        return modelView;  
    } 

这里我用的是spring MVC,你不用管他用什么mvc,我们只要知道,前台.do登录以后进入这个方法就行 
Subject currentUser = SecurityUtils.getSubject() 
;就是代表当前的用户。 
UsernamePasswordToken token = new UsernamePasswordToken( user.getUsercode(),EncryptUtils.encryptMD5(user.getPassword())); 
这里的token大家叫他令牌,也就相当于一张表格,你要去验证,你就得填个表,里面写好用户名密码,交给公安局的同志给你验证。 
currentUser.login(token); 
这句是提交申请,验证能不能通过,也就是交给公安局同志了。这里会回调reaml里的一个方法 
protected AuthenticationInfo doGetAuthenticationInfo() 
验证是否通过 
if(currentUser.isAuthenticated()) 
通过则转到你的页面,不通过到login页面并返回错误信息。 
现在我们看看我们的reaml类,这是一个自定义的realm,

    @Service("monitorRealm")  
    public class MonitorRealm extends AuthorizingRealm {  
        /* 
         * @Autowired UserService userService; 
         *  
         * @Autowired RoleService roleService; 
         *  
         * @Autowired LoginLogService loginLogService; 
         */  
      
        public MonitorRealm() {  
            super();  
      
        }  
      
        @Override  
        protected AuthorizationInfo doGetAuthorizationInfo(  
                PrincipalCollection principals) {  
            /* 这里编写授权代码 */  
            Set<String> roleNames = new HashSet<String>();  
            Set<String> permissions = new HashSet<String>();  
            roleNames.add("admin");  
            permissions.add("user.do?myjsp");  
            permissions.add("login.do?main");  
            permissions.add("login.do?logout");  
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);  
            info.setStringPermissions(permissions);  
            return info;  
        }  
        @Override  
        protected AuthenticationInfo doGetAuthenticationInfo(  
                AuthenticationToken authcToken) throws AuthenticationException {  
            /* 这里编写认证代码 */  
            UsernamePasswordToken token = (UsernamePasswordToken) authcToken;  
    //      User user = securityApplication.findby(upToken.getUsername());  
            User user = new User();  
            user.setUsercode(token.getUsername());  
            user.setUserName("admin");  
            user.setPassword(EncryptUtils.encryptMD5("admin"));  
    //      if (user != null) {  
            return new SimpleAuthenticationInfo(user.getUserName(),  
                    user.getPassword(), getName());  
        }  
        public void clearCachedAuthorizationInfo(String principal) {  
            SimplePrincipalCollection principals = new SimplePrincipalCollection(  
                    principal, getName());  
            clearCachedAuthorizationInfo(principals);  
        }  
    }  

这里我没有跟数据库打交道,如果要跟数据库打交道很简单,你调用一个service,service再调dao,根据用户名去打该用户的密码。用户我们可以前面的令牌也就是我说的表格来取的,我们前台提交给公安同志一个表格,里面有用户密码,但需要注意的是,我们在这里并不把表格中的用户密码路数据库中的用户密码进行比较,我们只是根据表格中的用户名把密码查出来,然后把数据库的用户密码放在一个SimpleAuthenticationInfo对象中返回即可,这位公安同志不负责验证,只负责验证的材料。我这里没有查库,伪造了一个用户密码放入了对象。密码是admin。什么时候验证,就是我们前面调currentUser.isAuthenticated()时验证,所有的材料都全了,需要验证的时候就调一下这个方法就可以了。我们前面spring里配了这个 
/*.jsp* = authc 
/*.do* = authc 
你配了authc过滤器,shiro会自动调currentUser.isAuthenticated()这个方法,没有登录的将被返回 
<property name="unauthorizedUrl" value="/error/noperms.jsp" /> 
配置的页面。 
好到这里登录就算是完成了。完成了登录下面就是要授权了,我们已经登录系统,我进入系统总要做点什么吧,比如这个系统就是一个公司的话,我现在已经进入公司了,如果我要进办公室,还得要授权(假如有门禁的话)刷卡。这们就里就是访问某个页面或是某个.do, 
授权: 
因为前面我们只配了验证过滤器,现在已经登录系统,如果我们请求一个*.do的话就会来到后台的action,我们授权也将在这里授。

  1. @Controller  
  2. @RequestMapping(value="user")  
  3. public class UserController {  
  4.       
  5.     /** 
  6.      * 跳转到myjsp页面 
  7.      *  
  8.      * @return 
  9.      */  
  10.     @RequestMapping(params = "myjsp")  
  11.     public String home() {  
  12.         Subject currentUser = SecurityUtils.getSubject();  
  13.         if(currentUser.isPermitted("user.do?myjsp")){  
  14.             return "/my";  
  15.         }else{  
  16.             return "error/noperms";  
  17.         }  
  18.     }  
  19. }
我一直都说action,其实spring mvc里不再是action了,叫controller,我们这里的home方法的访问路径是user.do?myjsp,也就是我们登录系统后请求一个这个方法user.do?myjsqp,转到home方法后,我要看他有没有权限访问此方法,我就用下面的代码 
Subject currentUser = SecurityUtils.getSubject(); 
currentUser.isPermitted("user.do?myjsp"); 
首先得到当前的用户,再看此用户是否有权访问user.do?myjsp,参数就是权限,这里后台数据库就会有这么一个权限,权限表中的权限地址就是user.do?myjsp,例如我们一般的系统左边是一棵功能菜单树,树的结点会有一个url链接,这个链接就是在权限表中。当然可能前面还会一个http:\\什么的。反正这里也跟后台的权限表中的地址一致就行了,shiro他是如何授权的。一样的你调用currentUser.isPermitted("user.do?myjsp");此方法后会回调realm中的protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals)方法,这个reaml类是非常重要的。这个类上面已经给出了,我们看看他是如何授权的。因为我没有连数据库,也是伪造了一个权限。如果是连数据库也很简单,用户表,角色表,权限表这三个表是有关联的,我们根据用户名就能查出此用户拥有的角色和所有的权限。


 Set<String> roleNames = new HashSet<String>();  
        Set<String> permissions = new HashSet<String>();  
        roleNames.add("admin");  
        permissions.add("user.do?myjsp");  
        permissions.add("login.do?main");  
        permissions.add("login.do?logout");  
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);  
        info.setStringPermissions(permissions);  
        return info; 

最后构造一个对象并把权限给它就OK拉。如果是数据库查出来的,直接我的字符串替成你查出来的就行了。这样在你的controller中根据权限返回到指定的页面。

    if(currentUser.isPermitted("user.do?myjsp")){  
                return "/my";  
            }else{  
                return "error/noperms";  
            }  



【云栖快讯】阿里巴巴小程序繁星计划,20亿补贴第一弹云应用免费申请,限量从速!  详情请点击

网友评论