spring-boot+shiro+spring-data-redis 实战

一,根据项目要求需要对spring-mvc+shiro的项目转换成spring-boot+shiro+spring-data-redis+redis集群的平台以便于实现shiro在spring-boot中安全认证+权限控制+全局sission管理

项目结构


pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>DC.API_WEB</artifactId>
        <groupId>com.hlink</groupId>
        <version>1.0.1</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hlink.web</groupId>
    <artifactId>DC.WEB_Emp</artifactId>

    <properties>
        <spring-version>5.0.5.RELEAS</spring-version>
        <shiro-version>1.4.0</shiro-version>
        <slf4j-version>1.7.3</slf4j-version>
        <kafka-version>2.11</kafka-version>
        <java-version>1.8</java-version>
        <junit-version>4.12</junit-version>
        <jackson.version>2.9.4</jackson.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.hlink</groupId>
            <artifactId>DC.API_Commons</artifactId>
            <version>1.0.0-compiler</version>
        </dependency>
        
        
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.22</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.0.8</version>
        </dependency>
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.0.8</version>
        </dependency>
       
        <dependency>
            <groupId>javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.11.0.GA</version>
        </dependency>
        <dependency>
            <groupId>com.sun.el</groupId>
            <artifactId>el-ri</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.4.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
            <version>2.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>2.4.2.1-RELEASE</version>
        </dependency>
        
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.2.RELEASE</version>
                <configuration>
                    <!-- 没有该配置,devtools 不生效 -->
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
        <finalName>web</finalName>
    </build>
</project>

spring-boot风格中spring-bean.xml中的数据源配置被省略,spring-mvc.xml中的前段控制器配置被省略,web.xml配置被省略,但是shiro的配置与spring-data-redis的配置仍需要自己配置,spring-boot推荐的方式是使用@Configuration 注解在该注解类中每个方法中使用@Bean注解的方式,大致参考:

  1. @Configuration  
  2. public class ShiroConfiguration {  
  3.     private final Logger log = LoggerFactory.getLogger(this.getClass());  
  4.   
  5.     @Bean  
  6.     public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {  
  7.         log.info("$--ShiroConfiguration.shirFilter()");  
  8.         ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();  
  9.         // 必须设置 SecurityManager  
  10.         shiroFilterFactoryBean.setSecurityManager(securityManager);  
  11.         //拦截器.  
  12.         Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();  
  13.         // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了  
  14.         filterChainDefinitionMap.put("/logout""logout");  
  15.         // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->;  
  16.         // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->  
  17.         filterChainDefinitionMap.put("/validatecodeServlet""anon");//验证码可以匿名访问  
  18.         filterChainDefinitionMap.put("/toPasswordReset""anon");//重置密码可以匿名访问  
  19.         filterChainDefinitionMap.put("/static/**""anon");  
  20.         filterChainDefinitionMap.put("/**""authc");//authc  
  21.         // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面  
  22.         shiroFilterFactoryBean.setLoginUrl("/login");  
  23.         // 登录成功后要跳转的链接  
  24.         shiroFilterFactoryBean.setSuccessUrl("/index");  
  25.         // 未授权界面;  
  26.         shiroFilterFactoryBean.setUnauthorizedUrl("/403");  
  27.         //自定义表达验证,校验验证码  
  28.         Map<String,Filter> filters = new HashMap<String, Filter>();  
  29.         filters.put("authc"new MyFormAuthenticationFilter());  
  30.         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);  
  31.         shiroFilterFactoryBean.setFilters(filters);  
  32.         return shiroFilterFactoryBean;  
  33.     }  
  34.   
  35.     @Bean  
  36.     public SecurityManager securityManager() {  
  37.         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();  
  38.         //设置realm.  
  39.         securityManager.setRealm(myShiroRealm());  
  40.         return securityManager;  
  41.     }  
  42.       
  43.     /**  
  44.      * 身份认证realm;  
  45.      * @return  
  46.      */  
  47.     @Bean  
  48.     public MyShiroRealm myShiroRealm(){  
  49.        MyShiroRealm myShiroRealm = new MyShiroRealm();  
  50.        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());  
  51.        return myShiroRealm;  
  52.     }  
  53.       
  54.     /** 
  55.      * 凭证匹配器 
  56.      * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 
  57.      *  所以我们需要修改下doGetAuthenticationInfo中的代码; 
  58.      * ) 
  59.      * @return 
  60.      */  
  61.     @Bean  
  62.     public HashedCredentialsMatcher hashedCredentialsMatcher(){  
  63.         HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();  
  64.           
  65.         hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;  
  66.         hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列2次,相当于 md5(md5(""));  
  67.           
  68.         return hashedCredentialsMatcher;  
  69.     }  
  70.       
  71.     @Bean    
  72.     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {    
  73.         AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();    
  74.         advisor.setSecurityManager(securityManager());    
  75.         return advisor;    
  76.     }    
  77. }  

