在前后端分离的SpringBoot项目中集成Shiro权限框架

项目背景

       公司在几年前就采用了前后端分离的开发模式,前端所有请求都使用ajax。这样的项目结构在与CAS单点登录等权限管理框架集成时遇到了很多问题,使得权限部分的代码冗长丑陋,CAS的各种重定向也使得用户体验很差,在前端使用vue-router管理页面跳转时,问题更加尖锐。于是我就在寻找一个解决方案,这个方案应该对代码的侵入较少,开发速度快,实现优雅。最近无意中看到springboot与shiro框架集成的文章,在了解了springboot以及shiro的发展状况,并学习了使用方法后,开始在网上搜索前后端分离模式下这两个框架的适应性,在经过测试后发现可行,完全符合个人预期。

解决方案

       本文中项目核心包为SpringBoot1.5.9.RELEASE以及shiro-spring 1.4.0,为了加快开发效率,持久化框架使用hibernate-JPA,为增加可靠性,sessionId的管理使用了shiro-redis开源插件,避免sessionId断电丢失,同时使得多端可共享session,项目结构为多模块项目,详见下图。

       其中spring-boot-shiro模块为本文重点,该模块包含shiro核心配置,shiro数据源配置以及各种自定义实现,登录相关服务等。该模块在项目中使用时可直接在pom中引用,并在spring-boot-main入口模块中配置相应数据库连接信息即可,且该模块可以在多个项目中复用,避免重复开发。spring-boot-module1为模拟真实项目中的业务模块,可能会有多个。spring-boot-common中包含通用工具类,常量,异常等等。多模块项目的搭建在本文中不作赘述。

       母模块pom.xml代码如下

