• (转) shiro权限框架详解04-shiro认证


    http://blog.csdn.net/facekbook/article/details/54906635

    shiro认证

    本文介绍shiro的认证功能

    • 认证流程
    • 入门程序(用户登录和退出)
    • 自定义Realm
    • 散列算法

    认证流程

    开始构造SecurityManager环境subject.login();提交认证securityManager.login()执行认证Authenticator执行认证Realm根据身份获取验证信息结束

    入门程序(用户登录和退出)

    创建Java项目

    jdk版本:1.7.0_67

    加入shiro的jar包以及依赖包

    log4j.properties日志文件配置

    log4j.rootLogger=debug, stdout
    
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
    

    eclipse中ini文件打开方式配置

    shiro使用ini文件作为配置文件。所以需要修改eclipse中ini文件的打开方式,默认的话ini文件是使用记事本打开。具体如下图: 

    创建ini配置文件

    在classpath路径下创建shiro-first.ini文件,文件内容是测试用户的账号和密码。内容如下:

    [users]
    zhangsan=123
    lisi=456

    认证代码

    @Test
        public void testLoginAndLogOut() {
            // 构建SecurityManager工厂,IniSecurityManagerFactory可以从ini文件中初始化SecurityManager环境
            Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-first.ini");
            // 通过工厂创建SecurityManager
            SecurityManager securityManager = factory.getInstance();
            // 将SecurityManager设置到运行环境中
            SecurityUtils.setSecurityManager(securityManager);
            //创建一个Subject实例,该实例认证需要使用上面创建的SecurityManager
            Subject subject = SecurityUtils.getSubject();
            //创建token令牌,账号和密码是ini文件中配置的
            AuthenticationToken token = new UsernamePasswordToken("zhangsan", "123");
            try {
                //用户登录
                subject.login(token);
            } catch (AuthenticationException e) {
                e.printStackTrace();
            }
            //用户认证状态
            Boolean isAuthenticated = subject.isAuthenticated();
            System.out.println("用户认证状态:"+isAuthenticated);//输出true
    
            //用户退出
            subject.logout();
    
            isAuthenticated = subject.isAuthenticated();
            System.out.println("用户认证状态:"+isAuthenticated);//输出false
        }

    认证执行流程

    1.创建token令牌,token中有用户提交的认证信息即账号和密码。 
    2.执行subject.login(token),最终由securityManager通过 Authenticator进行认证。 
    3.Authenticator的实现ModuleRealmAuthenticator调用realm从init文件读取用户真实的账号和密码,这里使用的是IniRealm(Shiro自带) 
    4.IniRealm先根据token中的账号去ini中找该账号,如果找不到则给ModuleRealmAuthenticator返回null,如果找到则匹配密码,匹配密码成功则认证通过。

    常见的异常

    • UnknownAccountException 
      账号不存在异常如下:
    org.apache.shiro.authc.UnknownAccountException: Realm [org.apache.shiro.realm.text.IniRealm@9cdc393] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - zhangsan1
    • IncorrectCredentialsException 
      当输入密码错误会抛出此异常,如下:
    org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] did not match the expected credentials.

    更多异常信息如下: 
    DisabledAccountException(帐号被禁用) 
    LockedAccountException(帐号被锁定) 
    ExcessiveAttemptsException(登录失败次数过多) 
    ExpiredCredentialsException(凭证过期)等 
    类结构如下图 

    自定义Realm

    上面的程序使用的是Shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息。但是实际情况中大部分情况下是从数据库中获取用户信息,所以需要自定义realm。

    Shiro中Realm

     
    最基础的是Realm接口,CachingRealm负责缓存管理,AuthenticatingRealm负责认证,AuthorizingRealm负责授权,通常自定义的Realm继承AuthorizingRealm。

    自定义Realm代码

    通过继承AuthorizingRealm类实现

    public class CustomRealm extends AuthorizingRealm {
    
        /**
         * 认证方法
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
            // 从token中获取用户身份信息
            String username = (String) token.getPrincipal();
    
            // 正常逻辑应该是通过username查询数据库。
            // 如果查询不到返回null
            if (!"zhangsan".equals(username)) {// 这里模仿查询不到
                return null;
            }
    
            // 模拟从数据获取密码
            String password = "123";
    
            // 返回认证信息交由父类AuthorizingRealm认证
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, "");
    
            return authenticationInfo;
        }
    
        /**
         * 授权方法
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            return null;
        }
    }

    ini配置文件

    新建shiro-realm.ini文件。内容如下:

    [main]
    #自定义realm
    customRealm=com.knight.shiro.realm.CustomRealm
    #将realm设置到securityManager
    securityManager.realm=$customRealm

    这里不需要配置users,是因为我们这里模拟users的获取来自数据库。

    测试代码

    @Test
        public void testCustomeRealm() {
            // 构建SecurityManager工厂,IniSecurityManagerFactory可以从ini文件中初始化SecurityManager环境
            Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
            // 通过工厂创建SecurityManager
            SecurityManager securityManager = factory.getInstance();
            // 将SecurityManager设置到运行环境中
            SecurityUtils.setSecurityManager(securityManager);
            //创建一个Subject实例,该实例认证需要使用上面创建的SecurityManager
            Subject subject = SecurityUtils.getSubject();
            //创建token令牌,账号和密码是ini文件中配置的
            //AuthenticationToken token = new UsernamePasswordToken("zhangsan", "123");//账号密码正确token
            //AuthenticationToken token = new UsernamePasswordToken("zhangsan", "1234");//密码错误异常token
            AuthenticationToken token = new UsernamePasswordToken("zhangsan1", "123");//账号错误异常token
            try {
                //用户登录
                subject.login(token);
            } catch (AuthenticationException e) {
                e.printStackTrace();
            }
            //用户认证状态
            Boolean isAuthenticated = subject.isAuthenticated();
            System.out.println("用户认证状态:"+isAuthenticated);//输出true
        }

    散列算法

    散列算法一般用于生成一段文本的摘要信息,散列算法不可逆,也就是将内容生成摘要,但是反过来通过摘要生成内容是不可以的。散列算法常用于对密码进行散列,常用的散列算法有MD5、SHA。一般散列算法需要提供一个salt(盐)与原始内容生成摘要,这样做的目的是为了安全性。

    例子

            Md5Hash  md5Hash = new Md5Hash("111111");
            System.out.println("md5加密,不加盐:"+md5Hash.toString());
    
            //md5加密,加盐,一次hash
            String password_md5_sale_1 = new Md5Hash("11111", "aga23", 1).toString();
            System.out.println("md5加密,加盐,一次hash:"+password_md5_sale_1);
    
            //md5加密,加盐,两次hash
            String password_md5_sale_2 = new Md5Hash("11111", "aga23", 2).toString();
            System.out.println("md5加密,加盐,两次hash:"+password_md5_sale_2);//相当于md5(md5('1111'))
    
            //使用simpleHash
            String simpleHash = new SimpleHash("MD5", "11111", "aga23", 1).toString();
            System.out.println(simpleHash);

    在realm中使用散列算法

    实际系统中是将盐和散列后的值存储在数据库中,自定义realm从数据库取出盐和加密后的值由shiro完成密码校验。

    自定义支持散列的realm

    public class CustomRealmMd5 extends AuthorizingRealm {
    
        /**
         * 认证方法
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
            // 从token中获取用户身份信息
            String username = (String) token.getPrincipal();
    
            // 正常逻辑应该是通过username查询数据库。
            // 如果查询不到返回null
            if (!"zhangsan".equals(username)) {// 这里模仿查询不到
                return null;
            }
    
            // 模拟从数据获取密码
            String password = "fdf907b0d3f427b9ffa2f86f213d1afd";
            // 盐
            String salt = "aga23";
            // 返回认证信息交由父类AuthorizingRealm认证
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password,
                    ByteSource.Util.bytes(salt), "");
    
            return authenticationInfo;
        }
    
        /**
         * 授权方法
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            return null;
        }
    }

    支持散列的realm配置

    [main]
    #定义凭证匹配器
    credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
    #设置散列算法
    credentialsMatcher.hashAlgorithmName=md5
    #设置散列次数
    credentialsMatcher.hashIterations=1
    
    #将凭证匹配器设置到realm
    customRealm=com.knight.shiro.realm.CustomRealmMd5
    customRealm.credentialsMatcher=$credentialsMatcher
    securityManager.realms=$customRealm
    

    测试代码

    注意修改配置文件的路径

        @Test
        public void testCustomeRealmMd5() {
            // 构建SecurityManager工厂,IniSecurityManagerFactory可以从ini文件中初始化SecurityManager环境
            Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm-md5.ini");
            // 通过工厂创建SecurityManager
            SecurityManager securityManager = factory.getInstance();
            // 将SecurityManager设置到运行环境中
            SecurityUtils.setSecurityManager(securityManager);
            //创建一个Subject实例,该实例认证需要使用上面创建的SecurityManager
            Subject subject = SecurityUtils.getSubject();
            //创建token令牌,账号和密码是ini文件中配置的
            //AuthenticationToken token = new UsernamePasswordToken("zhangsan", "11111");//账号密码正确token
            AuthenticationToken token = new UsernamePasswordToken("zhangsan", "1234");//密码错误异常token
            //AuthenticationToken token = new UsernamePasswordToken("zhangsan1", "11111");//账号错误异常token
            try {
                //用户登录
                subject.login(token);
            } catch (AuthenticationException e) {
                e.printStackTrace();
            }
            //用户认证状态
            Boolean isAuthenticated = subject.isAuthenticated();
            System.out.println("用户认证状态:"+isAuthenticated);//输出true
        }

    该文章涉及的代码

    代码地址

  • 相关阅读:
    Javascript FP-ramdajs
    微信小程序开发
    SPA for HTML5
    One Liners to Impress Your Friends
    Sass (Syntactically Awesome StyleSheets)
    iOS App Icon Template 5.0
    React Native Life Cycle and Communication
    Meteor framework
    RESTful Mongodb
    Server-sent Events
  • 原文地址:https://www.cnblogs.com/telwanggs/p/7118116.html
Copyright © 2020-2023  润新知