当前位置:   article > 正文

shiro学习_shiro获取authenticationinfo

shiro获取authenticationinfo

1.定义:

1.1 权限管理:

分类:shiro(Apache) 和 springsecurity
定义:属于系统安全的范畴,实现对用户访问系统的控制,按照安全规则控制用户可以访问且只能访问自己被授权的资源。
包括:身份认证(Authenticator)授权(Authorizer)

1.2 关于shiro:

shiro官网:https://shiro.apache.org/

shiro的核心架构图 重要部分:security manager(安全管理器)其中包括:

  • Authenticator:认证器,对用户身份进行认证,这是一个接口,shiro提供了一个modularRealmAuthenticato实现类。
  • Authorizer:授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否具有实现此功能的操作权限。
  • Realms:领域,相当于DataSource数据源,security manager进行安全认证需要通过Realms获取用户权限数据

在这里插入图片描述

2.身份认证:

定义:判断一个用户是否为合法用户的过程,其实就是我们登陆的过程。

关键对象:

  • Subject:主体,即访问系统的用户、程序等。
  • Principal:身份信息,即主体进行身份认证的标识,标识必须有唯一性。一个主体可以有多个身份,但必须有一个主身份。(相当于用户名)
  • Credential:凭证信息,即只有主体自己知道的安全信息。(相当于密码)
    在这里插入图片描述

2.1 编写代码:

  1. 创建一个maven项目,添加shiro依赖:
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.8.0</version>
    </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 在resource文件夹下编写一个shiro配置文件:shiro.ini
    这里的配置文件中存放着要验证的正确信息,相当于一个用户名密码的数据库。
[users]
wyh = 1126
wqj = 0602
  • 1
  • 2
  • 3
  1. 编写相关代码:
