SpringBoot整合Shiro权限管理实战教学

1.Shiro简简简简介

Apache Shiro™是一个功能强大且易于使用的Java安全框架,它执行身份验证,授权,加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的Web和企业应用程序。

可以看到Shiro的主要体系结构: 

1、 Authentication 认证 —- 用户登录 

2、 Authorization 授权 — 用户具有哪些权限

3、 Cryptography 安全数据加密

4、 Session Management 会话管理

5、 Web Integration web系统集成 

6、 Interations 集成其它应用,spring、缓存框架

下面我主要讲前两个,认证和授权的用法。

2.建立SpringBoot应用

声明:项目使用了SpringBoot、mybatis plus、RBAC的5张表(注意:我这里的系统默认一个用户只有一个角色)关于这几部门不明白的,请自行百度。

2.1项目引入的maven依赖有:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.2.1.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.yunqing</groupId>
  12. <artifactId>questionnaire</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>questionnaire</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. </properties>
  19. <dependencies>
  20. <!--springboot项目web相关依赖-->
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-web</artifactId>
  24. </dependency>
  25. <!--mybatis plus相关依赖-->
  26. <dependency>
  27. <groupId>com.baomidou</groupId>
  28. <artifactId>mybatis-plus-boot-starter</artifactId>
  29. <version>3.2.0</version>
  30. </dependency>
  31. <dependency>
  32. <groupId>com.baomidou</groupId>
  33. <artifactId>mybatis-plus-generator</artifactId>
  34. <version>3.2.0</version>
  35. </dependency>
  36. <dependency>
  37. <groupId>org.freemarker</groupId>
  38. <artifactId>freemarker</artifactId>
  39. <version>2.3.29</version>
  40. </dependency>
  41. <!--前端h5使用springboot默认的thymeleaf模板引擎-->
  42. <dependency>
  43. <groupId>org.springframework.boot</groupId>
  44. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  45. </dependency>
  46. <!--shiro核心依赖-->
  47. <dependency>
  48. <groupId>org.apache.shiro</groupId>
  49. <artifactId>shiro-spring</artifactId>
  50. <version>1.4.1</version>
  51. </dependency>
  52. <!-- thymeleaf对shiro的扩展依赖,为了前端使用shiro标签 -->
  53. <dependency>
  54. <groupId>com.github.theborakompanioni</groupId>
  55. <artifactId>thymeleaf-extras-shiro</artifactId>
  56. <version>2.0.0</version>
  57. </dependency>
  58. <!--mysql驱动默认8.0-->
  59. <dependency>
  60. <groupId>mysql</groupId>
  61. <artifactId>mysql-connector-java</artifactId>
  62. </dependency>
  63. <!--项目热部署依赖-->
  64. <dependency>
  65. <groupId>org.springframework.boot</groupId>
  66. <artifactId>spring-boot-devtools</artifactId>
  67. <scope>runtime</scope>
  68. <optional>true</optional>
  69. </dependency>
  70. <!--lombok插件-->
  71. <dependency>
  72. <groupId>org.projectlombok</groupId>
  73. <artifactId>lombok</artifactId>
  74. <optional>true</optional>
  75. </dependency>
  76. <dependency>
  77. <groupId>org.springframework.boot</groupId>
  78. <artifactId>spring-boot-starter-test</artifactId>
  79. <scope>test</scope>
  80. <exclusions>
  81. <exclusion>
  82. <groupId>org.junit.vintage</groupId>
  83. <artifactId>junit-vintage-engine</artifactId>
  84. </exclusion>
  85. </exclusions>
  86. </dependency>
  87. </dependencies>
  88. <build>
  89. <plugins>
  90. <plugin>
  91. <groupId>org.springframework.boot</groupId>
  92. <artifactId>spring-boot-maven-plugin</artifactId>
  93. </plugin>
  94. </plugins>
  95. </build>
  96. </project>

