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


    废话不多说,咱们直接接上回

    上一篇我们讲了如何使用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

    复制代码
    package com.example.hellospringboot.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.example.hellospringboot.model.Role;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.stereotype.Repository;
    
    @Mapper
    @Repository
    public interface RoleMapper extends BaseMapper {
    }
    复制代码
    复制代码
    package com.example.hellospringboot.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.example.hellospringboot.model.User;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.stereotype.Repository;
    
    @Mapper
    @Repository
    public interface UserMapper extends BaseMapper {
    }
    复制代码
    复制代码
    package com.example.hellospringboot.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.example.hellospringboot.model.Permission;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.stereotype.Repository;
    
    @Mapper
    @Repository
    public interface PermissionMapper extends BaseMapper {
    }
    复制代码

    这里我们用到了上上一节讲到的内容

    这里的Mapper会辅助于后续的安全数据读取

    3. 接下来是Service及其实现类

    复制代码
    package com.example.hellospringboot.service;
    
    import com.example.hellospringboot.model.Role;
    
    public interface RoleService {
        Role findRoleById(int id);
    }
    复制代码
    复制代码
    package com.example.hellospringboot.service.impl;
    
    import com.example.hellospringboot.mapper.RoleMapper;
    import com.example.hellospringboot.model.Role;
    import com.example.hellospringboot.service.RoleService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class RoleServiceImpl implements RoleService {
    
        @Autowired
        RoleMapper mapper;
    
        public Role findRoleById(int id){
            Role role = mapper.selectById(id);
            return role;
        }
    }
    复制代码
    复制代码
    package com.example.hellospringboot.service;
    
    import com.example.hellospringboot.model.User;
    
    public interface UserService {
        boolean checkUserByUsernameAndPassword(String userName, String passWord);
        User findUserByUserName(String userName);
    }
    复制代码
    复制代码
    package com.example.hellospringboot.service.impl;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.example.hellospringboot.mapper.UserMapper;
    import com.example.hellospringboot.model.User;
    import com.example.hellospringboot.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        UserMapper mapper;
    
        public boolean checkUserByUsernameAndPassword(String userName, String passWord){
            QueryWrapper wrapper = new QueryWrapper();
            wrapper = wrapper.eq("user_name", userName).eq("pass_word",passWord);
            List userList = mapper.selectList(wrapper);
            return userList.size() > 0;
        }
    
        public User findUserByUserName(String userName){
            QueryWrapper wrapper = new QueryWrapper();
            wrapper = wrapper.eq("user_name", userName);
            User user = mapper.selectOne(wrapper);
            return user;
        }
    
    }
    复制代码
    复制代码
    package com.example.hellospringboot.service;
    
    import com.example.hellospringboot.model.Permission;
    
    import java.util.List;
    
    public interface PermissionService {
        List findPermissionsByRoleId(int roleId);
    }
    复制代码
    复制代码
    package com.example.hellospringboot.service.impl;
    
    import com.baomidou.mybatisplus.core.conditions.Wrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.example.hellospringboot.mapper.PermissionMapper;
    import com.example.hellospringboot.model.Permission;
    import com.example.hellospringboot.service.PermissionService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class PermissionServiceImpl implements PermissionService {
    
        @Autowired
        PermissionMapper mapper;
    
        public List findPermissionsByRoleId(int roleId){
            QueryWrapper wrapper = new QueryWrapper<>();
            wrapper = wrapper.eq("role_id", roleId);
            List list = mapper.selectList(wrapper);
            return list;
        }
    
    }
    复制代码

    ok,我们已经准备好了所有的安全数据,及对应的读取方法

    到这里,我们就算是做好了所有的准备工作

    接下来看我们如何通过Shiro框架来运用这些已经装配好的枪炮子弹

    4. 引入Shiro框架相关依赖(pom.xml)

    复制代码
            
            <dependency>
                <groupId>org.apache.shirogroupId>
                <artifactId>shiro-springartifactId>
                <version>1.10.0version>
            dependency>
    复制代码

    这次pom.xml终于不是第一步了,哈哈哈。。。

    5. 创建Realm嫁接Shiro框架及安全数据(realm/MyAuthorizingRealm)

    复制代码
    package com.example.hellospringboot.realm;
    
    import com.example.hellospringboot.model.Permission;
    import com.example.hellospringboot.model.Role;
    import com.example.hellospringboot.model.User;
    import com.example.hellospringboot.service.PermissionService;
    import com.example.hellospringboot.service.RoleService;
    import com.example.hellospringboot.service.UserService;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    
    public class MyAuthorizingRealm extends AuthorizingRealm {
    
        @Autowired
        UserService userService;
    
        @Autowired
        RoleService roleService;
    
        @Autowired
        PermissionService permissionService;
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
            UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
            String userName = token.getUsername();
            String passWord = String.valueOf(token.getPassword());
            if (!userService.checkUserByUsernameAndPassword(userName, passWord)) {//判断用户账号是否正确
                throw new UnknownAccountException("用户名或密码错误!");
            }
            return new SimpleAuthenticationInfo(userName, passWord, getName());
        }
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            String userName = principalCollection.getPrimaryPrincipal().toString();
            User user = userService.findUserByUserName(userName);
            if (user == null) {
                throw new UnknownAccountException("用户名或密码错误!");
            }
            List rolesList = user.rolesList();
            Set roles = new HashSet<>();
            Set permissions = new HashSet<>();
            for (Integer roleId : rolesList) {
                Role role = roleService.findRoleById(roleId);
                roles.add(role.getName());
                List permissionList = permissionService.findPermissionsByRoleId(roleId);
                for (Permission permission : permissionList) {
                    permissions.add(permission.getName());
                }
            }
            info.setRoles(roles);
            info.setStringPermissions(permissions);
            return info;
        }
    }
    复制代码

    Realm的创建对于整个Shiro安全验证体系搭建而言是至关重要的一步!

    其中两个抽象方法

    doGetAuthenticationInfo —— 用于校验用户名及密码的合法性

    doGetAuthorizationInfo —— 用于赋予实体对应的角色及交互权限

    6. 测试用Controller创建

    复制代码
    package com.example.hellospringboot.controller;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.subject.Subject;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RequestMapping("/user")
    @RestController
    public class UserController {
    
        @PostMapping("/login")
        public String login(String user, String pass) {
            UsernamePasswordToken token = new UsernamePasswordToken(user, pass);
            Subject subject = SecurityUtils.getSubject();
            if(!subject.isAuthenticated()) {
                try {
                    subject.login(token);
                } catch (AuthenticationException e) {
                    return e.getMessage();
                }
            }
            return "ok";
        }
    
        @PostMapping("/logout")
        public String logout(){
            Subject subject = SecurityUtils.getSubject();
            if(subject.isAuthenticated()) {
                try {
                    subject.logout();
                } catch (AuthenticationException e) {
                    return e.getMessage();
                }
            }
            return "ok";
        }
    
        @GetMapping("/admin")
        public String admin() {
            return "admin";
        }
    
        @GetMapping("/user")
        public String user() {
            return "user";
        }
    
    }
    复制代码

    内容很简单:

    login——登录方法

    logout——登出方法

    admin、user——两个测试方法,用于测试不同角色对于不同方法可访问的细粒度控制

    7. ShiroConfig配置类创建,实现用户访问权限的细粒度控制

    复制代码
    package com.example.hellospringboot.configure;
    
    import com.example.hellospringboot.realm.MyAuthorizingRealm;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.realm.Realm;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    @Configuration
    public class ShiroConfig {
    
        @Bean
        public SecurityManager securityManager(Realm realm) {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(realm);
            return securityManager;
        }
    
        @Bean
        public MyAuthorizingRealm getRealm() {
            MyAuthorizingRealm realm = new MyAuthorizingRealm();
            return realm;
        }
    
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
            shiroFilter.setSecurityManager(securityManager);
            Map filterChainMap = new LinkedHashMap();
            filterChainMap.put("/user/login", "anon");
            filterChainMap.put("/user/logout", "anon");
            filterChainMap.put("/user/admin", "authc,roles[admin],perms[admin:read]");
            filterChainMap.put("/user/user", "authc,roles[user],perms[user:read]");
            shiroFilter.setFilterChainDefinitionMap(filterChainMap);
            return shiroFilter;
        }
    }
    复制代码

     

    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将会是接下来几个章节的重头戏,敬请期待~

     

     

     

    MyAuthorizingRealm
  • 相关阅读:
    03-视口
    递归代码和动态规划代码相互转化
    两种 java 向 yarn 提交 spark 任务命令的区别
    eclipse svn插件安装
    栈与队列 | 用栈实现队列 | 用队列实现栈 | 基础理论与代码原理
    数据库基础知识
    html--宠物
    python趣味编程-恐龙克隆游戏
    【web课程设计网页规划与设计】基于HTML+CSS+JavaScript火车票网上预订系统网站(4个页面)
    加在形参上,spring的@NonNull和lombok的@NonNull的区别
  • 原文地址:https://www.cnblogs.com/itfantasy/p/16837917.html