该配置类实际相当于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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <description>Shiro的配置</description>

    <import resource="classpath:spring_shiro_redis.xml"/>

    <aop:aspectj-autoproxy/>

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- Single realm app. If you have multiple realms, use the 'realms' property
            instead. -->
        <property name="realm" ref="authRealm"/><!-- 引用自定义的realm -->
        <!--将缓存管理器,交给安全管理器 -->
        <property name="cacheManager" ref="shiroSpringCacheManager"/>


    </bean>


    <!-- 自定义权限认证 -->
    <bean id="authRealm" class="com.hlink.DC.shiro.CustomRealm">

        <!-- 此处注入书库操作对象,根据用户名查询密码 <property name="userService" ref="null" /> -->
        <!-- 自定义密码加密算法 -->
        <property name="credentialsMatcher" ref="passwordMatcher"/>

        <property name="cacheManager" ref="shiroSpringCacheManager"/>
        <!-- 打开缓存 -->
        <property name="cachingEnabled" value="true"/>
        <!-- 打开身份认证缓存 -->
        <property name="authenticationCachingEnabled" value="true"/>
        <!-- 打开授权缓存 -->
        <property name="authorizationCachingEnabled" value="true"/>
        <!-- 缓存AuthenticationInfo信息的缓存名称 -->
        <property name="authenticationCacheName" value="authenticationCache"/>
        <!-- 缓存AuthorizationInfo信息的缓存名称 -->
        <property name="authorizationCacheName" value="authorizationCache"/>

    </bean>

    <!-- 配置自定义缓存管理器,中引入redis缓存管理器 -->
    <bean id="shiroSpringCacheManager" class="com.hlink.DC.shiro.ShiroSpringCacheManager">
        <property name="redisTemplate" ref="redisTemplate"></property>
    </bean>

    <!-- 密碼比較器 -->
    <bean id="passwordMatcher" class="com.hlink.DC.shiro.CustomCredentialsMatcher">
        <property name="cacheManager" ref="redisCacheManager"/>
        <property name="expire_minute" value="30"/>
        <property name="passwordHash" ref="passwordHash"/>

    </bean>
    <!-- 加密器 -->
    <bean id="passwordHash" class="com.hlink.DC.shiro.Encrypt">
        <!-- 使用MD5 -->
        <property name="hashAlgorithm" value="md5"/>
        <!-- 加密5-->
        <property name="hashIterations" value="5"/>
    </bean>

    <!-- 记住密码Cookie -->
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!-- cookie的名字 -->
        <constructor-arg value="rememberMe"/>
        <property name="httpOnly" value="true"/>
        <!-- 7, -->
        <property name="maxAge" value="604800"/>
    </bean>

    <!-- sesisonCookie 设置 -->
    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!-- cookie的名字 -->
        <constructor-arg value="sessionIdCookie"/>
        <property name="httpOnly" value="true"/>
        <!-- 30分钟 单位是秒 -->
        <property name="maxAge" value="1800"/>
    </bean>

    <!-- rememberMe管理器,cipherKey生成见{@code Base64Test.java} -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cipherKey"
                  value="#{T(org.apache.shiro.codec.Base64).decode('5aaC5qKm5oqA5pyvAAAAAA==')}"/>
        <property name="cookie" ref="rememberMeCookie"/>
    </bean>
    <!-- 会话管理器 -->
    <bean id="sessionManager"
          class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- 设置全局会话超时时间 半小时 单位是毫秒 -->
        <property name="globalSessionTimeout" value="1800000"/>
        <!-- url上带sessionId 默认为true -->
        <property name="sessionIdUrlRewritingEnabled" value="true"/>
        <property name="sessionIdCookie" ref="sessionIdCookie"/>
        <property name="sessionDAO" ref="sessionDAO"/>
    </bean>

    <!-- 会话DAO 用于会话的CRUD -->
    <bean id="sessionDAO"
          class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
        <!-- Session缓存名字,配置30分钟过期 -->
        <property name="activeSessionsCacheName" value="activeSessionCache"/>
        <property name="cacheManager" ref="shiroSpringCacheManager"/>
    </bean>

    <!-- Shiro Filter -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 安全管理器 -->
        <property name="securityManager" ref="securityManager"/>
        <!-- 默认的登陆访问url -->
        <property name="loginUrl" value="/login.jsp"/>
        <!-- 登陆成功后跳转的url -->
        <property name="successUrl" value="/index.jsp"/>
        <!-- 没有权限跳转的url -->
        <property name="unauthorizedUrl" value="/unauth.jsp"/>
        <property name="filterChainDefinitions">
            <value>
                <!-- anon 不需要认证 authc 需要认证 user 验证通过或RememberMe登录的都可以 -->
                /store/admin/session = anon
                /store/admin/logout = anon
                /store/admin/login = anon
                /web/validationCode/** = anon
                /store/admin= anon
                /** = anon
            </value>
        </property>
    </bean>

    <!-- 静态注入,相当于调用SecurityUtils.setSecurityManager(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"/>

    <bean id="exceptionHandler" class="com.hlink.DC.shiro.GlobalExceptionHandler" />

</beans>


shiro的缓存中本人使用spring-data-redis集成Redis集群来做,具体配置:

spring-shiro-redis.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:c="http://www.springframework.org/schema/c"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
   <description>spring-redis-cache配置文件</description>

   <!-- 加载配置属性文件 按需加载 -->
   <!-- <context:property-placeholder ignore-unresolvable="true" location="classpath:redis.cluster.properties" 
      /> -->
   <!-- 配置Cluster -->
   <bean id="redisClusterConfiguration"
      class="org.springframework.data.redis.connection.RedisClusterConfiguration">
      <property name="maxRedirects" value="3"></property>
      <!-- 节点配置 -->
      <property name="clusterNodes">
         <set>
            <bean class="org.springframework.data.redis.connection.RedisClusterNode">
               <constructor-arg name="host" value="192.168.50.241"></constructor-arg>
               <constructor-arg name="port" value="6000"></constructor-arg>
            </bean>
            <bean class="org.springframework.data.redis.connection.RedisClusterNode">
               <constructor-arg name="host" value="192.168.50.241"></constructor-arg>
               <constructor-arg name="port" value="6001"></constructor-arg>
            </bean>
            <bean class="org.springframework.data.redis.connection.RedisClusterNode">
               <constructor-arg name="host" value="192.168.50.241"></constructor-arg>
               <constructor-arg name="port" value="6002"></constructor-arg>
            </bean>
            <bean class="org.springframework.data.redis.connection.RedisClusterNode">
               <constructor-arg name="host" value="192.168.50.239 "></constructor-arg>
               <constructor-arg name="port" value="7000"></constructor-arg>
            </bean>
            <bean class="org.springframework.data.redis.connection.RedisClusterNode">
               <constructor-arg name="host" value="192.168.50.239"></constructor-arg>
               <constructor-arg name="port" value="7001"></constructor-arg>
            </bean>
            <bean class="org.springframework.data.redis.connection.RedisClusterNode">
               <constructor-arg name="host" value="192.168.50.239"></constructor-arg>
               <constructor-arg name="port" value="7002"></constructor-arg>
            </bean>
         </set>
      </property>
   </bean>

   <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
      <!--最大空闲连接数 -->
      <property name="maxIdle" value="100" />
      <property name="maxTotal" value="600" />
      <!--初始化连接数 -->
      <property name="minIdle" value="20" />
      <!--对拿到的connection进行validateObject校验 -->
      <property name="testOnBorrow" value="true" />
      <!--在进行returnObject对返回的connection进行validateObject校验 -->
      <property name="testOnReturn" value="true" />
      <!--定时对线程池中空闲的链接进行validateObject校验 -->
      <property name="testWhileIdle" value="true" />
   </bean>

   <bean id="jeidsConnectionFactory"
      class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
      <constructor-arg ref="redisClusterConfiguration" />
      <constructor-arg ref="jedisPoolConfig" />
      <property name="password" value="hlink"></property>
   </bean>


   <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
      <property name="connectionFactory" ref="jeidsConnectionFactory" />
      <!-- 开启事务 -->
      <property name="enableTransactionSupport" value="true" />
      <!-- 序列化策略 推荐使用StringRedisSerializer -->
      <!-- <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> 
         </property> -->
      <!-- <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> 
         </property> -->
      <!--<property name="hashKeySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> 
         </property> <property name="hashValueSerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> 
         </property> -->
      <property name="keySerializer">
         <bean
            class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"></bean>
      </property>
      <property name="valueSerializer">
         <bean
            class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
      </property>
      <property name="hashKeySerializer">
         <bean
            class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
      </property>
      <property name="hashValueSerializer">
         <bean
            class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
      </property>
   </bean>


<bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
        factory-method="create" c:connection-factory-ref="jeidsConnectionFactory" />
</beans>


shiro中的几个关键组成部分: 加密器+自定义密码比较器+全局异常拦截处理器+shiro缓存管理器,这几部分在上述的shiro集成配置中必须存在,如下:

加密器:

package com.hlink.DC.shiro;


import org.apache.shiro.crypto.hash.Md5Hash;

public class Encrypt {
   private int hashIterations = 2;
   private String hashAlgorithm;

   public int getHashIterations() {
      return hashIterations;
   }

   public void setHashIterations(int hashIterations) {
      this.hashIterations = hashIterations;
   }

   public String getHashAlgorithm() {
      return hashAlgorithm;
   }

   public void setHashAlgorithm(String hashAlgorithm) {
      this.hashAlgorithm = hashAlgorithm;
   }
   /*
    * 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,
    * 常见的散列算法如MD5SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”
    * 产生的散列值是“21232f297a57a5a743894a0e4a801fc3”, 可以到一些md5解密网站很容易的通过散列值得到密码“admin”
    * 即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,
    * 如用户名和ID(即盐);这样散列的对象是密码+用户名+ID”,这样生成的散列值相对来说更难破解。
    */

   // 高强度加密算法,不可逆

   public String md5(String password, String salt) {
      return new Md5Hash(password, salt, hashIterations).toString();
   }
   public String sha(String password, String salt) {
      return null;
   }
   public String hmac(String password, String salt) {
      return null;
   }
   public String encrypt(String password, String salt) {

      if (hashAlgorithm != null && hashAlgorithm.equals("md5") || hashAlgorithm.equals("MD5")) {
         return md5(password, salt);
      } /* else if(hashAlgorithm.equals("sha") || hashAlgorithm.equals("SHA")){
         return null;
      } else if(hashAlgorithm.equals("hmac") || hashAlgorithm.equals("HMAC")){
         return null;
      }*/ else{
         return null;
      }

   }

}

