Shiro学习笔记(2)——身份验证之Realm

  • 环境准备
  • 什么是Realm
  • 为什么要用Realm
  • 自定义Realm
  • 多个Realm
  • 配置Authenticator和AuthenticationStrategy
  • 自定义AuthenticationStrategy验证策略
  • 多个Realm验证顺序

环境准备

  • 创建java工程
  • 需要的jar包

  • 大家也可以使用maven,参考官网

什么是Realm

  • 在我所看的学习资料中,关于Realm的定义,写了整整一长串,但是对于初学者来说,看定义实在是太头疼了。
  • 对于什么是Realm,我使用过之后,个人总结一下:shiro要进行身份验证,就要从realm中获取相应的身份信息来进行验证,简单来说,我们可以自行定义realm,在realm中,从数据库获取身份信息,然后和 用户输入的身份信息进行匹配。这一切都由我们自己来定义。

为什么要用Realm

Shiro学习笔记(1)——shiro入门中,我们将身份信息(用户名/密码/角色/权限)写在配置文件中,但是实际开发中,这些身份信息应该保存在数据中,因此我们需要自定义Realm来从数据中获取身份信息,进行验证。

自定义Realm

  • 定义一个MyRealm,继承AuthorizingRealm
package com.shiro.realm;

import java.util.HashSet;
import java.util.Set;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyRealm1 extends AuthorizingRealm{

    private static final transient Logger log = LoggerFactory.getLogger(Main.class);

    /**
     * 获取身份信息,我们可以在这个方法中,从数据库获取该用户的权限和角色信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("----------doGetAuthorizationInfo方法被调用----------");
        String username = (String) getAvailablePrincipal(principals);
        //我们可以通过用户名从数据库获取权限/角色信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //权限
        Set<String> s = new HashSet<String>();
        s.add("printer:print");
        s.add("printer:query");
        info.setStringPermissions(s);
        //角色
        Set<String> r = new HashSet<String>();
        r.add("role1");
        info.setRoles(r);

        return info;
    }
    /**
     * 在这个方法中,进行身份验证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        //用户名
        String username = (String) token.getPrincipal();
        log.info("username:"+username);
        //密码
        String password = new String((char[])token.getCredentials());
        log.info("password:"+password);
        //从数据库获取用户名密码进行匹配,这里为了方面,省略数据库操作
        if(!"admin".equals(username)){
            throw new UnknownAccountException();
        }
        if(!"123".equals(password)){
            throw new IncorrectCredentialsException();
        }
        //身份验证通过,返回一个身份信息
        AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username,password,getName());

        return aInfo;
    }

}
  • 让我们定义的Realm起作用,就要在配置文件中配置(shiro-realm.ini)
#声明一个realm
MyRealm1=com.shiro.realm.MyRealm1
#指定securityManager的realms实现
securityManager.realms=$MyRealm1
  • 测试
package com.shiro.realm;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {

    private static final transient Logger log = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {
        //获取SecurityManager的实例
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
        SecurityManager securityManager = factory.getInstance();

        SecurityUtils.setSecurityManager(securityManager);

        Subject currenUser = SecurityUtils.getSubject();

        //如果还未认证
        if(!currenUser.isAuthenticated()){
            UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
            token.setRememberMe(true);
            try {
                currenUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("没有该用户: " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info( token.getPrincipal() + " 的密码不正确!");
            } catch (LockedAccountException lae) {
                log.info( token.getPrincipal() + " 被锁定 ,请联系管理员");
            }catch (AuthenticationException ae) {
                //其他未知的异常
            }
        }

        if(currenUser.isAuthenticated())
            log.info("用户 "+currenUser.getPrincipal() +" 登录成功");

        //是否有role1这个角色
        if(currenUser.hasRole("role1")){
            log.info("有角色role1");
        }else{
            log.info("没有角色role1");
        }
        //是否有对打印机进行打印操作的权限
        if(currenUser.isPermitted("printer:print")){
            log.info("可以对打印机进行打印操作");
        }else {
            log.info("不可以对打印机进行打印操作");
        }
    }

}
  • 测试结果

从结果截图中,我们可以看到,自定义的Realm中的doGetAuthorizationInfo 方法被调用了两次,并且分别在currenUser.hasRole()currenUser.isPermitted 方法调用时调用

多个Realm

  • 有时候,我们需要进行多次身份验证,我们可以定义多个Realm,如同流水线一样,shiro会依次调用Realm
  • MyRealm1
package com.shiro.mutilrealm;

import java.util.HashSet;
import java.util.Set;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.shiro.realm.Main;

public class MyRealm1 extends AuthorizingRealm{

    private static final transient Logger log = LoggerFactory.getLogger(Main.class);

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        String username = (String) getAvailablePrincipal(principals);
        //通过用户名从数据库获取权限字符串
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //权限
        Set<String> s = new HashSet<String>();
        s.add("printer:print");
        s.add("printer:query");
        info.setStringPermissions(s);
        //角色
        Set<String> r = new HashSet<String>();
        r.add("role1");
        info.setRoles(r);

        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        log.info("MyRealm1开始认证。。。。。。");
        //用户名
        String username = (String) token.getPrincipal();
        log.info("username:"+username);
        //密码
        String password = new String((char[])token.getCredentials());
        log.info("password:"+password);
        //从数据库获取用户名密码进行匹配,这里为了方面,省略数据库操作
        if(!"admin".equals(username)){
            throw new UnknownAccountException();
        }
        if(!"123".equals(password)){
            throw new IncorrectCredentialsException();
        }
        //身份验证通过
        AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username,password,getName());

        return aInfo;
    }

}
  • MyRealm2和MyRealm1 代码其实基本上是一样的,直接复制一份即可。当然,如果有需求,我们可以自由地定义修改Realm。这里只做个示例而已。

配置Authenticator和AuthenticationStrategy

  • 这两个东东是啥玩意?

    上面我们配置了多个Realm进行身份验证,假设一下:MyRealm1 验证通过了,MyRealm2验证不通过怎么办,这就需要定义一个验证策略来处理这种情况。Strategy的意思就是策略。Authenticator就是验证器

  • 配置文件(shiro-mutil-realm.ini)
#声明一个realm
MyRealm1=com.shiro.mutilrealm.MyRealm1
MyRealm2=com.shiro.mutilrealm.MyRealm2

#配置验证器
authenticator = org.apache.shiro.authc.pam.ModularRealmAuthenticator
#配置策略
# AllSuccessfulStrategy 表示 MyRealm1和MyRealm2 认证都通过才算通过
authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy
#将验证器和策略关联起来
authenticator.authenticationStrategy = $authcStrategy
#配置验证器所使用的Realm
authenticator.realms=$MyRealm2,$MyRealm1

#把Authenticator设置给securityManager
securityManager.authenticator = $authenticator

##########################################################################
# 1. AtLeastOneSuccessfulStrategy :如果一个(或更多)Realm 验证成功,则整体的尝试被认
# 为是成功的。如果没有一个验证成功,则整体尝试失败。

# 2. FirstSuccessfulStrategy 只有第一个成功地验证的Realm 返回的信息将被使用。所有进一步
# 的Realm 将被忽略。如果没有一个验证成功,则整体尝试失败

# 3. AllSucessfulStrategy 为了整体的尝试成功,所有配置的Realm 必须验证成功。如果没有一
# 个验证成功,则整体尝试失败。

# ModularRealmAuthenticator 默认的是AtLeastOneSuccessfulStrategy
###########################################################################
  • 验证的策略有三种,在配置文件中我用注释都写好了,就不再详细说明了
  • 测试
package com.shiro.mutilrealm;

import java.util.List;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {

    private static final transient Logger log = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {
        //获取SecurityManager的实例
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-mutil-realm.ini");
        SecurityManager securityManager = factory.getInstance();

        SecurityUtils.setSecurityManager(securityManager);

        Subject currenUser = SecurityUtils.getSubject();

        //如果还未认证
        if(!currenUser.isAuthenticated()){
            UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
            token.setRememberMe(true);
            try {
                currenUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("没有该用户: " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info( token.getPrincipal() + " 的密码不正确!");
            } catch (LockedAccountException lae) {
                log.info( token.getPrincipal() + " 被锁定 ,请联系管理员");
            }catch (AuthenticationException ae) {
                //其他未知的异常
            }
        }

        if(currenUser.isAuthenticated())
            log.info("用户 "+currenUser.getPrincipal() +" 登录成功");

        //得到一个身份集合
        PrincipalCollection principalCollection = currenUser.getPrincipals();

    }

}
  • 运行结果

结果很明显,MyRealm1和MyRealm2依次执行

自定义AuthenticationStrategy(验证策略)

  • 上面我们使用了shiro自带的AuthenticationStrategy,其实我们也可以自己定义。
package com.shiro.authenticationstrategy;

import java.util.Collection;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.AbstractAuthenticationStrategy;
import org.apache.shiro.realm.Realm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.shiro.realm.Main;

public class MyAuthenticationStrategy extends AbstractAuthenticationStrategy{

    private static final transient Logger log = LoggerFactory.getLogger(MyAuthenticationStrategy.class);
    /**
     * 所有Realm验证之前调用
     */
    @Override
    public AuthenticationInfo beforeAllAttempts(
            Collection<? extends Realm> realms, AuthenticationToken token)
            throws AuthenticationException {
        log.info("===============beforeAllAttempts方法被调用==================");
        return super.beforeAllAttempts(realms, token);
    }
    /**
     * 每一个Realm验证之前调用
     */
    @Override
    public AuthenticationInfo beforeAttempt(Realm realm,
            AuthenticationToken token, AuthenticationInfo aggregate)
            throws AuthenticationException {
        log.info("===============beforeAttempt方法被调用==================");
        return super.beforeAttempt(realm, token, aggregate);
    }
    /**
     * 每一个Realm验证之后调用
     */
    @Override
    public AuthenticationInfo afterAttempt(Realm realm,
            AuthenticationToken token, AuthenticationInfo singleRealmInfo,
            AuthenticationInfo aggregateInfo, Throwable t)
            throws AuthenticationException {
        log.info("===============afterAttempt方法被调用==================");
        return super.afterAttempt(realm, token, singleRealmInfo, aggregateInfo, t);
    }
    /**
     * 所有Realm验证之后调用
     */
    @Override
    public AuthenticationInfo afterAllAttempts(AuthenticationToken token,
            AuthenticationInfo aggregate) throws AuthenticationException {
        log.info("===============afterAllAttempts方法被调用==================");
        return super.afterAllAttempts(token, aggregate);
    }

}