[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"  
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
  5.     <modelVersion>4.0.0</modelVersion>  
  6.     <groupId>com.xxx</groupId>  
  7.     <artifactId>spring-boot-parent</artifactId>  
  8.     <packaging>pom</packaging>  
  9.     <version>1.0-SNAPSHOT</version>  
  10.     <modules>  
  11.         <module>spring-boot-main</module>  
  12.         <module>spring-boot-module1</module>  
  13.         <module>spring-boot-shiro</module>  
  14.         <module>spring-boot-common</module>  
  15.     </modules>  
  16.   
  17.     <properties>  
  18.         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
  19.         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>  
  20.         <java.version>1.8</java.version>  
  21.         <spring-boot.version>1.5.9.RELEASE</spring-boot.version>  
  22.         <shiro.version>1.4.0</shiro.version>  
  23.     </properties>  
  24.   
  25.     <dependencies>  
  26.         <dependency>  
  27.             <groupId>org.springframework.boot</groupId>  
  28.             <artifactId>spring-boot-starter-web</artifactId>  
  29.             <version>${spring-boot.version}</version>  
  30.         </dependency>  
  31.         <!--在外部tomcat中发布故移除内置包-->  
  32.         <dependency>  
  33.             <groupId>org.springframework.boot</groupId>  
  34.             <artifactId>spring-boot-starter-tomcat</artifactId>  
  35.             <version>${spring-boot.version}</version>  
  36.             <scope>provided</scope>  
  37.         </dependency>  
  38.         <dependency>  
  39.             <groupId>org.springframework.boot</groupId>  
  40.             <artifactId>spring-boot-starter-test</artifactId>  
  41.             <version>${spring-boot.version}</version>  
  42.             <scope>test</scope>  
  43.         </dependency>  
  44.         <dependency>  
  45.             <groupId>org.springframework.boot</groupId>  
  46.             <artifactId>spring-boot-devtools</artifactId>  
  47.             <version>${spring-boot.version}</version>  
  48.             <optional>true</optional>  
  49.         </dependency>  
  50.         <dependency>  
  51.             <groupId>org.springframework.boot</groupId>  
  52.             <artifactId>spring-boot-starter-data-jpa</artifactId>  
  53.             <version>${spring-boot.version}</version>  
  54.         </dependency>  
  55.         <dependency>  
  56.             <groupId>org.apache.shiro</groupId>  
  57.             <artifactId>shiro-spring</artifactId>  
  58.             <version>${shiro.version}</version>  
  59.         </dependency>  
  60.         <dependency>  
  61.             <groupId>com.alibaba</groupId>  
  62.             <artifactId>fastjson</artifactId>  
  63.             <version>1.2.8</version>  
  64.         </dependency>  
  65.         <dependency>  
  66.             <groupId>com.alibaba</groupId>  
  67.             <artifactId>druid</artifactId>  
  68.             <version>1.0.28</version>  
  69.         </dependency>  
  70.         <dependency>  
  71.             <groupId>mysql</groupId>  
  72.             <artifactId>mysql-connector-java</artifactId>  
  73.             <version>5.1.39</version>  
  74.             <scope>runtime</scope>  
  75.         </dependency>  
  76.         <!--<dependency>-->  
  77.             <!--<groupId>org.springframework.boot</groupId>-->  
  78.             <!--<artifactId>spring-boot-starter-thymeleaf</artifactId>-->  
  79.             <!--<version>${spring-boot.version}</version>-->  
  80.         <!--</dependency>-->  
  81.         <!--<dependency>-->  
  82.             <!--<groupId>net.sourceforge.nekohtml</groupId>-->  
  83.             <!--<artifactId>nekohtml</artifactId>-->  
  84.             <!--<version>1.9.22</version>-->  
  85.         <!--</dependency>-->  
  86.     </dependencies>  
  87. </project>  

       spring-boot-shiro模块接口如下图

      

       传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,在前后端分离的项目中(也可在移动APP项目使用),我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法,代码如下    

[java]  view plain  copy
  1. import org.apache.shiro.web.servlet.ShiroHttpServletRequest;  
  2. import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;  
  3. import org.apache.shiro.web.util.WebUtils;  
  4. import org.springframework.util.StringUtils;  
  5. import javax.servlet.ServletRequest;  
  6. import javax.servlet.ServletResponse;  
  7. import java.io.Serializable;  
  8.   
  9. /** 
  10.  * Created by Administrator on 2017/12/11. 
  11.  * 自定义sessionId获取 
  12.  */  
  13. public class MySessionManager extends DefaultWebSessionManager {  
  14.   
  15.     private static final String AUTHORIZATION = "Authorization";  
  16.   
  17.     private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";  
  18.   
  19.     public MySessionManager() {  
  20.         super();  
  21.     }  
  22.   
  23.     @Override  
  24.     protected Serializable getSessionId(ServletRequest request, ServletResponse response) {  
  25.         String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);  
  26.         //如果请求头中有 Authorization 则其值为sessionId  
  27.         if (!StringUtils.isEmpty(id)) {  
  28.             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);  
  29.             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);  
  30.             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);  
  31.             return id;  
  32.         } else {  
  33.             //否则按默认规则从cookie取sessionId  
  34.             return super.getSessionId(request, response);  
  35.         }  
  36.     }  
  37. }  

       如何配置让shiro执行我们的自定义sessionManager呢?下面看ShiroConfig类。      

