zoukankan      html  css  js  c++  java
  • SpringBoot + Shiro 入门

    SpringBoot + Shiro 入门

    shiro 是一个比 spring security 轻量的安全框架。

    官方架构图如下:

    20514825_2

    1、shiro 核心模块

    shiro 有三大核心组件,如下:

    • Subject: 代表当前正在执行操作的用户,但 Subject 代表的可以是人,也可以是任何第三方系统帐号。当然每个 Subject 实例都会被绑定到 SercurityManger 上。
    • SecurityManger: SecurityManager 是 Shiro 核心,主要协调 Shiro 内部的各种安全组件,这个我们不需要太关注,只需要知道可以设置自定义的 Realm。
    • Realm: 用户数据和 Shiro 数据交互的桥梁。比如需要用户身份认证、权限认证。都是需要通过Realm来读取数据。

    2、shiro 配置

    先引入maven 依赖

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.7.1</version>
    </dependency>
    

    shiro 配置只需要两个类,一个是 shiroConfig类,一个是 CustomRealm 类。

    2.1、CustomRealm 类

    自定义的 CustomRealm 继承AuthorizingRealm。并且重写父类中的doGetAuthorizationInfo(权限相关)、doGetAuthenticationInfo(身份认证)这两个方法。

    package com.lin.config.shiro;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    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 java.util.HashSet;
    import java.util.Set;
    public class CustomRealm extends AuthorizingRealm {
        /**
         * 用户授权(checkRole、checkPermission用到)
         *
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            String username = (String) principalCollection.getPrimaryPrincipal();
            //根据用户名查询权限
            Set<String> permission = new HashSet<>();
            permission.add("permission1");
            permission.add("permission2");
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            simpleAuthorizationInfo.setStringPermissions(permission);
            return simpleAuthorizationInfo;
        }
    
        /**
         *  用户认证
         *
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            String username = (String) authenticationToken.getPrincipal();
            System.out.println("=====username:" + username);
            //实际根据用户名从数据库中查询密码
            String password;
            if (username.equals("user")) {
                password = "123456";
            } else {
                return null;
            }
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, getName());
            return simpleAuthenticationInfo;
        }
    }
    

    doGetAuthorizationInfo: 权限认证,即登录过后,每个身份不一定,对应的所能看的页面也不一样。
    doGetAuthenticationInfo:身份认证。即登录通过账号和密码验证登陆人的身份信息。

    2.2、ShiroConfig 类

    顾名思义就是对 shiro 的一些配置,相对于之前的 xml 配置。包括:过滤的文件和权限,密码加密的算法,其用注解等相关功能。

    @Configuration
    public class ShiroConfig {
    
        //Filter工厂,设置对应的过滤条件和跳转条件
        @Bean(name = "shiroFilter")
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            //登录页面,没有登录时将会自动跳转该页面,前后端分离时通过前端路由控制,后端返回json数据或抛出异常。
            shiroFilterFactoryBean.setLoginUrl("/lin/shiro/unauth");
            //没有权限默认跳转的页面
    //        shiroFilterFactoryBean.setUnauthorizedUrl("/lin/shiro/error");
            Map<String, String> map = new LinkedHashMap<>();;
            //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
            //登录接口
            map.put("/lin/shiro/login", "anon");
            map.put("/**", "authc");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
            return shiroFilterFactoryBean;
        }
    
        //将自己的验证方式加入容器
        @Bean
        public CustomRealm customRealm() {
            CustomRealm customRealm = new CustomRealm();
            return customRealm;
        }
    
        //权限管理,配置主要是Realm的管理认证
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
            defaultSecurityManager.setRealm(customRealm());
            return defaultSecurityManager;
        }
    }
    

    3、运行测试

    创建 ShiroTestController,定义登录接口

    @RequestMapping("/lin/shiro")
    @Controller
    public class ShiroTestController {
        @RequestMapping(value = "/login", method = RequestMethod.POST)
        @ResponseBody
        public String login(@RequestParam("username") String username, @RequestParam("password") String password)
        {
            // 从SecurityUtils里边创建一个 subject
            Subject subject = SecurityUtils.getSubject();
            // 在认证提交前准备 token(令牌)
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            // 执行认证登陆
            try {
                subject.login(token);
            } catch (UnknownAccountException uae) {
                return "未知账户";
            } catch (IncorrectCredentialsException ice) {
                return "密码不正确";
            } catch (LockedAccountException lae) {
                return "账户已锁定";
            } catch (ExcessiveAttemptsException eae) {
                return "用户名或密码错误次数过多";
            } catch (AuthenticationException ae) {
                return "用户名或密码不正确!";
            }
            if (subject.isAuthenticated()) {
                return "登录成功";
            } else {
                token.clear();
                return "登录失败";
            }
        }
        
        
        @RequestMapping("/unauth")
        public String LoginFailure(){
            throw new AuthenticationException("未登录");
        }
    }
    

    打开 postman, 访问登录接口

    http://localhost:8081/lin/shiro/login?username=user&password=123456

    可以发现登录成功

    image-20210603101349434

    未登录时,访问其他接口,会发现自动跳转到登录页面

    image-20210603104245000

    4、密码加密验证

    4.1、HashedCredentialsMatcher 配置

    ShiroConfig 类添加 HashedCredentialsMatcher配置:

    //密码加密
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        // 散列的次数,比如散列两次,相当于 md5(md5(""));
        hashedCredentialsMatcher.setHashIterations(2);
        // storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }
    

    4.2、完善Realm配置

    完善 ShiroConfigRealm配置:

    //将自己的验证方式加入容器
    @Bean
    public CustomRealm customRealm() {
        CustomRealm customRealm = new CustomRealm();
        //密码加密
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customRealm;
    }
    

    并修改 CustomRealm 的用户认证接口:

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String username = (String) authenticationToken.getPrincipal();
        System.out.println("=====username:" + username);
        String password;
        if (username.equals("user")) {
            //实际根据用户名从数据库中查询加密的密码
            password = "b8c2d5b0a37cc51f91d5e8970347a3a3";
        } else {
            return null;
        }
        //用户名作为盐加密
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(username), getName());
        return simpleAuthenticationInfo;
    }
    

    通过debug方式可以知道加密调用的是 HashedCredentialsMatcher下的 doCredentialsMatch方法:

    image-20210604114155363

    这样密码的加密便配置完成了。

    5、利用注解配置权限

    shiro 支持通过注解在方法上来进行权限的控制,需要在 ShiroConfig上添加以下配置:

    //开启cglib代理
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
    
    //开启shiro aop注解支持,使用代理方式;所以需要开启代码支持,@RequiresRoles,@RequiresPermissions注解在方法上
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }
    

    以上便配置完成了,现在方法上添加注解,用户没有权限访问将跑出UnauthorizedException异常。

    @GetMapping(value = "/findById")
    //没有权限将抛出UnauthorizedException异常
    @RequiresPermissions("permission3")
    @ResponseBody
    public Result findById(@RequestParam(value = "id", required = true, defaultValue = "2") Long id) {
        User user = userService.findById(id);
        return ResultUtil.success(user);
    }
    

    以上基本的配置就完成了。

    题外话Shiro默认的 session 登录不提倡使用,会给服务端带来很大的压力,在分布式服务下也很难维护,因此可以禁用session,采用 jwt 实现无状态登录是更佳的选择。

    参考资料:

    https://blog.csdn.net/bicheng4769/article/details/86668209

    https://www.jianshu.com/p/7f724bec3dc3

    自我控制是最强者的本能-萧伯纳
  • 相关阅读:
    The user specified as a definer ('root'@'%') does not exist
    Linux安装卸载查看vsftpd
    【数据分析】算法+Echarts小练
    【leecode】小练习(简单8题)
    路飞学城Python-Day171
    【路飞学城Day170】算法小结
    【路飞学城第七模块考核】
    【leecode】独特的电子邮件地址
    【leecode】宝石与石头
    路飞学城Python-Day153
  • 原文地址:https://www.cnblogs.com/CF1314/p/14850374.html
Copyright © 2011-2022 走看看