2流高手速成记(之五):Springboot整合Shiro实现安全管理 ...

打印 上一主题 下一主题

主题 596|帖子 596|积分 1788

废话不多说,咱们直接接上回
上一篇我们讲了如何使用Springboot框架整合Nosql,并于文章最后部分引入了服务端Session的概念
而早在上上一篇中,我们则已经讲到了如何使用Springboot框架整合Mybatis/MybatisPlus实现业务数据的持久化(写入数据库)
本篇我们把关注点放在一个于这两部分有共同交集的内容——安全管理,并且引入我们今天的主角——Shiro框架
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
—— 来自百度百科
Shiro框架包含三个核心组件:
Subject —— 泛指当前与Shiro交互中的实体,可以是用户或者某后台进程
SecurityManager —— Shiro的核心组件,对内管理各种组件实例,对外提供各种安全服务
Realm —— Shiro与安全数据之间的桥接器
Shiro框架还包含有其他诸多概念,为降低大家的心智负担,这些我们暂且不谈,文末会给大家推荐延展阅读的相关文章
还是老规矩直接上干货,以完整的实例让大家对【如何基于Shiro实现权限的细粒度控制】有一个整体上的认知
 
 
 
不知道大家会不会觉得项目结构突然变复杂?别担心,接下来我会给大家逐一拆解
1. 创建数据表

首先是角色表——role

 

 
 
 
 然后是用户表——user

 

 
 
 最后是权限表——permission


 

 2. 创建三个对应的Mapper
  1. package com.example.hellospringboot.mapper;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import com.example.hellospringboot.model.Role;
  4. import org.apache.ibatis.annotations.Mapper;
  5. import org.springframework.stereotype.Repository;
  6. @Mapper
  7. @Repository
  8. public interface RoleMapper extends BaseMapper<Role> {
  9. }
复制代码
  1. package com.example.hellospringboot.mapper;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import com.example.hellospringboot.model.User;
  4. import org.apache.ibatis.annotations.Mapper;
  5. import org.springframework.stereotype.Repository;
  6. @Mapper
  7. @Repository
  8. public interface UserMapper extends BaseMapper<User> {
  9. }
复制代码
  1. package com.example.hellospringboot.mapper;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import com.example.hellospringboot.model.Permission;
  4. import org.apache.ibatis.annotations.Mapper;
  5. import org.springframework.stereotype.Repository;
  6. @Mapper
  7. @Repository
  8. public interface PermissionMapper extends BaseMapper<Permission> {
  9. }
复制代码
这里我们用到了上上一节讲到的内容
这里的Mapper会辅助于后续的安全数据读取
3. 接下来是Service及其实现类
  1. package com.example.hellospringboot.service;
  2. import com.example.hellospringboot.model.Role;
  3. public interface RoleService {
  4.     Role findRoleById(int id);
  5. }
复制代码
  1. package com.example.hellospringboot.service.impl;
  2. import com.example.hellospringboot.mapper.RoleMapper;
  3. import com.example.hellospringboot.model.Role;
  4. import com.example.hellospringboot.service.RoleService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.stereotype.Service;
  7. @Service
  8. public class RoleServiceImpl implements RoleService {
  9.     @Autowired
  10.     RoleMapper mapper;
  11.     public Role findRoleById(int id){
  12.         Role role = mapper.selectById(id);
  13.         return role;
  14.     }
  15. }
复制代码
  1. package com.example.hellospringboot.service;
  2. import com.example.hellospringboot.model.User;
  3. public interface UserService {
  4.     boolean checkUserByUsernameAndPassword(String userName, String passWord);
  5.     User findUserByUserName(String userName);
  6. }
复制代码
  1. package com.example.hellospringboot.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.example.hellospringboot.mapper.UserMapper;
  4. import com.example.hellospringboot.model.User;
  5. import com.example.hellospringboot.service.UserService;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Service;
  8. import java.util.List;
  9. @Service
  10. public class UserServiceImpl implements UserService {
  11.     @Autowired
  12.     UserMapper mapper;
  13.     public boolean checkUserByUsernameAndPassword(String userName, String passWord){
  14.         QueryWrapper<User> wrapper = new QueryWrapper<User>();
  15.         wrapper = wrapper.eq("user_name", userName).eq("pass_word",passWord);
  16.         List<User> userList = mapper.selectList(wrapper);
  17.         return userList.size() > 0;
  18.     }
  19.     public User findUserByUserName(String userName){
  20.         QueryWrapper<User> wrapper = new QueryWrapper<User>();
  21.         wrapper = wrapper.eq("user_name", userName);
  22.         User user = mapper.selectOne(wrapper);
  23.         return user;
  24.     }
  25. }
复制代码
  1. package com.example.hellospringboot.service;
  2. import com.example.hellospringboot.model.Permission;
  3. import java.util.List;
  4. public interface PermissionService {
  5.     List<Permission> findPermissionsByRoleId(int roleId);
  6. }