自定义密码比较器:

package com.hlink.DC.shiro;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;

public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
   private org.springframework.cache.CacheManager cacheManager;
   private int expire_minute = 30;
   private Encrypt passwordHash;

   public Encrypt getPasswordHash() {
      return passwordHash;
   }

   public void setPasswordHash(Encrypt passwordHash) {
      this.passwordHash = passwordHash;
   }

   public int getExpire_minute() {
      return expire_minute;
   }

   public void setExpire_minute(int expire_minute) {
      this.expire_minute = expire_minute;
   }

   public org.springframework.cache.CacheManager getCacheManager() {
      return cacheManager;
   }

   public void setCacheManager(org.springframework.cache.CacheManager cacheManager) {
      this.cacheManager = cacheManager;
   }

   public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
      UsernamePasswordToken usertoken = (UsernamePasswordToken) token;
      // 注意token.getPassword()拿到的是一个char[],不能直接用toString(),它底层实现不是我们想的直接字符串,只能强转
      Object tokenCredentials = passwordHash.encrypt(String.valueOf(usertoken.getPassword()),
            usertoken.getUsername());
      Object accountCredentials = getCredentials(info);
      // 将密码加密与系统加密后的密码校验,内容一致就返回true,不一致就返回false
      System.out.println("用户输入密码:"+tokenCredentials);
      System.out.println("系統保存密碼: "+getCredentials(info));
      return equals(tokenCredentials, accountCredentials);
   }

}