我们所继承的 AbstractAuthenticationStrategy 中,各个方法并不是抽象的,也就是说并一定要重写,我们可以根据需求重写需要的方法即可

  • 配置文件

    要让我们自定义的AuthenticationStrategy起作用,只要将上面配置文件(shiro-mutil-realm.ini)中

    authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy

    改为authcStrategy = com.shiro.authenticationstrategy.MyAuthenticationStrategy 即可

  • 测试代码不变
  • 结果

从截图中也可以清除的看到自定义的策略中,各个方法被调用的顺序。有了这些,我们就可以随心所欲的根据需求进行操作了

多个Realm验证顺序

  • 隐式排列

    • 当你配置多个realm的时候,处理的顺序默认就是你配置的顺序。
    • 这种情况通常就是只定义了realm,而没有配置securityManager的realms
  • 显式排列
    • 也就是显示的配置securityManager.realms,那么执行的顺序就是你配置该值的realm的顺序。
    • 通常更推荐显示排列。

我们可以简单的理解为,多个Realm验证的顺序,就是我们配置的顺序

时间: 2024-10-23 12:06:59

Shiro学习笔记(2)——身份验证之Realm的相关文章

Shiro:学习笔记(1)——身份验证

Shiro--学习笔记(1) 1.核心概念 1.Shiro不会自己去维护用户.维护权限:这些需要我们自己去设计/提供:然后通过相应的接口注入给Shiro.2.应用代码直接交互的对象是Subject,也就是说Shiro的对外核心是Subject: Subject:主体,代表了当前用户,不一定是一个具体的人,与当前交互的任何东西都是Subject,如网络爬虫.机器人等,是一个抽象概念.所有的Subject都会绑定到SecurityManager.可以把Subject认为是一个门面:SecurityM

