关闭

Apache Shiro

806人阅读 评论(0) 收藏 举报
分类:

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

杨 晓晋, 软件工程师, 深圳市雁联计算系统有限公司

2013 年 1 月 31 日

  • expand内容

用户权限模型

在揭开 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 校验不通过时展示的错误页面。

总结

至此,我们对 Shiro 有了较为深入的认识。Shiro 灵活,功能强大,几乎能满足我们实际应用中的各种情况,还等什么呢?让我开始使用 Shiro 为应用程序护航吧!

1、什么是 apache shiro :


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

如同 spring security 一样都是是一个权限安全框架,但是与Spring Security相比,在于他使用了和比较简洁易懂的认证和授权方式。



2、Apache Shiro 的三大核心组件:


1、Subject :当前用户的操作

2、SecurityManager:用于管理所有的Subject

3、Realms:用于进行权限信息的验证


Subject:即当前用户,在权限管理的应用程序里往往需要知道谁能够操作什么,谁拥有操作该程序的权利,shiro中则需要通过Subject来提供基础的当前用户信息,Subject 不仅仅代表某个用户,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。

SecurityManager:即所有Subject的管理者,这是Shiro框架的核心组件,可以把他看做是一个Shiro框架的全局管理组件,用于调度各种Shiro框架的服务。

Realms:Realms则是用户的信息认证器和用户的权限人证器,我们需要自己来实现Realms来自定义的管理我们自己系统内部的权限规则。



3、Authentication 和 Authorization


在shiro的用户权限认证过程中其通过两个方法来实现:

1、Authentication:是验证用户身份的过程。

2、Authorization:是授权访问控制,用于对用户进行的操作进行人证授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。




4、其他组件:


除了以上几个组件外,Shiro还有几个其他组件:

1、SessionManager :Shiro为任何应用提供了一个会话编程范式。

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




5、Shiro 完整架构图: 



图片转自:http://kdboy.iteye.com/blog/1154644



第二部分 Apache Shiro 整合Spring的Web程序构建


1、准备工具:


持久层框架:Hibernate4  这边我使用了hibernate来对数据持久层进行操作

控制显示层框架:SpringMVC 这边我使用了SpringMVC实际开发中也可以是其他框架

数据库MySQL

准备好所需要的jar放到项目中。

 


2、创建数据库:



首先需要四张表,分别为 user(用户)、role(角色)、permission(权限)、userRole(用户角色关系表)

这边分别创建四张表的实体类,通过Hiberantehibernate.hbm2ddl.auto属性的update 来自动生成数据表结构。