[java]  view plain  copy
  1. package com.xxx.shiro.config;  
  2.   
  3. import org.apache.shiro.authc.credential.HashedCredentialsMatcher;  
  4. import org.apache.shiro.mgt.SecurityManager;  
  5. import org.apache.shiro.session.mgt.SessionManager;  
  6. import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;  
  7. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;  
  8. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;  
  9. import org.crazycake.shiro.RedisCacheManager;  
  10. import org.crazycake.shiro.RedisManager;  
  11. import org.crazycake.shiro.RedisSessionDAO;  
  12. import org.springframework.beans.factory.annotation.Value;  
  13. import org.springframework.context.annotation.Bean;  
  14. import org.springframework.context.annotation.Configuration;  
  15. import org.springframework.web.servlet.HandlerExceptionResolver;  
  16. import java.util.LinkedHashMap;  
  17. import java.util.Map;  
  18.   
  19. /** 
  20.  * Created by Administrator on 2017/12/11. 
  21.  */  
  22. @Configuration  
  23. public class ShiroConfig {  
  24.   
  25.     @Value("${spring.redis.shiro.host}")  
  26.     private String host;  
  27.     @Value("${spring.redis.shiro.port}")  
  28.     private int port;  
  29.     @Value("${spring.redis.shiro.timeout}")  
  30.     private int timeout;  
  31.     @Value("${spring.redis.shiro.password}")  
  32.     private String password;  
  33.   
  34.     @Bean  
  35.     public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {  
  36.         System.out.println("ShiroConfiguration.shirFilter()");  
  37.         ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();  
  38.         shiroFilterFactoryBean.setSecurityManager(securityManager);  
  39.   
  40.         Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();  
  41.         //注意过滤器配置顺序 不能颠倒  
  42.         //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl  
  43.         filterChainDefinitionMap.put("/logout""logout");  
  44.         // 配置不会被拦截的链接 顺序判断  
  45.         filterChainDefinitionMap.put("/static/**""anon");  
  46.         filterChainDefinitionMap.put("/ajaxLogin""anon");  
  47.         filterChainDefinitionMap.put("/login""anon");  
  48.         filterChainDefinitionMap.put("/**""authc");  
  49.         //配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据  
  50.         shiroFilterFactoryBean.setLoginUrl("/unauth");  
  51.         // 登录成功后要跳转的链接  
  52. //        shiroFilterFactoryBean.setSuccessUrl("/index");  
  53.         //未授权界面;  
  54. //        shiroFilterFactoryBean.setUnauthorizedUrl("/403");  
  55.         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);  
  56.         return shiroFilterFactoryBean;  
  57.     }  
  58.   
  59.     /**  
  60.      * 凭证匹配器  
  61.      * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了  
  62.      * )  
  63.      *  
  64.      * @return  
  65.      */  
  66.     @Bean  
  67.     public HashedCredentialsMatcher hashedCredentialsMatcher() {  
  68.         HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();  
  69.         hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;  
  70.         hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));  
  71.         return hashedCredentialsMatcher;  
  72.     }  
  73.   
  74.     @Bean  
  75.     public MyShiroRealm myShiroRealm() {  
  76.         MyShiroRealm myShiroRealm = new MyShiroRealm();  
  77.         myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());  
  78.         return myShiroRealm;  
  79.     }  
  80.   
  81.   
  82.     @Bean  
  83.     public SecurityManager securityManager() {  
  84.         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();  
  85.         securityManager.setRealm(myShiroRealm());  
  86.         // 自定义session管理 使用redis  
  87.         securityManager.setSessionManager(sessionManager());  
  88.         // 自定义缓存实现 使用redis  
  89.         securityManager.setCacheManager(cacheManager());  
  90.         return securityManager;  
  91.     }  
  92.   
  93.     //自定义sessionManager  
  94.     @Bean  
  95.     public SessionManager sessionManager() {  
  96.         MySessionManager mySessionManager = new MySessionManager();  
  97.         mySessionManager.setSessionDAO(redisSessionDAO());  
  98.         return mySessionManager;  
  99.     }  
  100.   
  101.     /** 
  102.      * 配置shiro redisManager 
  103.      * <p> 
  104.      * 使用的是shiro-redis开源插件 
  105.      * 
  106.      * @return 
  107.      */  
  108.     public RedisManager redisManager() {  
  109.         RedisManager redisManager = new RedisManager();  
  110.         redisManager.setHost(host);  
  111.         redisManager.setPort(port);  
  112.         redisManager.setExpire(1800);// 配置缓存过期时间  
  113.         redisManager.setTimeout(timeout);  
  114.         redisManager.setPassword(password);  
  115.         return redisManager;  
  116.     }  
  117.   
  118.     /** 
  119.      * cacheManager 缓存 redis实现 
  120.      * <p> 
  121.      * 使用的是shiro-redis开源插件 
  122.      * 
  123.      * @return 
  124.      */  
  125.     @Bean  
  126.     public RedisCacheManager cacheManager() {  
  127.         RedisCacheManager redisCacheManager = new RedisCacheManager();  
  128.         redisCacheManager.setRedisManager(redisManager());  
  129.         return redisCacheManager;  
  130.     }  
  131.   
  132.     /** 
  133.      * RedisSessionDAO shiro sessionDao层的实现 通过redis 
  134.      * <p> 
  135.      * 使用的是shiro-redis开源插件 
  136.      */  
  137.     @Bean  
  138.     public RedisSessionDAO redisSessionDAO() {  
  139.         RedisSessionDAO redisSessionDAO = new RedisSessionDAO();  
  140.         redisSessionDAO.setRedisManager(redisManager());  
  141.         return redisSessionDAO;  
  142.     }  
  143.   
  144.     /** 
  145.      * 开启shiro aop注解支持. 
  146.      * 使用代理方式;所以需要开启代码支持; 
  147.      * 
  148.      * @param securityManager 
  149.      * @return 
  150.      */  
  151.     @Bean  
  152.     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {  
  153.         AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();  
  154.         authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);  
  155.         return authorizationAttributeSourceAdvisor;  
  156.     }  
  157.   
  158.     /** 
  159.      * 注册全局异常处理 
  160.      * @return 
  161.      */  
  162.     @Bean(name = "exceptionHandler")  
  163.     public HandlerExceptionResolver handlerExceptionResolver() {  
  164.         return new MyExceptionHandler();  
  165.     }  
  166. }  

       在定义的SessionManager的Bean中返回我们的MySessionManager,然后在SecurityManager的Bean中调用setSessionManager(SessionManager sessionManager)方法加载我们的自定义SessionManager。

 附上
