• shiro-----Shiro与SSM集成实现用户认证和授权


    目录

    Shiro实现用户认证

    认证流程

    测试登录

    盐加密

    测试

    Shiro授权流程?

    测试

    注解式开发

    在控制层方法上加上注解

    测试


    Shiro实现用户认证

    在 shiro 中,用户需要提供principals (身份)和credentials(凭证)给shiro,从而应用能验证用户身份。身份即帐号/凭证即密码

    认证流程

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_17,color_FFFFFF,t_70,g_se,x_16

    1、创建token令牌,token中有用户提交的认证信息即帐号和密码

    2、执行Subject.login(token),Subject实例通常是DelegatingSubject类(或子类)的实例对象;在认证开始时,通过SecurityManager实例来调用securityManager.login(token)方法

    3、SecurityManager接受到token(令牌)信息后委托Authenticator实例进行认证;Authenticator通过实现类ModularRealmAuthenticator来调用anthenticator.authenticate(token)方法;ModularRealmAuthenticator在认证过程中会对一个或多个Realm实例进行适配(可插拔)

    4、如果配置了多个Realm,ModularRealmAuthenticator会根据配置的AuthenticationStrategy(认证策略)来进行多Realm的认证过程;在Realm被调用后,AuthenticationStrategy将对每一个Realm的结果做出响应
    注意: 如果只有一个Realm,Realm将直接调用而无需再配置认证策略

    5、判断每一个Realm是否都支持提交的token,如果支持,Realm调用getAuthenticationInfo(token),该方法就是实际的认证处理,我们通过覆盖Realm的doGetAuthenticationInfo方法来编写我们**自定义的认证处理

    6、shiro中有三种认证策略的具体实现:**

    **AtleastOneSuccessfulStrategy:**只要有一个realm验证成功,则成功
    **FirstSuccessfulStrategy:**第一个realm验证成功,则成功,后续realm将被忽
    **AllSuccessfulStrategy:**所有realm成功,验证才成功

    认证失败后抛出的一些异常:

    • UnknownAccountException帐号不存在
    • IncorrectCredentialsException密码错误
    • DisabledAccountException帐号被禁用
    • LockedAccountException帐号被锁定
    • ExcessiveAttemptsException登录失败次数过多
    • ExpiredCredentialsException凭证过期

    导入Shrio与Spring及springMVC相关依赖

    
        
          org.apache.shiro
          shiro-core
          ${shiro.version}
        
        
        
          org.apache.shiro
          shiro-web
          ${shiro.version}
        
        
        
          org.apache.shiro
          shiro-spring
          ${shiro.version}
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    配置Shiro过滤器(web.xml)

      
      
        shiroFilter
        org.springframework.web.filter.DelegatingFilterProxy
        
          
          targetFilterLifecycle
          true
        
      
      
        shiroFilter
        /*
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    创建自定义Realm

    shiro自带的Realm接口,CachingRealm负责缓存处理,AuthenticationRealm负责认证,AuthorizingRealm负责授权等,但是通常情况下,正确的用户信息都是从数据库中取出,所以需要自定义realm,通常自定义的realm继承AuthorizingRealm,认证是重写**doGetAuthenticationInfo(AuthenticationToken token)方法,授权是重写doGetAuthorizationInfo(PrincipalCollection principals)**方法

    package com.zking.ssm.book.shiro;
    
    import com.zking.ssm.book.mapper.SysUserMapper;
    import com.zking.ssm.book.model.SysUser;
    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.apache.shiro.util.ByteSource;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import java.util.Set;
    
    /**
     * 自定义Realm的安全数据源,采用数据库的方式
     */
    public class ShiroRealm extends AuthorizingRealm {
    
        @Autowired
        private SysUserMapper sysUserMapper;
    
    
        /**
         *授权
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            
        }
    
        /**
         * 认证
         * @param authenticationToken 传入的账号密码令牌Token
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            //获取账号
            String username = authenticationToken.getPrincipal().toString();
            //获取密码
            String password = authenticationToken.getCredentials().toString();
    
            //根据账号查询数据库中的用户对象信息
            SysUser sysUser = sysUserMapper.userLogin(username);
            //判断账号是否存在
            if(sysUser ==null){
                throw new UnknownAccountException("账号不存在!!!");
            }
    
            //创建SimpleAuthenticationInfo,传入正确的账号和密码(来自于数据库)
            SimpleAuthenticationInfo simple=new SimpleAuthenticationInfo(
                    sysUser.getUsername(),
                    sysUser.getPassword(),
                    ByteSource.Util.bytes(sysUser.getSalt()),
                    this.getName()
            );
    
            return simple;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    配置spring与shiro的配置文件 spring-shiro.xml

    在这里配置了盐加密

    
    
    
        
        
    
            
            
            
            
            
            
                
                    
                    
                    
                    
                    
                    
                
            
        
    
    
        
        
            
            
        
        
        
            
            
            
            
            
           
            
            
                
                    
                    
                    /user/userLogin=anon
                    /book/**=authc
                    
                
            
        
    
        
        
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    与spring集成

    
    
            
            
    
          
    
            
            
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Controller层

    /**
         * 跳转到登录页面
         * @return
            @RequestMapping("tologin")
            public String toLogin(){
                return "login";
            }
         */
        /**
         * 登录方法
         * @return
         */
        @RequestMapping("/userLogin")
        public String userLogin(SysUser sysuser, Model model){
            //获取主体
            Subject subject = SecurityUtils.getSubject();
            //创建账号密码令牌
            UsernamePasswordToken token = new UsernamePasswordToken(
                        sysuser.getUsername(),
                        sysuser.getPassword()
            );
    
    
            //定义错误信息
            String msg=null;
            try {
                subject.login(token);
            }catch (UnknownAccountException e) {
                msg="账号错误";
                e.printStackTrace();
            }catch (IncorrectCredentialsException e) {
                msg="密码错误";
                e.printStackTrace();
            }catch (AuthenticationException e) {
                msg="账号或密码错误";
                e.printStackTrace();
            }
    
            //判断msg
            if(msg == null){
                return "redirect:/toIndex";
            }else{
                model.addAttribute("msg",msg);
               // return "login";
                return "forward:/home/index.html";//转发绕开视图解析器
            }
    
        }
    
    
        /**
         * 安全退出
         * @return
         */
        @RequestMapping("/userLogout")
        public String userLogout(){
            //获取主体
            Subject subject = SecurityUtils.getSubject();
            subject.logout();;
            return "redirect:/home/index.html";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    测试登录

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

    盐加密

    简单的介绍一下盐加密,其实很简单就是就是在数据库中多添加了一个字段,存随机数的,相当于盐salt。

    盐加密把相同的明文密码转换成不同的密文密码,而在转换成密文密码的同时用了一套盐进行加密,而在解密密文密码时需要在这个明文密码转换成密文密码的那套盐,才能进行解密。

    注册时
    用户 输入用户名和密码,这个时候生成salt,然后将salt和密码进行拼接,存到数据库中去,一个密码对应一个salt
    登录时
    用户输入用户名和密码,后台根据用户名去数据库中找到用户信息,然后将用户输入的密码和对应的盐salt进行解密,去跟存到数据库中的密码进行对比,如果一致登录成功,否则失败

    上述中的shiro登陆中用到的MD5+散列1024+Hex/Base64加密方式

    在这里加密还需要更改spring-shiro中的自定义Realm配置Shiro明文密码如何进行加密、解密

    
      
      
      
      
      
         
            
            
            
                
            
            
         
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    加密密码PasswordHelper类(盐加密)

    package com.zking.ssm.book.util;
    
    
    import org.apache.shiro.crypto.RandomNumberGenerator;
    import org.apache.shiro.crypto.SecureRandomNumberGenerator;
    import org.apache.shiro.crypto.hash.SimpleHash;
    
    /**
     * 用于shiro权限认证的密码工具类
     */
    public class PasswordHelper {
    
        /**d
         * 随机数生成器
         */
        private static RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
    
        /**
         * 指定hash算法为MD5
         */
        private static final String hashAlgorithmName = "md5";
    
        /**
         * 指定散列次数为1024次,即加密1024次
         */
        private static final int hashIterations = 1024;
    
        /**
         * true指定Hash散列值使用Hex加密存. false表明hash散列值用用Base64-encoded存储
         */
        private static final boolean storedCredentialsHexEncoded = true;
    
        /**
         * 获得加密用的盐
         *
         * @return
         */
        public static String createSalt() {
            return randomNumberGenerator.nextBytes().toHex();
        }
    
        /**
         * 获得加密后的凭证
         *
         * @param credentials 凭证(即密码)
         * @param salt        盐
         * @return
         */
        public static String createCredentials(String credentials, String salt) {
            SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, credentials,
                    salt, hashIterations);
            return storedCredentialsHexEncoded ? simpleHash.toHex() : simpleHash.toBase64();
        }
    
    
        /**
         * 进行密码验证
         *
         * @param credentials        未加密的密码
         * @param salt               盐
         * @param encryptCredentials 加密后的密码
         * @return
         */
        public static boolean checkCredentials(String credentials, String salt, String encryptCredentials) {
            return encryptCredentials.equals(createCredentials(credentials, salt));
        }
    
        public static void main(String[] args) {
            //盐
            String salt = createSalt();
            System.out.println(salt);
            System.out.println(salt.length());
            //凭证+盐加密后得到的密码
            String credentials = createCredentials("123", salt);
            System.out.println(credentials);
            System.out.println(credentials.length());
            boolean b = checkCredentials("123", salt, credentials);
            System.out.println(b);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    测试

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

    存储到数据库

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

    一个密码对应一个salt

    Shiro授权流程
    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_17,color_FFFFFF,t_70,g_se,x_16

    ==============================================================================================================================================================================================================================================================================================================

    1、调用授权验证方法(**Subject.isPermitted()Subject.hasRole()**等)

    2、Subject实例通常是DelegatingSubject类(或子类)的实例对象;在认证开始时,通过SecurityManager实例来调用**securityManager.isPermitted(string)**方法/**security.hasRole(string)**方法

    3、SecurityManager委托Authorizer的实例(默认是ModularRealmAuthorizer类的实例,同样支持多个realm)调用相应的授权方法

    4、每一个Realm将检查是否实现了相同的Authorizer接口,然后调用Realm自己的相应的授权验证方法

    5、使用多个Realm时,不同于认证策略处理方式,授权处理过程中:

    当调用Realm出现异常时,立即抛出,结束授权验证
    只要一个Realm验证成功,则认为授权成功,立即返回,结束验证

    在mapper层定义两个方法

    这里返回Set集合是因为在授权时,参数必须为Set集合

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

    在自定义Realm中重写授权方法

    /**
         *授权
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //获取登录账号
            String username = principalCollection.getPrimaryPrincipal().toString();
            //根据username获取用户对应角色
            Set roles = sysUserMapper.findRoles(username);
            //根据username获取对应的角色权限
            Set permissions = sysUserMapper.findPermissions(username);
            //创建SimpleAuthorizationInfo
            SimpleAuthorizationInfo simple=new SimpleAuthorizationInfo();
            //填充用户的角色和权限
            simple.setRoles(roles);
            simple.setStringPermissions(permissions);
            return simple;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在spring-shiro.xml中的安全管理器中配置shiro的核心过滤器和并设置权限,和验证失败页面

    
        
            
            
            
            
            
        
        
        
            
            
            
            
            
           
            
            
                
                    
                    
                    /user/userLogin=anon
                    /book/**=authc
                    
                
            
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    测试

    在未登录下访问book模块下

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_19,color_FFFFFF,t_70,g_se,x_16

    自动被权限拦截并返回登录页面watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

    权限标签

    导入shiro标签watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

    验证身份是否是管理员

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

    同时还有角色权限

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

    注解式开发

    1、在spring-mvc中配置shiro注解式开发

    
        
            
        
        
            
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.在控制层方法上加上注解

    @RequiresRoles(value={“管理员”,“高级用户”},logical= Logical.OR)

    在此方法上使用此权限注解,判断使用该方法是否是管理员或者高级用户

    OR该文AND 为同时满足两个角色

    @RequiresRoles(“管理员”)

    判断使用该方法是否是管理员

    /**
         * 返回List泛型格式的JSON数据
         * @param book
         * @param request
         * @return
         */
        @RequiresRoles(value={"管理员","高级用户"},logical= Logical.AND)
        @RequestMapping("/queryListBooks")
        @ResponseBody
        public List queryListBooks(Book book,HttpServletRequest request){
            PageBean pageBean=new PageBean();
            pageBean.setRequest(request);
            List books = bookService.queryBookPager(book, pageBean);
            return   books;
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    @RequiresPermissions(“bookmanager📖edit”)

    此注解表示使用此方法需要具有此权限

    /**
         * 返回Map类型的JSON数据
         * @param bookId
         * @return
         */
        @RequiresPermissions("bookmanager:book:edit")
        @RequestMapping("/querySingleMap")
        @ResponseBody
        public Map querySingleMap(Integer bookId){
            return  bookService.querySingleMap(bookId);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    测试

    在登录普通用户下访问

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

    watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6ZGrNzA5,size_20,color_FFFFFF,t_70,g_se,x_16

    至此,Shiro与SSM集成实现用户认证和授权介绍完毕,由于作者水平有限难免有疏漏,欢迎留言纠错。

  • 相关阅读:
    MATLAB Coder从入门到放弃
    单机高性能网络模型
    NR slot配置
    使用Tensorflow进行完整的深度神经网络CNN训练完成图片识别案例2
    【数据机构】最小生成树(prim算法)
    Android AssetManager初探
    实例001:数字组合 有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少?
    179. 最大数
    正厚软件干货|DOCKER环境部署(linux版)
    CPS攻击案例(一)——基于脉冲宽度调制PWM的无人机攻击
  • 原文地址:https://blog.csdn.net/hjseo_seg/article/details/126496198