• (转) 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
        }

    该文章涉及的代码

    代码地址

  • 相关阅读:
    css文字换行问题white-space:pre-line或者white-space:pre-wrap,解决word-wrap:break-word解决不了的
    ie浏览器检测不到cookie的问题
    jQuery ajax在IE浏览器的跨域问题--jquery.xdomainrequest.min.js
    移动端 input 获取焦点后弹出带搜索、确定、前往的键盘,以及隐藏系统键盘
    三个获取浏览器URL中参数值的方法
    上传文件时文件类型限制 <input id="File1" type="file" accept=""/>
    ES6 let和const 的相同点与区别
    html页面中的title设置为空格
    vue 之 key
    nginx 之 proxy_pass详解
  • 原文地址:https://www.cnblogs.com/telwanggs/p/7118116.html
Copyright © 2020-2023  润新知