MyShiroRealm的代码

[java]  view plain  copy
  1. package com.xxx.shiro.config;  
  2.   
  3. import com.xxx.shiro.entity.SysPermission;  
  4. import com.xxx.shiro.entity.SysRole;  
  5. import com.xxx.shiro.entity.UserInfo;  
  6. import com.xxx.shiro.service.UserInfoService;  
  7. import org.apache.shiro.authc.*;  
  8. import org.apache.shiro.authz.AuthorizationInfo;  
  9. import org.apache.shiro.authz.SimpleAuthorizationInfo;  
  10. import org.apache.shiro.realm.AuthorizingRealm;  
  11. import org.apache.shiro.subject.PrincipalCollection;  
  12. import org.apache.shiro.util.ByteSource;  
  13.   
  14. import javax.annotation.Resource;  
  15.   
  16. /** 
  17.  * Created by Administrator on 2017/12/11. 
  18.  * 自定义权限匹配和账号密码匹配 
  19.  */  
  20. public class MyShiroRealm extends AuthorizingRealm {  
  21.     @Resource  
  22.     private UserInfoService userInfoService;  
  23.   
  24.     @Override  
  25.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
  26. //        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");  
  27.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
  28.         UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();  
  29.         for (SysRole role : userInfo.getRoleList()) {  
  30.             authorizationInfo.addRole(role.getRole());  
  31.             for (SysPermission p : role.getPermissions()) {  
  32.                 authorizationInfo.addStringPermission(p.getPermission());  
  33.             }  
  34.         }  
  35.         return authorizationInfo;  
  36.     }  
  37.   
  38.     /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/  
  39.     @Override  
  40.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)  
  41.             throws AuthenticationException {  
  42. //        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");  
  43.         //获取用户的输入的账号.  
  44.         String username = (String) token.getPrincipal();  
  45. //        System.out.println(token.getCredentials());  
  46.         //通过username从数据库中查找 User对象,如果找到,没找到.  
  47.         //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法  
  48.         UserInfo userInfo = userInfoService.findByUsername(username);  
  49. //        System.out.println("----->>userInfo="+userInfo);  
  50.         if (userInfo == null) {  
  51.             return null;  
  52.         }  
  53.         if (userInfo.getState() == 1) { //账户冻结  
  54.             throw new LockedAccountException();  
  55.         }  
  56.         SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(  
  57.                 userInfo, //用户名  
  58.                 userInfo.getPassword(), //密码  
  59.                 ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt  
  60.                 getName()  //realm name  
  61.         );  
  62.         return authenticationInfo;  
  63.     }  
  64.   
  65. }  

       传统项目中,登录成功后应该重定向请求,但在前后端分离项目中,通过ajax登录后应该返回登录状态标志以及相关信息。Web层登录方法代码如下

       

