zoukankan      html  css  js  c++  java
  • SpringBoot整合Shiro+MD5+Salt+Redis实现认证和动态权限管理(上)----筑基中期

    写在前面

    通过前几篇文章的学习,我们从大体上了解了shiro关于认证和授权方面的应用。在接下来的文章当中,我将通过一个demo,带领大家搭建一个SpringBoot整合Shiro的一个项目开发脚手架,将之前学过的知识点串到一起,其中,也会补充一些之前没有讲过的内容。通过这个demo结束这几天的学习,同时也是结束国庆中秋小长假shiro系列专题入门文章。

    SpringBoot整合Shiro思路分析

    鉴权流程分析

    我们将我们的SpringBoot应用整合shiro,主要目的就是让shiro帮我们处理认证和授权的相关内容。也就是说,我们需要让shiro接管我们SpringBoot应用的会话。让用户的每一次请求都经过shiro进行认证和授权。因此,我们需要将用户请求拦截下来转发给shiro处理,这个拦截器是shiro提供的,ShiroFilter

    步骤

    1. 用户通过客户端(浏览器、手机App、小程序)发起请求

    2. ShiroFilter拦截请求并判断请求访问的资源是否为受保护资源:

      2.1 是,则执行步骤3

      2.2 不是,则直接放行

    3. 判断用户是否已通过认证:

      3.1 是 ,则执行步骤4

      3.2 否,将用户请求重定向到认证页面,让用户先认证

    4. 获取用户权限信息和访问资源所需要的权限信息进行比对:

      4.1 用户具备访问权限,则放行

      4.2 用户不具备权限,返回403的相应提示

    数据库分析设计

    我们通过MySQL保存我们的认证和权限的相关数据。采用用户-角色-权限模型实现动态管理用户权限信息。

    我们将系统当中的菜单、按钮、后端接口都抽象成系统的资源数据。以下是数据库表的设计:

    文末提供sql脚本的下载。

    整合步骤

    环境搭建

    maven

    创建一个SpringBoot的web应用,并引入如下依赖

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-web-starter</artifactId>
        <version>1.6.0</version>
    </dependency>
    

    添加对用户、角色和资源的CRUD支持

    这里代码就省略了,不影响理解,完整代码可以从文末提供的方式中下载。

    配置Shiro

    自定义Realm

    /**自定义Realm,使用mysql数据源
     * @author 赖柄沣 bingfengdev@aliyun.com
     * @version 1.0
     * @date 2020/10/6 9:09
     */
    public class MySQLRealm extends AuthorizingRealm {
    
        @Autowired
        private IUserService userService;
        @Autowired
        private IRoleService roleService;
        @Autowired
        private IResourceService resourceService;
    
        /**
         * 授权
         * @param principals
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            String username = (String) principals.getPrimaryPrincipal();
            List<Role> roleList = roleService.findByUsername(username);
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            for (Role role : roleList) {
                authorizationInfo.addRole(role.getRoleName());
            }
    
            List<Long> roleIdList  = new ArrayList<>();
            for (Role role : roleList) {
                roleIdList.add(role.getRoleId());
            }
    
            List<Resource> resourceList = resourceService.findByRoleIds(roleIdList);
            for (Resource resource : resourceList) {
                authorizationInfo.addStringPermission(resource.getResourcePermissionTag());
            }
    
            return authorizationInfo;
        }
    
    
        /**
         * 认证
         * @param token
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            if(token==null){
                return null;
            }
    
            String principal = (String) token.getPrincipal();
            User user = userService.findByUsername(principal);
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
            return simpleAuthenticationInfo;
        }
    }
    

    shiro中的Realm对象充当了认证、授权数信息的据源作用。关于更多自定义Realm的内容请参考我的另一篇文章《Shiro入门学习---使用自定义Realm完成认证|练气中期》

    ShiroConfig

    /**shiro配置类
     * @author 赖柄沣 bingfengdev@aliyun.com
     * @version 1.0
     * @date 2020/10/6 9:11
     */
    @Configuration
    public class ShiroConfig {
    
        /**
         * 创建ShiroFilter拦截器
         * @return ShiroFilterFactoryBean
         */
        @Bean(name = "shiroFilterFactoryBean")
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            //配置不拦截路径和拦截路径,顺序不能反
            HashMap<String, String> map = new HashMap<>(5);
    
            map.put("/authc/**","anon");
            map.put("/login.html","anon");
            map.put("/js/**","anon");
            map.put("/css/**","anon");
    
            map.put("/**","authc");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
    
            //覆盖默认的登录url
            shiroFilterFactoryBean.setLoginUrl("/authc/unauthc");
            return shiroFilterFactoryBean;
        }
    
        @Bean
        public Realm getRealm(){
            //设置凭证匹配器,修改为hash凭证匹配器
            HashedCredentialsMatcher myCredentialsMatcher = new HashedCredentialsMatcher();
            //设置算法
            myCredentialsMatcher.setHashAlgorithmName("md5");
            //散列次数
            myCredentialsMatcher.setHashIterations(512);
            MySQLRealm realm = new MySQLRealm();
            realm.setCredentialsMatcher(myCredentialsMatcher);
            return realm;
        }
    
        /**
         * 创建shiro web应用下的安全管理器
         * @return DefaultWebSecurityManager
         */
        @Bean
        public DefaultWebSecurityManager getSecurityManager(Realm realm){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(realm);
            SecurityUtils.setSecurityManager(securityManager);
            return securityManager;
        }
    
    }
    

    在编写shiro配置类这一步,需要大家注意的是,因为我们使用的是md5+salt+hash加密我们的密码,因此要换掉默认的凭证匹配器CredentialsMatcher对象,对于这部分的内容请参考我的另一篇文章《shiro入门学习--使用MD5和salt进行加密|练气后期》

    实现认证模块

    VO层

    /**认证请求参数
     * @author 赖柄沣 bingfengdev@aliyun.com
     * @version 1.0
     * @date 2020/10/7 15:12
     */
    @Data
    public class LoginVO implements Serializable {
    
        private String username;
        private String password;
    
    }
    

    web层

    /**认证模块
     * @author 赖柄沣 bingfengdev@aliyun.com
     * @version 1.0
     * @date 2020/10/6 10:07
     */
    @RestController
    @RequestMapping("/authc")
    public class AuthcController {
    
        @Autowired
        private AuthcService authcService;
    
        @PostMapping("/login")
        public boolean login(@RequestBody LoginVO loginVO){
    
            return authcService.login(loginVO);
        }
    
    
    
        @GetMapping("/unauthc")
        public String unauthc(){
            return "请先登录";
        }
        
    }
    

    service层

    /**
     * @author 赖柄沣 bingfengdev@aliyun.com
     * @version 1.0
     * @date 2020/10/7 15:15
     */
    @Service
    public class AuthcServiceImpl implements AuthcService {
        @Override
        public boolean login(LoginVO loginVO) throws AuthenticationException {
            if (loginVO==null){
                return false;
            }
    
            if (loginVO.getUsername()==null||"".equals(loginVO.getUsername())){
                return false;
            }
    
            if (loginVO.getPassword() == null || "".equals(loginVO.getPassword())){
                return false;
            }
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(loginVO.getUsername(), loginVO.getPassword());
    
            subject.login(token);
            return true;
        }
    }
    

    实现产品模块

    /**产品模块
     * @author 赖柄沣 bingfengdev@aliyun.com
     * @version 1.0
     * @date 2020/10/6 10:14
     */
    @RestController
    @RequestMapping("/product")
    public class ProductController {
    
        
        @RequiresPermissions("product:get")
        @GetMapping("/get/list")
        public String getProductList() {
            return "productList";
        }
        
        @RequiresPermissions("product:delete")
        @GetMapping("/delete")
        public String deleteProduct() {
            return "删除产品数据";
        }
    }
    

    对于注解实现访问控制,shiro主要有两个注解:RequiresPermissions和RequiresRoles。均可以用在类和方法上。具体用在哪可以根据自己的系统权限划分粒度决定。

    对于这两个注解,有两个参数:

    1. value :分别对应permission的权限字符串值和role的角色名称;
    2. logical:逻辑运算符。这是一个枚举类型,有ANDOR两个值。当使用AND时表示需要满足所有传入的value值,OR表示仅需满足一个value 即可。默认为AND

    关于shiro权限(访问控制)的更多内容,可以阅读我的另一篇文章《shiro入门学习--授权(Authorization)|筑基初期》

    简单测试

    认证通过的情况

    认证未通过的情况

    获取产品信息

    请求没有访问权限的资源

    默认的消息提示可以换一下。

    未经过认证直接访问受保护资源

    写在最后

    在这一篇文章当中,我们搭建了一个初步的SpringBoot整合Shiro的应用,实现了认证和授权。

    在下一篇文章当中,我们将接着完善这个小demo。加入自定义shiro会话管理和shiro缓存的内容。并且会将这个小demo进行升级,使其变成前后端分离的模式。

    双节即将结束,代码人在江湖!

    如果您觉得这篇文章能给您带来帮助,那么可以点赞鼓励一下。如有错误之处,还请不吝赐教。在此,谢过各位乡亲父老!

    代码及sql下载方式:微信搜索【Java开发实践】,加关注并回复20201007 即可获取下载链接。

  • 相关阅读:
    MySQL: Speed of INSERT Statements
    ImportError: No module named argparse
    How To Use Coordinates To Extract Sequences In Fasta File
    30分钟掌握Dart语言
    Could not find com.android.tools.build:aapt2:3.2.0-alpha14-4748712.
    完美解决 No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android
    iOS 可变字符串NSMutableString的使用
    NSMutableString和NSString区别,及相互转换方法
    iOS学习-字符串的删除替换
    Android 常用正则表达式
  • 原文地址:https://www.cnblogs.com/bingfengdev/p/13788534.html
Copyright © 2011-2022 走看看