2.2创建LoginController首先令/user/index访问到主页面

  1. package com.yunqing.questionnaire.controller;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. /**
  6. * Created by yunqing on 2019/11/20 23:33
  7. */
  8. @Controller
  9. @RequestMapping("/user")
  10. public class LoginController {
  11. @GetMapping("/index")
  12. public String index() {
  13. return "index";
  14. }
  15. }

springboot默认h5页面放在templates中  这样访问/user/index就能访问到上图的index.html页面了。

3.SpringBoot整合Shiro实现用户认证

3.1 分析Shiro的核心Api

Subject:用户主体(把操作交给SecurityManager)

SecurityManager:安全管理器(关联Realm)

Realm:Shiro连接数据的桥梁

3.2 SpringBoot整合Shiro

3.2.1自定义realm类

  1. /**
  2. * 自定义Realm
  3. * Created by yunqing on 2019/11/21 21:43
  4. */
  5. @Slf4j
  6. public class UserRealm extends AuthorizingRealm {
  7. /**
  8. * 执行授权逻辑
  9. * @param principalCollection
  10. * @return
  11. */
  12. @Override
  13. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
  14. log.info("执行授权逻辑");
  15. }
  16. /**
  17. * 执行认证逻辑
  18. * @param authenticationToken
  19. * @return
  20. * @throws AuthenticationException
  21. */
  22. @Override
  23. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
  24. log.info("执行认证逻辑");
  25. }
  26. }

3.2.2编写Shiro配置类ShiroConfig

  1. /**
  2. * Created by yunqing on 2019/11/21 21:39
  3. */
  4. @Configuration
  5. public class ShiroConfig {
  6. /**
  7. * 创建ShiroFilterFactoryBean
  8. */
  9. @Bean
  10. public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
  11. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  12. return shiroFilterFactoryBean;
  13. }
  14. /**
  15. * 创建DefaultWebSecurityManager
  16. */
  17. @Bean(name = "securityManager")
  18. public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
  19. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  20. //关联realm
  21. securityManager.setRealm(userRealm);
  22. return securityManager;
  23. }
  24. /**
  25. * 创建Realm
  26. */
  27. @Bean(name = "userRealm")
  28. public UserRealm getRealm() {
  29. return new UserRealm();
  30. }
  31. }

3.3使用Shiro内置过滤器实现页面拦截

  1. /**
  2. * Created by yunqing on 2019/11/21 21:39
  3. */
  4. @Configuration
  5. public class ShiroConfig {
  6. /**
  7. * 创建ShiroFilterFactoryBean
  8. */
  9. @Bean
  10. public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
  11. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  12. //设置安全管理器
  13. shiroFilterFactoryBean.setSecurityManager(securityManager);
  14. /**
  15. * 添加shiro内置过滤器
  16. * 常用的过滤器
  17. * anon:无需认证登录就可以访问
  18. * authc:必须认证才能访问
  19. * user:如果使用remeberMe的功能可以直接访问
  20. * perms:该资源必须得到资源权限才能访问
  21. * role:该资源必须得到角色权限才能访问
  22. */
  23. Map<String, String> filterMap = new LinkedHashMap<>();
  24. filterMap.put("/user/index", "anon");//设置/user/index不需要认证
  25. filterMap.put("/user/add", "authc");//设置/user/add需要认证
  26. //filterMap.put("/user/*", "authc");
  27. shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
  28. //Shiro默认需要认证跳转login.jsp,这里更改通过controller跳转到login.html
  29. shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
  30. //shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuth");
  31. return shiroFilterFactoryBean;
  32. }
  33. /**
  34. * 创建DefaultWebSecurityManager
  35. */
  36. @Bean(name = "securityManager")
  37. public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
  38. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  39. //关联realm
  40. securityManager.setRealm(userRealm);
  41. return securityManager;
  42. }
  43. /**
  44. * 创建Realm
  45. */
  46. @Bean(name = "userRealm")
  47. public UserRealm getRealm() {
  48. return new UserRealm();
  49. }
  50. }