全局异常拦截:

package com.hlink.DC.shiro;

import net.sf.json.JSONObject;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@ControllerAdvice
public class GlobalExceptionHandler implements HandlerExceptionResolver {


    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        JSONObject js = new JSONObject();

        if (e instanceof UnknownAccountException) {
            js.put("code", "4006");
            js.put("msg", "用户名不存在·");
        } else if (e instanceof IncorrectCredentialsException) {
            js.put("code", "4006");
            js.put("msg", "用户名或密码错误");
        } else if (e instanceof NullPointerException) {
            js.put("code", "4006");
            js.put("msg", "系統空指針異常");
        } else if (e instanceof ClassCastException) {
            js.put("code", "4006");
            js.put("msg", "系统类型转换异常");
        }  else{
            js.put("code", "404");
            js.put("msg", e.getMessage());
        }
        ModelAndView mv = new ModelAndView();
        mv.addObject(js);
        return mv;
    }

    @ExceptionHandler(value = Exception.class)
    @ResponseBody// 返回json数据
    public JSONObject jsonErrorHandler(HttpServletRequest req, Exception e){
        JSONObject js = new JSONObject();

        if (e instanceof UnknownAccountException) {
            js.put("code", "4006");
            js.put("msg", "用户名不存在·");
        } else if (e instanceof IncorrectCredentialsException) {
            js.put("code", "4006");
            js.put("msg", "用户名密码错误");
        } else if (e instanceof NullPointerException) {
            js.put("code", "4006");
            js.put("msg", "空指針異常");
        } else if (e instanceof ClassCastException) {
            js.put("code", "4006");
            js.put("msg", "类型转换异常");
        }  else{
            js.put("code", "404");
            js.put("msg", e.getMessage());
        }

        return js;
    }
}