[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. /*** 
  2.  * 用户表 
  3.  *  
  4.  * @author Swinglife 
  5.  *  
  6.  */  
  7. @Table(name = "t_user")  
  8. @Entity  
  9. public class User {  
  10.   
  11.     @Id  
  12.     @GeneratedValue(strategy = GenerationType.AUTO)  
  13.     Integer id;  
  14.     /** 用户名 **/  
  15.     String username;  
  16.     /** 密码 **/  
  17.     String password;  
  18.     /** 是否删除 **/  
  19.     Integer isDelete;  
  20.     /** 创建时间 **/  
  21.     Date createDate;  
  22.     //多对多用户权限表  
  23.     @OneToMany(mappedBy = "user",cascade=CascadeType.ALL)  
  24.     List<UserRole> userRoles;  
  25.   
  26. 省略get set….  
  27.   
  28. }  

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. /**** 
  2.  * 角色表 
  3.  *  
  4.  * @author Swinglife 
  5.  *  
  6.  */  
  7. @Entity  
  8. @Table(name = "t_role")  
  9. public class Role {  
  10.     @Id  
  11.     @GeneratedValue(strategy = GenerationType.AUTO)  
  12.     Integer id;  
  13.     /**角色名**/  
  14.     String name;  
  15.     /**角色说明**/  
  16.     String description;  
  17.   
  18.   
  19. }  

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. /**** 
  2.  * 权限表 
  3.  *  
  4.  * @author Swinglife 
  5.  *  
  6.  */  
  7. @Entity  
  8. @Table(name = "t_permission")  
  9. public class Permission {  
  10.   
  11.     @Id  
  12.     @GeneratedValue(strategy = GenerationType.AUTO)  
  13.     Integer id;  
  14.     /**token**/  
  15.     String token;  
  16.     /**资源url**/  
  17.     String url;  
  18.     /**权限说明**/  
  19.     String description;  
  20.     /**所属角色编号**/  
  21.     Integer roleId;  
  22.   
  23. }  

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. /*** 
  2.  * 用户角色表 
  3.  *  
  4.  * @author Swinglife 
  5.  *  
  6.  */  
  7. @Entity  
  8. @Table(name = "t_user_role")  
  9. public class UserRole {  
  10.   
  11.     @Id  
  12.     @GeneratedValue(strategy = GenerationType.AUTO)  
  13.     Integer id;  
  14.   
  15.     @ManyToOne(cascade = CascadeType.ALL)  
  16.     @JoinColumn(name = "userId", unique = true)  
  17.     User user;  
  18.     @ManyToOne  
  19.     @JoinColumn(name = "roleId", unique = true)  
  20.     Role role;  
  21.   
  22. }  

3、编写操作用户业务的Service:


[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. @Service  
  2. public class AccountService {  
  3.   
  4.     /**** 
  5.      * 通过用户名获取用户对象 
  6.      *  
  7.      * @param username 
  8.      * @return 
  9.      */  
  10.     public User getUserByUserName(String username) {  
  11.         User user = (User) dao.findObjectByHQL("FROM User WHERE username = ?"new Object[] { username });  
  12.         return user;  
  13.     }  
  14.   
  15.     /*** 
  16.      * 通过用户名获取权限资源 
  17.      *  
  18.      * @param username 
  19.      * @return 
  20.      */  
  21.     public List<String> getPermissionsByUserName(String username) {  
  22.         System.out.println("调用");  
  23.         User user = getUserByUserName(username);  
  24.         if (user == null) {  
  25.             return null;  
  26.         }  
  27.         List<String> list = new ArrayList<String>();  
  28.         // System.out.println(user.getUserRoles().get(0).get);  
  29.         for (UserRole userRole : user.getUserRoles()) {  
  30.             Role role = userRole.getRole();  
  31.             List<Permission> permissions = dao.findAllByHQL("FROM Permission WHERE roleId = ?"new Object[] { role.getId() });  
  32.             for (Permission p : permissions) {  
  33.                 list.add(p.getUrl());  
  34.             }  
  35.         }  
  36.         return list;  
  37.     }  
  38.   
  39.     // 公共的数据库访问接口  
  40.     // 这里省略BaseDao dao的编写  
  41.     @Autowired  
  42.     private BaseDao dao;  
  43. }  



4、编写shiro组件自定义Realm:


[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. package org.swinglife.shiro;  
  2.   
  3. import java.util.List;  
  4.   
  5. import org.apache.shiro.authc.AuthenticationException;  
  6. import org.apache.shiro.authc.AuthenticationInfo;  
  7. import org.apache.shiro.authc.AuthenticationToken;  
  8. import org.apache.shiro.authc.SimpleAuthenticationInfo;  
  9. import org.apache.shiro.authc.UsernamePasswordToken;  
  10. import org.apache.shiro.authz.AuthorizationInfo;  
  11. import org.apache.shiro.authz.SimpleAuthorizationInfo;  
  12. import org.apache.shiro.realm.AuthorizingRealm;  
  13. import org.apache.shiro.subject.PrincipalCollection;  
  14. import org.swinglife.model.User;  
  15. import org.swinglife.service.AccountService;  
  16.   
  17. /**** 
  18.  * 自定义Realm 
  19.  *  
  20.  * @author Swinglife 
  21.  *  
  22.  */  
  23. public class MyShiroRealm extends AuthorizingRealm {  
  24.   
  25.     /*** 
  26.      * 获取授权信息 
  27.      */  
  28.     @Override  
  29.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {  
  30.         //根据自己系统规则的需要编写获取授权信息,这里为了快速入门只获取了用户对应角色的资源url信息  
  31.         String username = (String) pc.fromRealm(getName()).iterator().next();  
  32.         if (username != null) {  
  33.             List<String> pers = accountService.getPermissionsByUserName(username);  
  34.             if (pers != null && !pers.isEmpty()) {  
  35.                 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();  
  36.                 for (String each : pers) {  
  37.                     //将权限资源添加到用户信息中  
  38.                     info.addStringPermission(each);  
  39.                 }  
  40.                 return info;  
  41.             }  
  42.         }  
  43.         return null;  
  44.     }  
  45.     /*** 
  46.      * 获取认证信息 
  47.      */  
  48.     @Override  
  49.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException {  
  50.         UsernamePasswordToken token = (UsernamePasswordToken) at;  
  51.         // 通过表单接收的用户名  
  52.         String username = token.getUsername();  
  53.         if (username != null && !"".equals(username)) {  
  54.             User user = accountService.getUserByUserName(username);  
  55.             if (user != null) {  
  56.                 return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());  
  57.             }  
  58.         }  
  59.   
  60.         return null;  
  61.     }  
  62.       
  63.     /**用户的业务类**/  
  64.     private AccountService accountService;  
  65.       
  66.     public AccountService getAccountService() {  
  67.         return accountService;  
  68.     }  
  69.   
  70.     public void setAccountService(AccountService accountService) {  
  71.         this.accountService = accountService;  
  72.     }  
  73.   
  74. }  

上述类继承了Shiro的AuthorizingRealm类 实现了AuthorizationInfoAuthenticationInfo两个方法,用于获取用户权限和认证用户登录信息


5、编写LoginController:



[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. package org.swinglife.controller;  
  2.   
  3. import org.apache.shiro.SecurityUtils;  
  4. import org.apache.shiro.authc.UsernamePasswordToken;  
  5. import org.apache.shiro.subject.Subject;  
  6. import org.springframework.beans.factory.annotation.Autowired;  
  7. import org.springframework.stereotype.Controller;  
  8. import org.springframework.web.bind.annotation.RequestMapping;  
  9. import org.springframework.web.bind.annotation.RequestMethod;  
  10. import org.springframework.web.portlet.ModelAndView;  
  11. import org.swinglife.model.User;  
  12. import org.swinglife.service.AccountService;  
  13.   
  14. /**** 
  15.  * 用户登录Controller 
  16.  *  
  17.  * @author Swinglife 
  18.  *  
  19.  */  
  20. @Controller  
  21. public class LoginController {  
  22.   
  23.     /*** 
  24.      * 跳转到登录页面 
  25.      *  
  26.      * @return 
  27.      */  
  28.     @RequestMapping(value = "toLogin")  
  29.     public String toLogin() {  
  30.         // 跳转到/page/login.jsp页面  
  31.         return "login";  
  32.     }  
  33.   
  34.     /*** 
  35.      * 实现用户登录 
  36.      *  
  37.      * @param username 
  38.      * @param password 
  39.      * @return 
  40.      */  
  41.     @RequestMapping(value = "login")  
  42.     public ModelAndView Login(String username, String password) {  
  43.         ModelAndView mav = new ModelAndView();  
  44.         User user = accountService.getUserByUserName(username);  
  45.         if (user == null) {  
  46.             mav.setView("toLogin");  
  47.             mav.addObject("msg""用户不存在");  
  48.             return mav;  
  49.         }  
  50.         if (!user.getPassword().equals(password)) {  
  51.             mav.setView("toLogin");  
  52.             mav.addObject("msg""账号密码错误");  
  53.             return mav;  
  54.         }  
  55.         SecurityUtils.getSecurityManager().logout(SecurityUtils.getSubject());  
  56.         // 登录后存放进shiro token  
  57.         UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());  
  58.         Subject subject = SecurityUtils.getSubject();  
  59.         subject.login(token);  
  60.         // 登录成功后会跳转到successUrl配置的链接,不用管下面返回的链接。  
  61.         mav.setView("redirect:/home");  
  62.         return mav;  
  63.     }  
  64.   
  65.     // 处理用户业务类  
  66.     @Autowired  
  67.     private AccountService accountService;  
  68. }  


6、编写信息认证成功后的跳转页面:


[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. package org.swinglife.controller;  
  2.   
  3. import org.springframework.stereotype.Controller;  
  4. import org.springframework.web.bind.annotation.RequestMapping;  
  5.   
  6. @Controller  
  7. public class IndexController {  
  8.   
  9.     @RequestMapping("home")  
  10.     public String index() {  
  11.         System.out.println("登录成功");  
  12.         return "home";  
  13.     }  
  14. }  


7、Shiro的配置文件.xml


[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  2.         <property name="securityManager" ref="securityManager" />  
  3.         <property name="loginUrl" value="/toLogin" />  
  4.         <property name="successUrl" value="/home" />  
  5.         <property name="unauthorizedUrl" value="/403" />  
  6.            
  7.         <property name="filterChainDefinitions">  
  8.             <value>  
  9.                 /toLogin = authc <!-- authc 表示需要认证才能访问的页面 -->  
  10.                 /home = authc, perms[/home]  <!-- perms 表示需要该权限才能访问的页面 -->  
  11.             </value>  
  12.         </property>  
  13.     </bean>  
  14.   
  15.   
  16.   
  17.   
  18.     <bean id="myShiroRealm" class="org.swinglife.shiro.MyShiroRealm">  
  19.         <property name="accountService" ref="accountService" />  
  20.     </bean>  
  21.   
  22.     <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  23.         <property name="realm" ref="myShiroRealm"></property>  
  24.     </bean>  
  25.   
  26.     <bean id="accountService" class="org.swinglife.service.AccountService"></bean>  
  27.   
  28.     <!-- <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">   
  29.         <property name="cacheManager" ref="cacheManager" /> </bean> -->  



loginUrl 用于配置登陆页

successUrl 用于配置登录成功后返回的页面,不过该参数只会在当登录页面中并没有任何返回页面时才会生效,否则会跳转到登录Controller中的指定页面。

unauthorizedUrl 用于配置没有权限访问页面时跳转的页面


filterChainDefinitions:apache shiro通过filterChainDefinitions参数来分配链接的过滤,资源过滤有常用的以下几个参数:

1、authc 表示需要认证的链接

2、perms[/url] 表示该链接需要拥有对应的资源/权限才能访问

3、roles[admin] 表示需要对应的角色才能访问

4、perms[admin:url] 表示需要对应角色的资源才能访问


8、登陆页login.jsp

[html] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <body>  
  2.   
  3. <h1>user login</h1>  
  4. <form action="login" method="post">  
  5. username:<input type="text" name="username"><p>  
  6. password:<input type="password" name="password">  
  7. <p>  
  8. ${msg }  
  9. <input type="submit" value="submit">  
  10. </form>  
  11. </body>  




9、运行程序

在数据库中添加一条用户、角色、以及权限数据,并且在关联表中添加一条关联数据:








在浏览器中访问: home页面 就会跳转到登录页面:  




最后输入 账号密码 就会跳转到登录成功页面。


shiro jar:http://download.csdn.net/detail/swingpyzf/8766673

项目源码:github:https://github.com/swinglife/shiro_ex


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:388474次
    • 积分:6268
    • 等级:
    • 排名:第4055名
    • 原创:233篇
    • 转载:163篇
    • 译文:4篇
    • 评论:71条
    Github
    https://github.com/jinhang
    博客专栏
    最新评论