上面的代码运行后,首先localhost:8080/user/index进入index.html我们设置了不拦截,index.html页面如下

  1. <body>
  2. <a href="/user/add">跳转添加页面</a>
  3. <a href="/user/update">跳转修改页面</a>
  4. </body>

这时候的LoginController页面也加入了这几个跳转的方法

  1. @GetMapping("/index")
  2. public String index() {
  3. return "index";
  4. }
  5. @GetMapping("/add")
  6. public String add() {
  7. return "add";
  8. }
  9. @GetMapping("/update")
  10. public String update() {
  11. return "update";
  12. }
  13. @GetMapping("/toLogin")
  14. public String toLogin() {
  15. return "login";
  16. }

如上面设置/user/add需要认证,/user/update没设置认证

  

3.4实现用户认证(登录)操作

3.4.1 登录页面login.html

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. 登录页面login
  9. <h3 th:text="${msg}" style="color: red"></h3>
  10. <form action="/user/login">
  11. 用户名:<input type="text" name="username"/><br/>
  12. 密码:<input type="password" name="password"/><br/>
  13. <input type="submit" value="登录">
  14. </form>
  15. </body>
  16. </html>

3.4.2 编写LoginController中的登录逻辑

  1. @GetMapping("/login")
  2. public String login(String username, String password, Model model) {
  3. System.out.println(username);
  4. /**
  5. * 1.获取Subject
  6. */
  7. Subject subject = SecurityUtils.getSubject();
  8. /**
  9. * 把用户名,密码封装进UsernamePasswordToken
  10. */
  11. UsernamePasswordToken token = new UsernamePasswordToken(username, password);
  12. try{
  13. /**
  14. * subject携带token调用login,不出异常则登陆成功
  15. * 具体登录去ShiroConfig的认证里去决定
  16. */
  17. subject.login(token);
  18. //登陆成功
  19. return "redirect:index";//重定向到index页面
  20. } catch (IncorrectCredentialsException e) {
  21. //e.printStackTrace();
  22. //登录失败:密码错误
  23. model.addAttribute("msg", "密码错误");
  24. return "login";
  25. } catch (UnknownAccountException e) {
  26. //e.printStackTrace();
  27. //登录失败:用户名不存在
  28. model.addAttribute("msg", "用户名不存在");
  29. return "login";
  30. }
  31. }

3.4.3编写自定义realm的认证逻辑,去数据库查询用户名密码

  1. /**
  2. * 执行认证逻辑
  3. * @param authenticationToken
  4. * @return
  5. * @throws AuthenticationException
  6. */
  7. @Override
  8. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
  9. log.info("执行认证逻辑");
  10. //通过authenticationToken获取当前登录用户信息
  11. UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
  12. //mybatis plus 去数据库根据登录名查询User
  13. QueryWrapper<User> wrapper = new QueryWrapper<>();
  14. wrapper.eq("login_name", token.getUsername());
  15. User user = userService.getOne(wrapper);
  16. //判断是否查询到
  17. if (StringUtils.isEmpty(user)) {
  18. return null; //shiro内部会抛出UnknownAccountException异常
  19. }
  20. //判断密码,如果认证成功,则第一个参数是传给授权逻辑的,相当于授权逻辑中的Principal()
  21. return new SimpleAuthenticationInfo(user, user.getPassWord, "");
  22. }

登录成功后重定向到index.html再次点击跳转添加页面,成功

测试一下登录过程中用户名不存在,和密码错得情况,看看利用Model返回的msg,login.html页面的thymeleaf标签

是否接收到msg的值。

 

4.实现用户授权