shiro缓存管理器:


package com.hlink.DC.shiro;

import com.hlink.DC_DB.model.User;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.Destroyable;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Collection;
import java.util.Set;

/**
 * <p>
 * 自定义cacheManage 扩张shiro里面的缓存 使用reids作缓存
 * </p>
 * <description> 引入自己定义的CacheManager 关于CacheManager的配置文件在spring-redis-cache.xml * </description>
 * 
 * @author xxxx
 * @date 201823 * @time 14:01:53
 */
public class ShiroSpringCacheManager implements CacheManager, Destroyable {
   private String cacheKeyPrefix = "shiro_login:";
   private RedisTemplate<String, Object> redisTemplate;

   public String getCacheKeyPrefix() {
      return cacheKeyPrefix;
   }

   public void setCacheKeyPrefix(String cacheKeyPrefix) {
      this.cacheKeyPrefix = cacheKeyPrefix;
   }

   public RedisTemplate<String, Object> getRedisTemplate() {
      return redisTemplate;
   }

   public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
      this.redisTemplate = redisTemplate;
   }

   @Override
   public <K, V> Cache<K, V> getCache(String name) throws CacheException {
      return new ShiroRedisCache<K, V>(cacheKeyPrefix + name);
   }

   @Override
   public void destroy() throws Exception {
      redisTemplate = null;

   }

   public class ShiroRedisCache<K, V> implements Cache<K, V> {

      private String cacheKey;

      public ShiroRedisCache(String cacheKey) {
         this.cacheKey = cacheKey;
      }

      @Override
      public V get(K key) throws CacheException {
         BoundHashOperations<String, K, V> hash = redisTemplate.boundHashOps(cacheKey);
         Object k = hashKey(key);
         return hash.get(k);
      }

      @SuppressWarnings("unchecked")
      @Override
      public V put(K key, V value) throws CacheException {
         BoundHashOperations<String, K, V> hash = redisTemplate.boundHashOps(cacheKey);
         Object k = hashKey(key);
         hash.put((K) k, value);
         return value;
      }

      @Override
      public V remove(K key) throws CacheException {
         BoundHashOperations<String, K, V> hash = redisTemplate.boundHashOps(cacheKey);

         Object k = hashKey(key);
         V value = hash.get(k);
         hash.delete(k);
         return value;
      }

      @Override
      public void clear() throws CacheException {
         redisTemplate.delete(cacheKey);
      }

      @Override
      public int size() {
         BoundHashOperations<String, K, V> hash = redisTemplate.boundHashOps(cacheKey);
         return hash.size().intValue();
      }

      @Override
      public Set<K> keys() {
         BoundHashOperations<String, K, V> hash = redisTemplate.boundHashOps(cacheKey);
         return hash.keys();
      }

      @Override
      public Collection<V> values() {
         BoundHashOperations<String, K, V> hash = redisTemplate.boundHashOps(cacheKey);
         return hash.values();
      }

      protected Object hashKey(K key) {

         if (key instanceof PrincipalCollection) {// 此处很重要,如果key是登录凭证,那么这是访问用户的授权缓存;将登录凭证转为user对象,返回userid属性做为hash
                                          // key,否则会以user对象做为hash key,这样就不好清除指定用户的缓存了
            PrincipalCollection pc = (PrincipalCollection) key;
            User user = (User) pc.getPrimaryPrincipal();
            return user.getId();
         }
         return key;
      }
   }

}

最后在贴一下我的启动类:

package com.hlink.DC;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableAutoConfiguration
@EnableTransactionManagement
@ComponentScan
@ImportResource(locations = {"classpath:applicationContext.xml", "classpath:spring_shiro_redis.xml", "classpath:spring-shiro.xml", "classpath:spring-dubbo.xml"})
public class DCApplication {
    public static void main(String[] args) {
        SpringApplication.run(DCApplication.class, args);
    }

}

spring-boot对市场上常见的框架并不是完全集成的,例如dubbo等,但它的配置加载却具有相当的灵活性,开发者仍可以在开始接触的过渡期间使用自己的配置文件(applicationContext.xml,spring-shiro.xml.spring-dubbo.xml等)只需要在启动类中引入加载即可使用,但切记springboot已经完美的兼容了springmvc,所以不要再多余配置spring-mvc.xml中的所有配置

后言:以上内容均属个人感悟,如有错误欢迎指正,谢谢!!


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值