Shiro之身份验证

jopen 10年前

principals即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。credentials是证明/凭证,即只有主体知道的安全值,如密码/数字证书等。最常见的principals和credentials组合就是用户/密码了。

下面我们来看一个认证的例子,由于我们是用maven构建的实例,所以需要在pom.xml中添加依赖:

<dependency>      <groupId>junit</groupId>      <artifactId>junit</artifactId>      <version>4.11-20120805-1225</version>  </dependency>      <dependency>      <groupId>commons-logging</groupId>      <artifactId>commons-logging</artifactId>      <version>1.1.3</version>  </dependency>  <dependency>      <groupId>org.apache.shiro</groupId>      <artifactId>shiro-core</artifactId>      <version>1.2.3</version>  </dependency>

另外,准备一些用户微分凭据,shiro.ini:

[users]  zhang=123  wang=123

测试用例:

package org.shiro;    import junit.framework.Assert;  import org.apache.shiro.SecurityUtils;  import org.apache.shiro.authc.AuthenticationException;  import org.apache.shiro.authc.UsernamePasswordToken;  import org.apache.shiro.config.IniSecurityManagerFactory;  import org.apache.shiro.subject.Subject;  import org.apache.shiro.util.Factory;  import org.junit.Test;    public class ShiroTest1{      @Test      public void shiro_test1(){          //获取SecurityManager工厂,此处使用ini配置文件初始化SecurityManager          Factory<org.apache.shiro.mgt.SecurityManager> factory =                   new IniSecurityManagerFactory("classpath:shiro.ini");          //得到SecurityManager实例并绑定给SecurityUtils          org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();          SecurityUtils.setSecurityManager(securityManager);          //得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)          Subject subject = SecurityUtils.getSubject();          UsernamePasswordToken token = new UsernamePasswordToken("zhang","123");          try {              subject.login(token);          } catch (AuthenticationException e) {              System.err.println(e.getMessage());          }          //断言用户已经登录          Assert.assertEquals(true, subject.isAuthenticated());          //退出          subject.logout();      }  }

从上例中,我们可以看到shiro的身份认证流程,如果还没有明白,可以看看下图:

  1. 首先调用Subject.login(token)进行登录,其会自动委托给SecurityManager,调用之前必须通过SecurityUtils.setSecurityManager()设置

2.    SecurityManager负责真正的身份验证逻辑,它会委托给Authenticator进行身份验证

3.    Authenticator才是真正的身份验证者,shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现

4.    Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证。

5.    Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此外可以配置多个Realm,将按照相应的顺序及策略进行访问。

 

Realm

Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法。也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作。可以把Realm看成DataSource,即安全数据源。如我们前面的例子使用ini配置,它使用的是org.apache.shiro.realm.text.IniRealm。

org.apache.shiro.realm.Realm接口如下:

public interface Realm {      String getName();                                //返回一个唯一的Realm名字      boolean supports(AuthenticationToken token);     //判断此Realm是否支持此Token      AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)              throws AuthenticationException;          //根据Token获取认证信息  }

单Realm配置实例如下:

MyRealm.java:

package org.shiro;    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.realm.Realm;    public class MyRealm implements Realm {        public String getName() {          return "myRealm";      }        public boolean supports(AuthenticationToken token) {          return token instanceof UsernamePasswordToken;      }        public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)              throws AuthenticationException {          System.out.println("myrealm~~~~~~~~~~~~~~~~~~~~~~~~~~");          //得到用户名            String username = (String)token.getPrincipal();           //得到密码            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());        }  }

shiro-realm.ini:

myRealm=org.shiro.MyRealm  securityManager.realms=$myRealm

ShiroTest2.java:

package org.shiro;    import junit.framework.Assert;  import org.apache.shiro.SecurityUtils;  import org.apache.shiro.authc.AuthenticationException;  import org.apache.shiro.authc.UsernamePasswordToken;  import org.apache.shiro.config.IniSecurityManagerFactory;  import org.apache.shiro.subject.Subject;  import org.apache.shiro.util.Factory;  import org.junit.Test;    public class ShiroTest2{        @Test      public void shiro_test1(){          //获取SecurityManager工厂,此处使用ini配置文件初始化SecurityManager          Factory<org.apache.shiro.mgt.SecurityManager> factory =                   new IniSecurityManagerFactory("classpath:shiro-realm.ini");          //得到SecurityManager实例并绑定给SecurityUtils          org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();          SecurityUtils.setSecurityManager(securityManager);          //得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)          Subject subject = SecurityUtils.getSubject();          UsernamePasswordToken token = new UsernamePasswordToken("zhang","123");          try {              subject.login(token);          } catch (AuthenticationException e) {              System.err.println(e.getMessage());          }          //断言用户已经登录          Assert.assertEquals(true, subject.isAuthenticated());          //退出          subject.logout();      }  }

多Realm配置实例如下:

MyRealm1.java

package org.shiro;    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.realm.Realm;    public class MyRealm1 implements Realm {        public String getName() {          return "myRealm1";      }        public boolean supports(AuthenticationToken token) {          return token instanceof UsernamePasswordToken;      }        public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)              throws AuthenticationException {          System.out.println("myRealm1--------------------");          //得到用户名            String username = (String)token.getPrincipal();           //得到密码            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());        }  }

MyRealm2.java:

package org.shiro;    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.realm.Realm;    public class MyRealm2 implements Realm {        public String getName() {          return "myRealm2";      }        public boolean supports(AuthenticationToken token) {          return token instanceof UsernamePasswordToken;      }        public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)              throws AuthenticationException {          System.out.println("myRealm2--------------------");          //得到用户名            String username = (String)token.getPrincipal();           //得到密码            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());        }  }

shiro-multi-realm.ini:

myRealm1=org.shiro.MyRealm1  myRealm2=org.shiro.MyRealm2  securityManager.realms=$myRealm1,$myRealm2

ShiroTest3.java

package org.shiro;    import junit.framework.Assert;  import org.apache.shiro.SecurityUtils;  import org.apache.shiro.authc.AuthenticationException;  import org.apache.shiro.authc.UsernamePasswordToken;  import org.apache.shiro.config.IniSecurityManagerFactory;  import org.apache.shiro.subject.Subject;  import org.apache.shiro.util.Factory;  import org.junit.Test;    public class ShiroTest3{        @Test      public void shiro_test1(){          //获取SecurityManager工厂,此处使用ini配置文件初始化SecurityManager          Factory<org.apache.shiro.mgt.SecurityManager> factory =                   new IniSecurityManagerFactory("classpath:shiro-multi-realm.ini");          //得到SecurityManager实例并绑定给SecurityUtils          org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();          SecurityUtils.setSecurityManager(securityManager);          //得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)          Subject subject = SecurityUtils.getSubject();          UsernamePasswordToken token = new UsernamePasswordToken("zhang","123");          try {              subject.login(token);          } catch (AuthenticationException e) {              System.err.println(e.getMessage());          }          //断言用户已经登录          Assert.assertEquals(true, subject.isAuthenticated());          //退出          subject.logout();      }  }

注意ini配置中,都是通过$name来引用自定义的Realm的。另外,securityManager会按照realms指定的顺序进行身份认证。如果删除了“securityManager.realms=$myRealm1,$myRealm2”,那么securityManager会按照realm声明的顺序进行使用(即无需设置realms属性,其会自动发现)。当我们显示指定realm后,其他没有指定realm将被忽略。

 

shiro默认提供的Realm

以后一般继承AuthorizingRealm即可,其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。

 

JDBC Realm使用

示例,我们先在pom.xml中添加两年操作数据库的依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">      <modelVersion>4.0.0</modelVersion>      <groupId>apl</groupId>      <artifactId>shiro-test</artifactId>      <version>0.0.1-SNAPSHOT</version>        <dependencies>          <dependency>              <groupId>junit</groupId>              <artifactId>junit</artifactId>              <version>4.11-20120805-1225</version>          </dependency>          <dependency>              <groupId>commons-logging</groupId>              <artifactId>commons-logging</artifactId>              <version>1.1.3</version>          </dependency>          <dependency>              <groupId>org.apache.shiro</groupId>              <artifactId>shiro-core</artifactId>              <version>1.2.3</version>          </dependency>          <dependency>              <groupId>mysql</groupId>              <artifactId>mysql-connector-java</artifactId>              <version>5.1.25</version>          </dependency>          <dependency>              <groupId>com.alibaba</groupId>              <artifactId>druid</artifactId>              <version>0.2.23</version>          </dependency>      </dependencies>  </project>

shiro-jdbc-realm.ini

dataSource=com.alibaba.druid.pool.DruidDataSource  dataSource.driverClassName=com.mysql.jdbc.Driver  dataSource.url=jdbc:mysql://localhost:3306/shiro  dataSource.username=root  dataSource.password=root  jdbcRealm.dataSource=$dataSource  jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm  securityManager.realms=$jdbcRealm

ShiroTest4.java测试用例:

public class ShiroTest4 {      @Test      public void testJDBCRealm() {          //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager          Factory<org.apache.shiro.mgt.SecurityManager> factory =                  new IniSecurityManagerFactory("classpath:shiro-jdbc-realm.ini");            //2、得到SecurityManager实例 并绑定给SecurityUtils          org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();          SecurityUtils.setSecurityManager(securityManager);            //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)          Subject subject = SecurityUtils.getSubject();          UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");            try {              //4、登录,即身份验证              subject.login(token);          } catch (AuthenticationException e) {              //5、身份验证失败              e.printStackTrace();          }            Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录            //6、退出          subject.logout();      }    }