shiro讲解之 SpringMVC 集成Shiro

shiro讲解之 SpringMVC 集成Shiro

本章节将通过实例来学习下SpringMVC+Spring+Shiro如何集成并用一个精简的例子说明。


整合

  • 新建一个完整的Spring+SpringMVC 框架

    • 关于Spring整个SpringMVC的例子已在SpingMVC模块有分享,可移步至SpringMVC。
  • 项目目录

    • 这里写图片描述
  • 整合 Shiro 步骤

    • 下载 Shiro 及相关jar包

      • pom.xml新增以下依赖

        <!-- Shiro -->
                <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-all -->
                <dependency>
                    <groupId>org.apache.shiro</groupId>
                    <artifactId>shiro-all</artifactId>
                    <version>1.3.2</version>
                </dependency>
                <!-- Shiro end -->
        
                <!-- Ehcache -->
                <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache -->
                <dependency>
                    <groupId>org.apache.shiro</groupId>
                    <artifactId>shiro-ehcache</artifactId>
                    <version>1.2.4</version>
                </dependency>
        
                <!-- Ehcahche end -->
    • 配置web.xml

      • Shiro web.xml配置信息参考 Shiro Web App。这里我们的整合将直接参考Spring Example中的web.xml的配置信息。

        <!-- 配置Shiro Filter -->
            <!-- Shiro Filter is defined in the spring application context: -->
            <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>/*</url-pattern>
            </filter-mapping>

        web.xml文件中配置Shiro 的DelegatingFilterProxy,该Filter的核心作用点就是用户拦截和过滤请求的URL,之于如何处理URL将在Shiro配置文件的 shiroFilter中有具体的说明。

    • 新建ehcache.xml缓存
      在整合Shiro的过程中我们在使用shiro的缓存机制时会使用到缓存,这一点在pom.xml的依赖有提现,另外在后续的篇章中我们也将详细说明Shiro的缓存机制,这里做初步了解。
      在classpath(一般建议,也可以自定义)下新建一个ehcache.xml,配置信息如下,如有必要可全部复制

      <?xml version="1.0" encoding="UTF-8"?>
      <ehcache>
      
          <diskStore path="java.io.tmpdir" /> <!-- 缓存存放目录(此目录为放入系统默认缓存目录),也可以是”D:/cache“ java.io.tmpdir -->
      
      
          <!-- 登录记录缓存 锁定10分钟 -->
          <cache name="passwordRetryCache"
                 maxEntriesLocalHeap="2000"
                 eternal="false"
                 timeToIdleSeconds="3600"
                 timeToLiveSeconds="0"
                 overflowToDisk="false"
                 statistics="true">
          </cache>
      
          <cache name="authorizationCache"
                 maxEntriesLocalHeap="2000"
                 eternal="false"
                 timeToIdleSeconds="3600"
                 timeToLiveSeconds="0"
                 overflowToDisk="false"
                 statistics="true">
          </cache>
      
          <cache name="authenticationCache"
                 maxEntriesLocalHeap="2000"
                 eternal="false"
                 timeToIdleSeconds="3600"
                 timeToLiveSeconds="0"
                 overflowToDisk="false"
                 statistics="true">
          </cache>
      
          <cache name="shiro-activeSessionCache"
                 maxEntriesLocalHeap="2000"
                 eternal="false"
                 timeToIdleSeconds="3600"
                 timeToLiveSeconds="0"
                 overflowToDisk="false"
                 statistics="true">
          </cache>
      
          <defaultCache maxElementsInMemory="10000" eternal="false"
              timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true"
              maxElementsOnDisk="10000000" diskPersistent="false"
              diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" />
      
          <!-- name:Cache的唯一标识 maxElementsInMemory:内存中最大缓存对象数 maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大 
              eternal:Element是否永久有效,一但设置了,timeout将不起作用 overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中 
              timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大 
              timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大 
              diskPersistent:是否缓存虚拟机重启期数据 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒 
              diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区 
              memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用) -->
      </ehcache>  
      
    • 在ApplicationContext.xml中配置Shiro相关信息
      在ApplicationContext.xml文件中配置Shiro的必要信息,这里我们的整合将直接参考Spring Example中的applicationContext.xml的配置信息。基本配置如下(在讲解后续功能时会不断完善该配置)

      <!-- 1.配置SecurityManager -->
          <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
              <property name="cacheManager" ref="cacheManager" />
              <!-- Shiro 多Realm认证策略 -->
              <!-- <property name="authenticator" ref="authenticator"></property> -->
              <property name="realms">
                  <list>
                      <ref bean="shiroRealm"></ref>
                  </list>
              </property>
          </bean>
      
          <!-- 2.配置 cacheManager -->
          <!-- 2.1 自定义ehcache 此处需要加入ehcache jar 及其配置文件 -->
          <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
              <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property>
          </bean>
      
          <!-- 3.配置Realm -->
          <bean id="shiroRealm" class="com.shiro.example.interceptor.realm.ShiroRealm">
              <!-- <property name="credentialsMatcher">
                  <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                      <property name="hashAlgorithmName" value="SHA1"></property>
                      <property name="hashIterations" value="1024"></property>
                  </bean>
              </property> -->
          </bean>
      
          <!-- <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
              <property name="authenticationStrategy">
                  多个Reaml认证中有一个认证成功即成功策略
                  <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
                  第一个Reaml认证策略 <bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"></bean> 
                      必须所有Reaml都成功认证策略 <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean>
              </property>
          </bean> -->
      
          <!-- 4.配置lifecycleBeanPostProcessor,可以自动的调用Spring IOC 容器中shiro bean的生命周期方法 -->
          <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
      
          <!-- 5.启用ICO 容器中使用Shiro 注解,但必须在配置了lifecycleBeanPostProcessor之后方可使用 -->
          <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>
      
      
          <!-- 6.配置ShiroFilter 6.1 id必须和web.xml中的DelegatingFilterProxy的 FilterName一致 -->
          <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
              <property name="securityManager" ref="securityManager" />
              <property name="loginUrl" value="/login/toLogin" />
              <property name="successUrl" value="/example/index" />
              <property name="unauthorizedUrl" value="/example/unauthorized" />
              <property name="filterChainDefinitions">
                  <value>
                      /login/toLogin = anon
                      /login/loginVal = anon
                      /login/logout = logout
                      /** = authc
                  </value>
              </property>
          </bean>

一个Shiro例子

以上配置好以后我们将做一个Shiro 认证的登录例子。

  • 新建Form表单,本例子中的login.jsp

    <%@ page language="java" contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
    <%
        String path = request.getContextPath();
        String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
                + path;
    %>
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Insert title here</title>
    </head>
    <body>
    
    
    
        <form action="<%=basePath %>/login/loginVal" method="POST">
            username: <input type="text" name="username" /> <br> <br>
            password: <input type="password" name="password" /> <br> <br>
            <input type="submit" value="Submit" />
    
        </form>
    
    </body>
    </html>
  • 实现Controller方法

    package com.shiro.example.controller;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.subject.Subject;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    @Controller
    @RequestMapping(value = "/login")
    public class LoginProcess {
    
        @RequestMapping("/loginVal")
        public String login(@RequestParam("username") String username, @RequestParam("password") String password)
                throws Exception {
            System.out.println("username: " + username + "+ password: " + password);
            Subject subject = SecurityUtils.getSubject();
            // 如果当前用户并未经过认证登录的
            /*if (!subject.isAuthenticated()) {*/
                UsernamePasswordToken token = new UsernamePasswordToken(username, password);
                 token.setRememberMe(true);
                try {
    
                    subject.login(token);
    
                } catch (Exception e) {
                    System.out.println("登录失败: " + e.getMessage());
                    return "erro";
                }
            /*}
            System.out.println("用户尚未被认证");*/
            return "index";
        }
    
        @RequestMapping("/toLogin")
        public String toLogin() throws Exception {
            return "login";
        }
    
    }
    
  • 实现自定义Realm方法

    package com.shiro.example.interceptor.realm;
    
    import java.util.HashSet;
    import java.util.Set;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.LockedAccountException;
    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.crypto.hash.SimpleHash;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    
    import com.shiro.example.entity.SubjectEntity;
    
    public class ShiroRealm extends AuthorizingRealm {
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            System.out.println("[FirstRealm] doGetAuthenticationInfo");
    
            // 1. 把 AuthenticationToken 转换为 UsernamePasswordToken
            UsernamePasswordToken upToken = (UsernamePasswordToken) token;
    
            // 2. 从 UsernamePasswordToken 中来获取 username
            String username = upToken.getUsername();
    
            // 3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
            SubjectEntity principals = new SubjectEntity("123456", "Dustyone");
    
            // 4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
            if ("unknown".equals(username)) {
                throw new UnknownAccountException("用户不存在!");
            }
    
            // 5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常.
            if ("monster".equals(username)) {
                throw new LockedAccountException("用户被锁定");
            }
    
            // 6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为:
            // SimpleAuthenticationInfo
            // 以下信息是从数据库中获取的.
            // 1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象.
            Object principal = principals.getUsername();
            // 2). credentials: 密码.
            Object credentials = principals.getPassword(); // "fc1709d0a95a6be30bc5926fdb7f22f4";
    
    
            // 3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
            String realmName = getName();
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
    
    
            /*
             * //3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可 String
             * realmName = getName(); //4). 盐值. ByteSource credentialsSalt =
             * ByteSource.Util.bytes(username);
             * 
             * SimpleAuthenticationInfo info = null; //new
             * SimpleAuthenticationInfo(principal, credentials, realmName); info =
             * new SimpleAuthenticationInfo(principal, credentials, credentialsSalt,
             * realmName);
             */
            return info;
        }
  • 新建Subject Entity

    package com.shiro.example.entity;
    
    import java.io.Serializable;
    
    public class SubjectEntity implements Serializable{
    
        /**
         * Subject Entity
         */
        private static final long serialVersionUID = 1L;
        public SubjectEntity() {
            super();
        }
        @Override
        public String toString() {
            return "SubjectEntity [username=" + username + ", password=" + password + "]";
        }
        public SubjectEntity(String password, String username) {
            super();
            this.username = username;
            this.password = password;
        }
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        private String username;
        private String password;
    }
    
  • Example 文件

    ```
    package com.shiro.example.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("/example")
    public class Example {
    
        @RequestMapping(value = "/index")
        public String example() throws Exception {
            return "example";
        }
    
        @RequestMapping(value="/unauthorized") 
        public String unauthorized(){
            return "unauthorized";
        }
    }
    
    ```
    