shiro学习笔记_0600_自定义realm实现授权

博客shiro学习笔记_0400_自定义Realm实现身份认证 介绍了认证,这里介绍授权. 1,仅仅通过配置文件来指定权限不够灵活且不方便.在实际的应用中大多数情况下都是将用户信息,角色信息,权限信息 保存到了数据库中.所以需要从数据库中去获取相关的数据信息.可以使用 shiro 提供的JdbcRealm来实现,,也可以自定义realm来实现.使用jdbcRealm往往也不够灵活.所以在实际应用中大多数情况都是自定义Realm来实现. 2,自定义Realm 需要继承 AuthorizingRea

Shiro学习笔记(5)——web集成

Web集成 shiro配置文件shiroini 界面 webxml最关键 Servlet 測试 基于 Basic 的拦截器身份验证 Web集成 大多数情况.web项目都会集成spring.shiro在普通web项目和spring项目中的配置是不一样的.关于spring-shiro集成,能够參考Shiro学习笔记(3)--授权(Authorization) 中的JSP标签授权部分演示样例代码 本次介绍普通的web项目,不使用不论什么框架. shiro配置文件(shiro.ini) 创建web项目.

Shiro学习笔记(3)——授权(Authorization)

什么是授权 授权三要素 Shiro的三种授权方式 1 编码方式授权 2 基于注解的授权 3 JSP标签授权 1.什么是授权 授权,就是访问控制,控制某个用户在应用程序中是否有权限做某件事 2.授权三要素 权限 请看Shiro学习笔记(1)--shiro入门中权限部分内容 角色 通常代表一组行为或职责.这些行为演化为你在一个软件应用中能或者不能做的事情.角色通常是分配给用户帐户的,因此,通过分配,用户能够"做"的事情可以归属于各种角色 隐式角色:一个角色代表着一系列的操作,当需要对某一操