复制代码
  1. package com.example.hellospringboot.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.Wrapper;
  3. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  4. import com.example.hellospringboot.mapper.PermissionMapper;
  5. import com.example.hellospringboot.model.Permission;
  6. import com.example.hellospringboot.service.PermissionService;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.stereotype.Service;
  9. import java.util.List;
  10. @Service
  11. public class PermissionServiceImpl implements PermissionService {
  12.     @Autowired
  13.     PermissionMapper mapper;
  14.     public List<Permission> findPermissionsByRoleId(int roleId){
  15.         QueryWrapper<Permission> wrapper = new QueryWrapper<>();
  16.         wrapper = wrapper.eq("role_id", roleId);
  17.         List<Permission> list = mapper.selectList(wrapper);
  18.         return list;
  19.     }
  20. }
复制代码
ok,我们已经准备好了所有的安全数据,及对应的读取方法
到这里,我们就算是做好了所有的准备工作
接下来看我们如何通过Shiro框架来运用这些已经装配好的枪炮子弹
4. 引入Shiro框架相关依赖(pom.xml)
  1.         
  2.         <dependency>
  3.             <groupId>org.apache.shiro</groupId>
  4.             <artifactId>shiro-spring</artifactId>
  5.             <version>1.10.0</version>
  6.         </dependency>
复制代码
这次pom.xml终于不是第一步了,哈哈哈。。。
5. 创建Realm嫁接Shiro框架及安全数据(realm/MyAuthorizingRealm)
  1. package com.example.hellospringboot.realm;
  2. import com.example.hellospringboot.model.Permission;
  3. import com.example.hellospringboot.model.Role;
  4. import com.example.hellospringboot.model.User;
  5. import com.example.hellospringboot.service.PermissionService;
  6. import com.example.hellospringboot.service.RoleService;
  7. import com.example.hellospringboot.service.UserService;
  8. import org.apache.shiro.authc.*;
  9. import org.apache.shiro.authz.AuthorizationInfo;
  10. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  11. import org.apache.shiro.realm.AuthorizingRealm;
  12. import org.apache.shiro.subject.PrincipalCollection;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import java.util.HashSet;
  15. import java.util.List;
  16. import java.util.Set;
  17. public class MyAuthorizingRealm extends AuthorizingRealm {
  18.     @Autowired
  19.     UserService userService;
  20.     @Autowired
  21.     RoleService roleService;
  22.     @Autowired
  23.     PermissionService permissionService;
  24.     @Override
  25.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
  26.         UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
  27.         String userName = token.getUsername();
  28.         String passWord = String.valueOf(token.getPassword());
  29.         if (!userService.checkUserByUsernameAndPassword(userName, passWord)) {//判断用户账号是否正确
  30.             throw new UnknownAccountException("用户名或密码错误!");
  31.         }
  32.         return new SimpleAuthenticationInfo(userName, passWord, getName());
  33.     }
  34.     @Override
  35.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
  36.         SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
  37.         String userName = principalCollection.getPrimaryPrincipal().toString();
  38.         User user = userService.findUserByUserName(userName);
  39.         if (user == null) {
  40.             throw new UnknownAccountException("用户名或密码错误!");
  41.         }
  42.         List<Integer> rolesList = user.rolesList();
  43.         Set<String> roles = new HashSet<>();
  44.         Set<String> permissions = new HashSet<>();
  45.         for (Integer roleId : rolesList) {
  46.             Role role = roleService.findRoleById(roleId);
  47.             roles.add(role.getName());
  48.             List<Permission> permissionList = permissionService.findPermissionsByRoleId(roleId);
  49.             for (Permission permission : permissionList) {
  50.                 permissions.add(permission.getName());
  51.             }
  52.         }
  53.         info.setRoles(roles);
  54.         info.setStringPermissions(permissions);
  55.         return info;
  56.     }
  57. }