4.1 使用shiro内置过滤器拦截资源

  1. /**
  2. * 创建ShiroFilterFactoryBean
  3. */
  4. @Bean
  5. public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
  6. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  7. //设置安全管理器
  8. shiroFilterFactoryBean.setSecurityManager(securityManager);
  9. /**
  10. * 添加shiro内置过滤器
  11. * 常用的过滤器
  12. * anon:无需认证登录就可以访问
  13. * authc:必须认证才能访问
  14. * user:如果使用remeberMe的功能可以直接访问
  15. * perms:该资源必须得到资源权限才能访问
  16. * role:该资源必须得到角色权限才能访问
  17. */
  18. Map<String, String> filterMap = new LinkedHashMap<>();
  19. filterMap.put("/user/index", "anon");//路径/user/index不需要认证
  20. filterMap.put("/user/login", "anon");//路径/user/login不需要认证
  21. filterMap.put("/user/add", "perms[user:add]");//路径/user/add不仅需要登录,还需要登录的角色拥有访问授权字符串为user:add资源的权利
  22. //filterMap.put("/user/update", "perms[user:update]");
  23. filterMap.put("/user/*", "authc");//拦截/user/*的路径需要进行认证,不需要认证的写在上面了
  24. shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
  25. shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
  26. shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuth");//登录后如果没有访问权限,设置跳转noAuth.html页面
  27. return shiroFilterFactoryBean;
  28. }

4.2 完成资源的授权,修改UserRealm类

  1. @Autowired
  2. private UserService userService;
  3. @Autowired
  4. private UserRoleService userRoleService;
  5. @Autowired
  6. private RoleResourceService roleResourceService;
  7. @Autowired
  8. private ResourceService resourceService;
  9. /**
  10. * 执行授权逻辑
  11. * @param principalCollection
  12. * @return
  13. */
  14. @Override
  15. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
  16. log.info("执行授权逻辑");
  17. /**
  18. * 给user:add进行授权
  19. */
  20. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
  21. //SecurityUtils.getSubject() 获取当前user
  22. //info.addStringPermission("user:add");
  23. //Subject subject = SecurityUtils.getSubject();
  24. User user = (User) principalCollection.getPrimaryPrincipal();//获取当前user
  25. QueryWrapper<UserRole> wrapper = new QueryWrapper<>();
  26. wrapper.eq("user_id", user.getId());
  27. UserRole userRole = userRoleService.getOne(wrapper);//获取角色id 当前系统默认一个用户对应一个角色。
  28. if (StringUtils.isEmpty(userRole)) return null;
  29. String roleId = userRole.getRoleId();
  30. QueryWrapper<RoleResource> queryWrapper = new QueryWrapper<>();
  31. queryWrapper.eq("role_id", roleId);
  32. List<RoleResource> list = roleResourceService.list(queryWrapper);//查出角色对应的资源id
  33. List<String> strs = new ArrayList<>();
  34. list.forEach(e->strs.add(e.getResourceId()));
  35. Set<String> flags = new HashSet<>();
  36. strs.forEach(e->{
  37. String flag = resourceService.getById(e).getFlag();
  38. if (flag!=null && !"".equals(flag))
  39. flags.add(flag);
  40. });
  41. info.addStringPermissions(flags);//授权当前角色可以访问的资源
  42. return info;
  43. }

5.设置页面只显示当前角色能访问的资源thymeleaf整合shiro

5.1 配置ShiroDialect,在ShiroConfig中配置

  1. /**
  2. * 使前端shiro-thymeleaf生效
  3. * @return
  4. */
  5. @Bean
  6. public ShiroDialect shiroDialect(){
  7. return new ShiroDialect();
  8. }

5.2 在页面上使用shiro标签,index.html页面

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:shiro="http://www.w3.org/1999/xhtml">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div shiro:hasPermission="user:add">
  9. <a href="/user/add">跳转添加页面</a>
  10. </div>
  11. <div shiro:hasPermission="user:update">
  12. <a href="/user/update">跳转修改页面</a>
  13. </div>
  14. </body>
  15. </html>

注意,因为加入了shiro授权标签,所以最初访问/user/index就不会显示内容,因为没有被授权,这时候,我们可以直接通过/user/toLogin访问login页面进行登录,如下所示:数据库中admin配置了添加user:add资源授权,admin111配置了修改user:update资源授权。

  

未经允许不得转载:大自然的搬运工 » SpringBoot整合Shiro权限管理实战教学

赞 (0)

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址