[java]  view plain  copy
  1. /** 
  2.  * 登录方法 
  3.  * @param userInfo 
  4.  * @return 
  5.  */  
  6. @RequestMapping(value = "/ajaxLogin", method = RequestMethod.POST)  
  7. @ResponseBody  
  8. public String ajaxLogin(UserInfo userInfo) {  
  9.     JSONObject jsonObject = new JSONObject();  
  10.     Subject subject = SecurityUtils.getSubject();  
  11.     UsernamePasswordToken token = new UsernamePasswordToken(userInfo.getUsername(), userInfo.getPassword());  
  12.     try {  
  13.         subject.login(token);  
  14.         jsonObject.put("token", subject.getSession().getId());  
  15.         jsonObject.put("msg""登录成功");  
  16.     } catch (IncorrectCredentialsException e) {  
  17.         jsonObject.put("msg""密码错误");  
  18.     } catch (LockedAccountException e) {  
  19.         jsonObject.put("msg""登录失败,该用户已被冻结");  
  20.     } catch (AuthenticationException e) {  
  21.         jsonObject.put("msg""该用户不存在");  
  22.     } catch (Exception e) {  
  23.         e.printStackTrace();  
  24.     }  
  25.     return jsonObject.toString();  
  26. }  

       本项目使用SpringMVC框架,可以自行修改使用其他MVC框架。登录成功则返回sessionId作为token给前端存储,前端请求时将该token放入请求头,以Authorization为key,以此来鉴权。如果出现账号或密码错误等异常则返回错误信息。

       传统项目中,登出后应重定向请求,到登录界面或其他指定界面,在前后端分离的项目中,我们应该返回json信息。在上面提到的ShiroConfig中配置了默认登录路由

      

       在Web层加入方法

       

[java]  view plain  copy
  1. /** 
  2.      * 未登录,shiro应重定向到登录界面,此处返回未登录状态信息由前端控制跳转页面 
  3.      * @return 
  4.      */  
  5.     @RequestMapping(value = "/unauth")  
  6.     @ResponseBody  
  7.     public Object unauth() {  
  8.         Map<String, Object> map = new HashMap<String, Object>();  
  9.         map.put("code""1000000");  
  10.         map.put("msg""未登录");  
  11.         return map;  
  12.     }  

       此处简单提示未登录返回状态码,也可自行定义信息。

       在项目中,权限相关表可能不在业务库中,因此有必要单独配置权限相关表的数据源。详细配置可以参见《Spring Boot多数据源配置与使用》一文。

       Shiro数据源配置代码

       

[java]  view plain  copy
  1. package com.xxx.shiro.datasource;  
  2.   
  3. import java.util.Map;  
  4. import javax.persistence.EntityManager;  
  5. import javax.sql.DataSource;  
  6. import org.springframework.beans.factory.annotation.Autowired;  
  7. import org.springframework.beans.factory.annotation.Qualifier;  
  8. import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;  
  9. import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;  
  10. import org.springframework.context.annotation.Bean;  
  11. import org.springframework.context.annotation.Configuration;  
  12. import org.springframework.data.jpa.repository.config.EnableJpaRepositories;  
  13. import org.springframework.orm.jpa.JpaTransactionManager;  
  14. import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;  
  15. import org.springframework.transaction.PlatformTransactionManager;  
  16. import org.springframework.transaction.annotation.EnableTransactionManagement;  
  17.   
  18. /** 
  19.  * Created by Administrator on 2017/12/11. 
  20.  */  
  21. @Configuration  
  22. @EnableTransactionManagement  
  23. @EnableJpaRepositories(  
  24.         entityManagerFactoryRef="shiroEntityManagerFactory",  
  25.         transactionManagerRef="shiroTransactionManager",  
  26.         basePackages= { "com.xxx.shiro.dao" })  
  27. public class ShiroDataSourceConfig {  
  28.     @Autowired  
  29.     private JpaProperties jpaProperties;  
  30.   
  31.     @Autowired  
  32.     @Qualifier("shiroDataSource")  
  33.     private DataSource shiroDataSource;  
  34.   
  35.     @Bean(name = "shiroEntityManager")  
  36.     public EntityManager shiroEntityManager(EntityManagerFactoryBuilder builder) {  
  37.         return shiroEntityManagerFactory(builder).getObject().createEntityManager();  
  38.     }  
  39.   
  40.     @Bean(name = "shiroEntityManagerFactory")  
  41.     public LocalContainerEntityManagerFactoryBean shiroEntityManagerFactory (EntityManagerFactoryBuilder builder) {  
  42.         return builder  
  43.                 .dataSource(shiroDataSource)  
  44.                 .properties(getVendorProperties(shiroDataSource))  
  45.                 .packages("com.xxx.shiro.entity")  
  46.                 .persistenceUnit("shiroPersistenceUnit")  
  47.                 .build();  
  48.     }  
  49.   
  50.     private Map<String, String> getVendorProperties(DataSource dataSource) {  
  51.         return jpaProperties.getHibernateProperties(dataSource);  
  52.     }  
  53.   
  54.     @Bean(name = "shiroTransactionManager")  
  55.     PlatformTransactionManager shiroTransactionManager(EntityManagerFactoryBuilder builder) {  
  56.         return new JpaTransactionManager(shiroEntityManagerFactory(builder).getObject());  
  57.     }  
  58. }  

       

       IDEA下JpaProperties可能会报错,可以忽略。

       入口模块结构如下图


       DataSourceConfig中配置了多个数据源的Bean,其中shiro数据源Bean代码

       

