作者

项目背景

       公司在几年前就采用了前后端分离的开发模式,前端所有请求都使用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代码如下

<?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">

<modelVersion>4.0.0</modelVersion>

<groupId>com.xxx</groupId>

<artifactId>spring-boot-parent</artifactId>

<packaging>pom</packaging>

<version>1.0-SNAPSHOT</version>

<modules>

<module>spring-boot-main</module>

<module>spring-boot-module1</module>

<module>spring-boot-shiro</module>

<module>spring-boot-common</module>

</modules>


<properties>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<java.version>1.8</java.version>

<spring-boot.version>1.5.9.RELEASE</spring-boot.version>

<shiro.version>1.4.0</shiro.version>

</properties>


<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

<version>${spring-boot.version}</version>

</dependency>

<!--在外部tomcat中发布故移除内置包-->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-tomcat</artifactId>

<version>${spring-boot.version}</version>

<scope>provided</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<version>${spring-boot.version}</version>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-devtools</artifactId>

<version>${spring-boot.version}</version>

<optional>true</optional>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-jpa</artifactId>

<version>${spring-boot.version}</version>

</dependency>

<dependency>

<groupId>org.apache.shiro</groupId>

<artifactId>shiro-spring</artifactId>

<version>${shiro.version}</version>

</dependency>

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>1.2.8</version>

</dependency>

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>druid</artifactId>

<version>1.0.28</version>

</dependency>

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>5.1.39</version>

<scope>runtime</scope>

</dependency>

<!--<dependency>-->

<!--<groupId>org.springframework.boot</groupId>-->

<!--<artifactId>spring-boot-starter-thymeleaf</artifactId>-->

<!--<version>${spring-boot.version}</version>-->

<!--</dependency>-->

<!--<dependency>-->

<!--<groupId>net.sourceforge.nekohtml</groupId>-->

<!--<artifactId>nekohtml</artifactId>-->

<!--<version>1.9.22</version>-->

<!--</dependency>-->

</dependencies>

</project>

 

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

      

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

 


 
  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类。      

 


 
  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的代码

 


 
  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层登录方法代码如下

       


 
  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层加入方法

       


 
  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数据源配置代码

       


 
  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代码

       


 
  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的全局异常捕获来处理异常,避免重复代码。该项目中代码如下


 
  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),从而实现权限控制

 

2.  《Spring Boot多数据源配置与使用

 

3.  《springboot整合shiro-登录认证和权限管理

 

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