Shiro学习笔记(4)——ini 配置

ini 配置文件 在前面三个笔记中也有使用到ini配置文件,但是没有进行详细的解析,本次来介绍一下如何配置. ini配置文件其实和properties配置文件一样的使用方法,都是键值对的形式(key=value),#号代表注释 ini配置中主要配置有四大类:main,users,roles,urls [main] #提供了对根对象 securityManager 及其依赖的配置 securityManager=org.apache.shiro.mgt.DefaultSecurityManager

Apache Shiro学习笔记(六)FilterChain

鲁春利的工作笔记,好记性不如烂笔头 Apache Shiro学习笔记(七)IniWebEnvironment

Apache Shiro学习笔记(二)身份验证

鲁春利的工作笔记,好记性不如烂笔头 身份验证,即在应用中谁能证明他就是他本人,应用系统中一般通过用户名/密码来证明.在 shiro 中,用户需要提供principals(身份)和credentials(证明)给shiro,从而应用能验证用户身份:    principals:身份,即主体的标识属性,可以是任何东西,如用户名.邮箱等,唯一即可.一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号.    credentials:证明/凭

Shiro 学习笔记(二)——shiro身份验证

身份验证: 在应用中证明他就是他本人.一般上用身份证.用户/密码 来证明. 在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份 Principals: 身份,即主体的标识属性,只要是主体的

Shiro 学习笔记(一)——shiro简介

Apache Shiro 是一个安全框架.说白了,就是进行一下 权限校验,判断下这个用户是否登录了,是否有权限去做这件事情. Shiro 可以帮助我们完成:认证.授权.加密.会话管理.与web 集成.缓存等. 其基本功能点 如下图: Authentication  : 身份认证/登录,验证用户是不是拥有相应的身份 Authorization : 授权,即权限验证,验证某个已认证的用户是否拥有某个权限:即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色,或者 细粒度的验证某个用户对某个