[java]  view plain  copy
  1. /** 
  2.  * shiro数据源 
  3.  * @return 
  4.  */  
  5. @Bean(name = "shiroDataSource")  
  6. @Qualifier("shiroDataSource")  
  7. @ConfigurationProperties(prefix="spring.datasource.shiro")  
  8. public DataSource shiroDataSource() {  
  9.     return DataSourceBuilder.create().build();  
  10. }  

       ServletInitializer和StartApp为SpringBoot在外部tomcat启动配置,不赘述。

       SpringBoot的相关配置在application.yml中,shiro配置代码如下图

      

       Primary为主库配置。当在某个项目中引入spring-boot-shiro模块时,只需要在配置文件中加入shiro数据源及redis的相关配置,并在DataSourceConfig加入shiro数据源Bean即可。

       Shiro框架会根据用户登录及权限状态抛出异常,建议使用SpringMVC的全局异常捕获来处理异常,避免重复代码。该项目中代码如下

[java]  view plain  copy
  1. package com.xxx.shiro.config;  
  2.   
  3. import com.alibaba.fastjson.support.spring.FastJsonJsonView;  
  4. import org.apache.shiro.authz.UnauthenticatedException;  
  5. import org.apache.shiro.authz.UnauthorizedException;  
  6. import org.springframework.web.servlet.HandlerExceptionResolver;  
  7. import org.springframework.web.servlet.ModelAndView;  
  8. import javax.servlet.http.HttpServletRequest;  
  9. import javax.servlet.http.HttpServletResponse;  
  10. import java.util.HashMap;  
  11. import java.util.Map;  
  12.   
  13. /** 
  14.  * Created by Administrator on 2017/12/11. 
  15.  * 全局异常处理 
  16.  */  
  17. public class MyExceptionHandler implements HandlerExceptionResolver {  
  18.   
  19.     public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) {  
  20.         ModelAndView mv = new ModelAndView();  
  21.         FastJsonJsonView view = new FastJsonJsonView();  
  22.         Map<String, Object> attributes = new HashMap<String, Object>();  
  23.         if (ex instanceof UnauthenticatedException) {  
  24.             attributes.put("code""1000001");  
  25.             attributes.put("msg""token错误");  
  26.         } else if (ex instanceof UnauthorizedException) {  
  27.             attributes.put("code""1000002");  
  28.             attributes.put("msg""用户无权限");  
  29.         } else {  
  30.             attributes.put("code""1000003");  
  31.             attributes.put("msg", ex.getMessage());  
  32.         }  
  33.   
  34.         view.setAttributesMap(attributes);  
  35.         mv.setView(view);  
  36.         return mv;  
  37.     }  
  38. }  

      该Bean在ShiroConfig中已有注册代码。

       至此,shiro框架的集成就结束了。至于shiro框架的使用细节,可以自行查阅相关资料。项目代码本人测试可正常工作,未应用到生产环境,仅供学习交流使用。

参考文章

1.  《在前后端分离的项目中,后台使用shiro框架时,怎样使用它的会话管理系统(session),从而实现权限控制》http://blog.csdn.net/palerock/article/details/73457415

2.  《Spring Boot多数据源配置与使用》http://www.jianshu.com/p/34730e595a8c

3.  《springboot整合shiro-登录认证和权限管理》http://www.cnblogs.com/ityouknow/p/7089177.html


需要源码的朋友可以看看我的另一篇博文《SpringBoot+Shiro+MyBatisPlus搭建前后端分离的多模块项目

版权声明:本文为博主原创文章,未经博主允许不得转载。 http://blog.csdn.net/u013615903/article/details/78781166
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值