shiro学习笔记(2) -- 初识Realm

2. 初识Realm

1.什么是Realm

​ Realm 是可以访问程序特定的安全数据如用户、角色、权限等的一个组件。Realm 通常和数据源是一对一的对应关系,如关系数据库,LDAP 目录,文件系统,或其他类似资源。如JDBC,文件IO,Hibernate 或JPA,或其他数据访问API。Shiro 从 Realm 获取安全数据(如用户、角色、权限), shiro要进行身份验证,就要从realm中获取相应的身份信息来进行验证。

2.Realm Configuration

​ 如果使用 Shiro 的 ini 配置文件,你可以在[main]区域内像配置其它对象一样定义和引用Realms,但是 Realm 在 secrityManager上的配置有两种方式:明确方式和隐含方式。

2.1 明确指定(显式)

#声明一个 real
fooRealm = com.company.foo.Realm
barRealm = com.company.another.Realm
bazRealm = com.company.baz.Realm

# `$`引用声明自定义Realm, 指定 securityManager 的 realms 实现
securityManager.realms = $fooRealm, $barRealm, $bazRealm

明确设置是确定性的,你可以非常确切地知道哪个 realm 在使用并且知道它们执行的顺序。

2.2隐含方式(隐式)

blahRealm = com.company.blah.Realm
fooRealm = com.company.foo.Realm
barRealm = com.company.another.Realm

这种方法可能引发意想不到的行为,如果你改变 realm 定义的顺序的话。建议你避免使用此方法,并使用显式分配,它拥有确定的行为。

3. 配置单Realm

3.1继承Realm接口

public class MyRealm implements Realm
{
    /**
     * 返回一个唯一的 Realm 名字
     * @return
     */
    public String getName()
    {
        return "myrealm1";
    }

    /***
     * 判断此 Realm 是否支持此 Token
     * @param authenticationToken
     * @return
     */
    public boolean supports(AuthenticationToken authenticationToken)
    {
        // 仅支持 UsernamePasswordToken 类型的 Token
        return authenticationToken instanceof UsernamePasswordToken;
    }

    /**
     * 根据 Token 获取认证信息
     * @param token
     * @return
     * @throws AuthenticationException
     */
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
    {
        // 获取用户名
        String userName = token.getPrincipal().toString();

        // 获取用户密码
        // (这里必须这样先转换为char[], 然后在转换为String, 因为token.getCredentials() 底层调用了public char[] getPassword()方法)
        String password =  new String((char[])token.getCredentials());

        if(!"zhang".equals(userName)) {
            // 如果用户名错误
            throw new UnknownAccountException();
        }
        if(!"123".equals(password)) {
            // 如果密码错误
            throw new IncorrectCredentialsException();
        }

        // 如果身份认证成功,返回一个 AuthenticationInfo 实现
        return new SimpleAuthenticationInfo(userName, password, getName());
    }
}

3.2 ini 配置文件指定自定义 Realm 实现

#声明一个 realm 
myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1 
#指定 securityManager 的 realms 实现
securityManager.realms=$myRealm1

3.3 测试用例

​ 和上一篇的测试用例一样,只是改变了引入文件。传送门

4.配置多Realm

和单个配置差不多,只是多定义一个Realm,然后ini文件中引用。

#声明一个 real
fooRealm = com.company.foo.Realm
barRealm = com.company.another.Realm
bazRealm = com.company.baz.Realm

# 这个地方就是引用多个
# `$`引用声明自定义Realm, 指定 securityManager 的 realms 实现
securityManager.realms = $fooRealm, $barRealm, $bazRealm

5.JDBC Realm的使用

5.1 添加依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.38</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>0.2.23</version>
</dependency>

5.2 初始数据库

drop database if exists shiro;
create database shiro;
use shiro;

create table users (
  id bigint auto_increment,
  username varchar(100),
  password varchar(100),
  password_salt varchar(100),
  constraint pk_users primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_users_username on users(username);

create table user_roles(
  id bigint auto_increment,
  username varchar(100),
  role_name varchar(100),
  constraint pk_user_roles primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_user_roles on user_roles(username, role_name);

create table roles_permissions(
  id bigint auto_increment,
  role_name varchar(100),
  permission varchar(100),
  constraint pk_roles_permissions primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_roles_permissions on roles_permissions(role_name, permission);

insert into users(username,password)values('zhang','123');

5.3 ini 配置(shiro-jdbc-realm.ini)

# 直接引用数据库默认的JdbcRealm,系统会自动识别users表
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm 
dataSource=com.alibaba.druid.pool.DruidDataSource 
dataSource.driverClassName=com.mysql.jdbc.Driver 
dataSource.url=jdbc:mysql://localhost:3306/shiro 
dataSource.username=root 
dataSource.password= 
jdbcRealm.dataSource=$dataSource 
securityManager.realms=$jdbcRealm

5.4 测试用例

​ 和上一篇的测试用例一样,只是改变了引入文件。

6 自定义Realm

6.1 定义一个MyRealm,继承AuthorizingRealm

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;
    }

}

6.2 让我们定义的Realm起作用,就要在配置文件中配置(shiro-realm.ini)

#声明一个realm  
MyRealm1=com.shiro.realm.MyRealm1 
#指定securityManager的realms实现  
securityManager.realms=$MyRealm1

6.3 测试

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("不可以对打印机进行打印操作");
        }
    }

}

7 Authenticator 及 AuthenticationStrategy

7.1 Authenticator

Authenticator 的职责是验证用户帐号,是 Shiro API 中身份验证核心的入口点,可以理解为它是一个验证器。

7.2 AuthenticationStrategy

AuthenticationStrategy 是shiro 的认证策略

  1. **AtLeastOneSuccessfulStrategy **:如果一个(或更多)Realm 验证成功,则整体的尝试被认为是成功的。如果没有一个验证成功,则整体尝试失败。
  2. FirstSuccessfulStrategy :只有第一个成功地验证的Realm 返回的信息将被使用。所有进一步的Realm 将被忽略。如果没有一个验证成功,则整体尝试失败
  3. AllSucessfulStrategy :为了整体的尝试成功,所有配置的Realm 必须验证成功。如果没有一个验证成功,则整体尝试失败。

ModularRealmAuthenticator 默认的是AtLeastOneSuccessfulStrategy

文件的配置

#声明一个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

8 自定义AuthenticationStrategy(验证策略)

8.1 上面我们使用了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);
    }
}

8.2 配置文件

要让我们自定义的AuthenticationStrategy起作用,只要将上面配置文件(shiro-mutil-realm.ini)中 
authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy 
改为authcStrategy = com.shiro.authenticationstrategy.MyAuthenticationStrategy 即可

8.3 测试用例

​ 和上一篇的测试用例一样,只是改变了引入文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值