public class testAuthenticator {
    public static void main(String[] args) {
        //1.新建一个安全管理器
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2.设置realm,获得要验证的值
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//        3.将安全管理器加载进工具类
        SecurityUtils.setSecurityManager(securityManager);
//        4.通过工具类获得对象
        Subject subject = SecurityUtils.getSubject();
//        5.设置token:token相当于我们输入的用户名和密码
        UsernamePasswordToken wyh = new UsernamePasswordToken("we", "1126");
        try {
            subject.login(wyh);
            System.out.println(subject.isAuthenticated());
        }catch (IncorrectCredentialsException e){
            System.out.println("密码错误!");
        }catch (UnknownAccountException e){
            System.out.println("用户名错误");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

2.2 源码分析:

主要执行身份验证的是subject.login这个方法
这个方法调用了安全管理器的login方法:

Subject subject = this.securityManager.login(this, token);
  • 1

调用的是defaultSecurityManager中的login方法:

 public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
            info = this.authenticate(token);
  • 1
  • 2
  • 3
  • 4

这里调用了AuthenticatingSecurityManager的authenticate方法:

    private Authenticator authenticator = new ModularRealmAuthenticator();
    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }
  • 1
  • 2
  • 3
  • 4

调用了AbstractAuthenticator的authenticate方法:
在这里判断了传入的token是否为空,并创建了一个info值,为info值赋值。

public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        if (token == null) {
            throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
        } else {
            log.trace("Authentication attempt received for token [{}]", token);

            AuthenticationInfo info;
            try {
                info = this.doAuthenticate(token);
                if (info == null) {
                    String msg = "No account information found for authentication token [" + token + "] by this Authenticator instance.  Please check that it is configured correctly.";
                    throw new AuthenticationException(msg);
                }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

执行的是ModularRealmAuthenticator的 doAuthenticate方法:
第一个assertRealmsConfigured方法是对realm值做了一个判断,是否有realm值,如果没有则报错。
然后对realm的个数进行判断,是否只有一个realm,我们实验中是只有一个realm的,所以执行本类下的doSingleRealmAuthentication方法。
执行getAuthenticationInfo方法,获取info的值

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        this.assertRealmsConfigured();
        Collection<Realm> realms = this.getRealms();
        return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
    }

 protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
        if (!realm.supports(token)) {
            String msg = "Realm [" + realm + "] does not support authentication token [" + token + "].  Please ensure that the appropriate Realm implementation is configured correctly or that the realm accepts AuthenticationTokens of this type.";
            throw new UnsupportedTokenException(msg);
        } else {
            AuthenticationInfo info = realm.getAuthenticationInfo(token);
            if (info == null) {
                String msg = "Realm [" + realm + "] was unable to find account data for the submitted AuthenticationToken [" + token + "].";
                throw new UnknownAccountException(msg);
            } else {
                return info;
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

调用的是AuthenticatingRealm的getAuthenticationInfo方法。
因为没有将info存入缓存,所以执行doGetAuthenticationInfo方法获得info。
然后进行判断info是否为空,执行本类中的assertCredentialsMatch方法进行token和info中的密码是否一致。

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
        if (info == null) {
            info = this.doGetAuthenticationInfo(token);
            log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
            if (token != null && info != null) {
                this.cacheAuthenticationInfoIfPossible(token, info);
            }
        } else {
            log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
        }

        if (info != null) {
            this.assertCredentialsMatch(token, info);
        } else {
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }
        return info;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

assertCredentialsMatch方法是判断密码是否匹配的方法

  protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
        CredentialsMatcher cm = this.getCredentialsMatcher();
        if (cm != null) {
            if (!cm.doCredentialsMatch(token, info)) {
                String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
                throw new IncorrectCredentialsException(msg);
            }
        } else {
            throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication.  If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

执行SimpleAccountRealm类的doGetAuthenticationInfo方法:
通过UsernamePasswordToken类中的getUsername方法获得用户名username,再通过getUser方法通过用户名在realm中获取整个user对象。这一步完成了对username的判断。
然后判断其是否有锁,密码是否失效过期。将account返回成info。(account里存放了依据用户名获得的用户对象)

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken)token;
        SimpleAccount account = this.getUser(upToken.getUsername());
        if (account != null) {
            if (account.isLocked()) {
                throw new LockedAccountException("Account [" + account + "] is locked.");
            }

            if (account.isCredentialsExpired()) {
                String msg = "The credentials for account [" + account + "] are expired";
                throw new ExpiredCredentialsException(msg);
            }
        }
        return account;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

SimpleAccount中存放了简单的用户信息:

public class SimpleAccount implements Account, MergableAuthenticationInfo, SaltedAuthenticationInfo, Serializable {
    private SimpleAuthenticationInfo authcInfo;
    private SimpleAuthorizationInfo authzInfo;
    private boolean locked;
    private boolean credentialsExpired;
   
    public SimpleAccount(Object principal, Object credentials, String realmName) {
        this((PrincipalCollection)(principal instanceof PrincipalCollection ? (PrincipalCollection)principal : new SimplePrincipalCollection(principal, realmName)), credentials);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

总结:
用户名的校验:在SimpleAccountRealm类的doGetAuthenticationInfo方法中进行,并在这个方法中获得了对应用户名的用户对象accent。
密码的校验:AuthenticatingRealm的assertCredentialsMatch方法。是自动的不需要我们操作。
注:
里面的token存放的用户输入的值,info中存放的是realm中取到的值。

结构关系:
在这里插入图片描述
所以我们要连接数据库时,重写一个类继承AuthorizingRealm并覆盖相应方法,将用户名与制定数值进行校验。

2.3 自定义realm:

我们自己开发的过程中是需要连接数据库的,我们需要自定义一个realm,值为数据库内的数据。
从上面的源码我们知道,授权和认证的具体方法是在doGetAuthorizationInfodoGetAuthenticationInfo两个方法中实现的,所以我们需要的就是重写这两个方法实现目的。

public class custormerRealm extends AuthorizingRealm{
//    授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
//    认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//        1.先获取用户名
        String principal = (String) authenticationToken.getPrincipal();
//        2.进行用户名验证
        if("wyh".equals(principal)){
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal,"1234",this.getName());
            return authenticationInfo;
        }
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

代码中模拟了从数据库中拿取数据。
注:密码的校验是自动完成的,不需要我们自己写,但是我们日常的使用中会遇到密码加密的情况,这时就需要对shiro提供的密码校验方法进行一个改写。

2.4 shiro中的MD5和salt实现:

  1. MD5算法介绍:
  • 作用:一般用来 加密 或者 签名(校验和)
  • 特点:MD5算法不可逆(只能通过明文翻译成密文),如果内容相同则无论执行多少次md5生成的结果始终是一致的(利用这个特性可以进行对结果的校验,如果使用md5生成的结果相同,说明内容相同。这就是用来做签名的用处)
  • MD5破解:网络上的破解MD5使用的是穷举的算法,利用“如果内容相同则无论执行多少次md5生成的结果始终是一致的”这个特点进行无数次尝试,直至匹配到正确结果。
  • 加密结果:始终是一个16进制32位长度字符串
  1. salt介绍:
    字面意思是加盐,是在明文密码中再加一些字符串然后再进行MD5的加密,不过需要注意的是需要将添加的内容保存在数据库中,页面用户传入数据后,后台要先将添加的内容拼接到明文密码中,再进行MD5加密,然后与数据库中存储的加密好的密码进行匹配判断比较。

注:都是在service层中进行!

代码演示:

public class TestShiroMD5 {
    public static void main(String[] args) {
//        使用MD5加密
        Md5Hash md5Hash = new Md5Hash("1234");
        System.out.println(md5Hash);
        
//        使用MD5 + salt
        Md5Hash md5Hash1 = new Md5Hash("1234","X0#j2");
        System.out.println(md5Hash1);

//        使用MD5 + salt + hash散列
        Md5Hash md5Hash2 = new Md5Hash("1234","X0#j2",1024);
        System.out.println(md5Hash2);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

结果:可以看出,相同的密码经过不同的加密方法后得到的结果是不同的。
在这里插入图片描述
shiro中的实现:

  1. 重写realm
    在 realm 中进行身份认证的方法内部进行密码的校验。
public class CustomerMd5Realm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String) authenticationToken.getPrincipal();
        if (principal.equals("wyh")){
            return new SimpleAuthenticationInfo(principal,
                    //数据库中经过加密后的密码
                    "955452dbdeeb30414b372ceec8805d49",
                    //如果要加盐需要将盐的值标明
                    ByteSource.Util.bytes("X0#j2"),
                    this.getName());
        }
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  1. 使用:
public class TestCustomerMd5Realm {
    public static void main(String[] args) {
        //SecurityManager管理着认证、授权、会话管理,是shiro的核心
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //为我们的SecurityManager设置一个域,域中需要包含的是需要对比的数据
        CustomerMd5Realm realm = new CustomerMd5Realm();
        //为realm设置密码匹配器
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(1024);

        realm.setCredentialsMatcher(hashedCredentialsMatcher);
        defaultSecurityManager.setRealm(realm);

        //将安全管理器注入安全工具
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //获取subject,可以理解为一个用户,将一个用户抽象为一个Subject对象
        Subject subject = SecurityUtils.getSubject();
        //设置token用户信息,这里相当于我们从页面获取的数据
        UsernamePasswordToken wyh = new UsernamePasswordToken("wyh", "1234");

        try {
            subject.login(wyh);
            System.out.println("登录成功");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户名错误");
        } catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密码错误");
        }
    }
}
  • 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

核心是在 realm.setCredentialsMatcher,realm中使用默认的SimpleCredentialsMatcher进行密码的验证,其中验证的方法就是通过 equals 方法进行值的比较,因为用户输入的一定是明文密码,而数据库中存储的是加密后的密码,所以使用默认的 CredentialsMatcher匹配 肯定会有问题,我们需要重给realm一个CredentialsMatcher。
realm提供了setCredentialsMatcher()方法,我们可以自己设置 CredentialsMatcher。
只要我们在 realm 中设置了盐值后,会自动给我们的密码加盐。
核心代码:

		CustomerMd5Realm realm = new CustomerMd5Realm();
        // 为realm设置密码匹配器
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 设置加密的类型为md5
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 设置散列值
        hashedCredentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(hashedCredentialsMatcher);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3.授权:

授权,即访问控制,控制谁能访问哪些资源。一定是基于认证的基础之上发生的。

关键对象:
who 对 what 进行 how 操作。
who:主体即subject,主体是需要访问系统中资源的对象。
what:即Resource资源
how:权限,规定了主体对资源的操作许可,权限离开资源没有意义。

授权方式:

  1. RBAC 基于角色的访问控制,以角色为中心进行访问控制。
	if(subject.hasRole("admin")){
	//操作什么资源
	}
  • 1
  • 2
  • 3
  1. RBAC 基于资源的访问控制,以资源为中心进行访问控制。
	if(subject.isPermission("user:update:01")){//资源实例:比如控制这个subject拥有修改序号为01的用户的权限
	//对01用户进行修改
	}
	if(subject.isPermission("user:update:*")){//资源类型:控制这个subject拥有修改方法的权限
	//对用户进行修改
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

权限字符串:
权限字符串的规则是:资源标识符:操作:资源实例标识符,意思是对哪个资源的哪个实例具有什么操作。(* 代表所有,是最大的权限)

3.1 基于角色的访问控制

核心:给用户添加角色如 admin、user等

  1. 在 realm 的 doGetAuthorizationInfo 中为当前subject添加角色:
    通过这段代码:我们为当前的 subject 添加了两个角色:admin和user。
    并且我们可以通过 getPrimaryPrincipal 获得当前 subject 的身份信息。
	@Override
    //参数是当前subject的身份集合
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获得的是主身份
        String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
        System.out.println("身份信息: "+primaryPrincipal);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRole("admin");
        simpleAuthorizationInfo.addRole("user");
        return simpleAuthorizationInfo;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 在上面的认证代码后,添加此代码:
    首先通过 isAuthenticated() 方法判断当前 subject 是否通过认证,只有通过认证的 subject 才能进行角色判断。
    通过 hasRole()方法进行是否为单个角色的判断、
    通过 hasAllRoles()方法进行是否为多个角色的判断、
    通过 hasRoles()方法进行对传入集合中的每个角色进行判断。
	if (subject.isAuthenticated()){
            //基于单角色的角色控制
            System.out.println(subject.hasRole("user"));
            //基于多角色的角色控制
            System.out.println(subject.hasAllRoles(Arrays.asList("user","admin","super")));
            //是否具有其中一个角色
            boolean[] booleans = subject.hasRoles(Arrays.asList("user", "admin", "super"));
            for (boolean aBoolean : booleans) {
                System.out.println(aBoolean);
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

结果展示:
可以发现,每调用一次 hasRole()就会调用一次 realm 中的 doGetAuthorizationInfo()方法。(其他两种方法的底层就是实现了 hasRole 方法)
在这里插入图片描述

3.2 基于资源的访问控制:

  1. 在realm中为用户添加可访问的资源
//授权
    @Override
    //参数是当前subject的身份集合
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获得的是主身份
        String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
        System.out.println("身份信息: "+primaryPrincipal);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRole("admin");
        simpleAuthorizationInfo.addRole("user");

        simpleAuthorizationInfo.addStringPermission("user:*:*");
        simpleAuthorizationInfo.addStringPermission("food:delete");

        return simpleAuthorizationInfo;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  1. 可对用户权限进行判断:
    我们上面给当前 subject 的权限是 "user : * : * " 和 “food : delete”,说明当前subject对user中的所有资源可进行任意操作,而对于food只能进行 delete 操作。所以对food进行 update 操作是不行的。
        if (subject.isAuthenticated()){
            System.out.println(subject.isPermitted("user:delete:*"));
            boolean[] permitted = subject.isPermitted("user:*:01", "food:update:*");
            for (boolean b : permitted) {
                System.out.println(b);
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述

4.与springboot 的整合:

思路:
首先shiro需要拦截所有的请求,并对每一个请求进行判断是否认证通过,再对认证通过的请求进行授权处理。
使用 ShiroFilter 进行拦截操作。在 ShiroFilter 中使用SecurityManger 进行认证和授权操作。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/534044
推荐阅读
相关标签
  

闽ICP备14008679号