本例子涉及的核心功能点

  • Shiro 认证(登录验证)

    • 由我们自定义的Realm代码中可知我们指定登录的Subject的usernam=Dustyone password=123456。唯有此principal(实体)组合才能通过Shiro认证(暂时不涉及加密)。
    • 正确登录
      这里写图片描述

      这里写图片描述

    • 错误principal登录

      这里写图片描述

      这里写图片描述

  • Shiro 授权

    • 在shiroFilter的 filterChainDefinitions我们做了访问权限声明即除了 /login/toLogin/login/loginVal 可以被匿名访问意外其他的任何url请都将被 shiro filetr收集并进行Subject 授权(即用户是否有权限访问**, 此处的两个星号标识资源。)。以 /example/index请求为例,在用户未登录的情况下访问该路径将直接跳转至登录页面。只有在登录成功的情况下才能访问 /example/index

    • 未登录的情况下访问 /example/index 将直接跳至登录页面
      这里写图片描述

    • 已登录的情况下访问 /example/index 直接可以访问该资源
      这里写图片描述

      这里写图片描述

  • Shiro 缓存
    用户登录成功之后(特质在启用了shiro的缓存机制之后),shiro 缓存将对最近一个Subject的认证或授权信息写入缓存。当该Subject 再次访问需要认证或授权的资源时,shiro将从缓存中(本例子中特指ehcache.xml)读取Subject的相关信息而不再此对该Subject进行认证或授权处理(这里的认证或授权特指我们自定义的Reaml中的 doGetAuthenticationInfo 和 doGetAuthorizationInfo 两个方法)。

  • Shiro 登出
    在访问logout 之后shiro将清除所有缓存信息并跳至登录页面。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值