复制代码
Realm的创建对于整个Shiro安全验证体系搭建而言是至关重要的一步!
其中两个抽象方法
doGetAuthenticationInfo —— 用于校验用户名及密码的合法性
doGetAuthorizationInfo —— 用于赋予实体对应的角色及交互权限
6. 测试用Controller创建
  1. package com.example.hellospringboot.controller;
  2. import org.apache.shiro.SecurityUtils;
  3. import org.apache.shiro.authc.AuthenticationException;
  4. import org.apache.shiro.authc.UsernamePasswordToken;
  5. import org.apache.shiro.subject.Subject;
  6. import org.springframework.web.bind.annotation.GetMapping;
  7. import org.springframework.web.bind.annotation.PostMapping;
  8. import org.springframework.web.bind.annotation.RequestMapping;
  9. import org.springframework.web.bind.annotation.RestController;
  10. @RequestMapping("/user")
  11. @RestController
  12. public class UserController {
  13.     @PostMapping("/login")
  14.     public String login(String user, String pass) {
  15.         UsernamePasswordToken token = new UsernamePasswordToken(user, pass);
  16.         Subject subject = SecurityUtils.getSubject();
  17.         if(!subject.isAuthenticated()) {
  18.             try {
  19.                 subject.login(token);
  20.             } catch (AuthenticationException e) {
  21.                 return e.getMessage();
  22.             }
  23.         }
  24.         return "ok";
  25.     }
  26.     @PostMapping("/logout")
  27.     public String logout(){
  28.         Subject subject = SecurityUtils.getSubject();
  29.         if(subject.isAuthenticated()) {
  30.             try {
  31.                 subject.logout();
  32.             } catch (AuthenticationException e) {
  33.                 return e.getMessage();
  34.             }
  35.         }
  36.         return "ok";
  37.     }
  38.     @GetMapping("/admin")
  39.     public String admin() {
  40.         return "admin";
  41.     }
  42.     @GetMapping("/user")
  43.     public String user() {
  44.         return "user";
  45.     }
  46. }
复制代码
内容很简单:
login——登录方法
logout——登出方法
admin、user——两个测试方法,用于测试不同角色对于不同方法可访问的细粒度控制
7. ShiroConfig配置类创建,实现用户访问权限的细粒度控制

  1. package com.example.hellospringboot.configure;
  2. import com.example.hellospringboot.realm.MyAuthorizingRealm;
  3. import org.apache.shiro.mgt.SecurityManager;
  4. import org.apache.shiro.realm.Realm;
  5. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  6. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import java.util.LinkedHashMap;
  10. import java.util.Map;
  11. @Configuration
  12. public class ShiroConfig {
  13.     @Bean
  14.     public SecurityManager securityManager(Realm realm) {
  15.         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  16.         securityManager.setRealm(realm);
  17.         return securityManager;
  18.     }
  19.     @Bean
  20.     public MyAuthorizingRealm getRealm() {
  21.         MyAuthorizingRealm realm = new MyAuthorizingRealm();
  22.         return realm;
  23.     }
  24.     @Bean
  25.     public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
  26.         ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
  27.         shiroFilter.setSecurityManager(securityManager);
  28.         Map<String, String> filterChainMap = new LinkedHashMap<String, String>();
  29.         filterChainMap.put("/user/login", "anon");
  30.         filterChainMap.put("/user/logout", "anon");
  31.         filterChainMap.put("/user/admin", "authc,roles[admin],perms[admin:read]");
  32.         filterChainMap.put("/user/user", "authc,roles[user],perms[user:read]");
  33.         shiroFilter.setFilterChainDefinitionMap(filterChainMap);
  34.         return shiroFilter;
  35.     }
  36. }
复制代码
 
securityManager 和 getRealm 显示指定了Shiro两大组件的实例声明
shiroFilterFactoryBean 则是实现角色访问权限控制的重要方法
        filterChainMap.put("/user/login", "anon"); // 代表login方法可以匿名访问
        filterChainMap.put("/user/logout", "anon"); // 代表logout方法可以匿名访问
        filterChainMap.put("/user/admin", "authc,roles[admin],perms[admin:read]"); // 代表admin方法需要用户满足admin角色,同时具备admin:read权限
        filterChainMap.put("/user/user", "authc,roles[user],perms[user:read]"); // 代表user方法需要用户满足user角色,同时具备user:read权限
至此,整个接入流程便结束了
我们再次结合最开始我们配置的数据来对业务逻辑进行分析
用户 admin,同时具备admin、user两种角色
用户 juste,仅具备user一种角色
角色 admin,同时具备admin:write、admin:read两种权限
角色 user,同时具备user:write、user:read两种权限
因此
用户 admin,同时具备admin:write、admin:read、user:write、user:read 四种操作权限
用户 juste,同时具备user:write、user:read两种操作权限
大家理清楚这其中的关系了吗?^ ^
8. 执行Postman验证结果


 
 我们在执行login之前,admin方法无权访问

 
 
 
 
 
 登录admin之后,同时具备admin和user方法的访问权限



 
logout登出,然后login登录普通用户juste
会发现依然具备user方法的访问权限,但是失去了admin方法的访问权限
到此,验证我们基于Shiro框架的细粒度权限控制已经实现
除了Shiro框架,我们还有另一个选择,那就是同样可以通过集成Spring Security框架来达成相同的目的
关于更多Shiro框架的内容,及其和Spring Security之间的异同,大家感兴趣可以参考这篇文章:
Shiro最全基础教程_思月行云的博客-CSDN博客
对于Spring Security框架,我们暂且留个悬念,以后会专门再给大家讲解这部分内容
下一节,我们将把关注点投向微服务领域,SpringCloudAlibaba将会是接下来几个章节的重头戏,敬请期待~
 
 
 
  1. MyAuthorizingRealm
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

梦见你的名字

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表