Shrio认证详解+自定义Realm

Authentication(身份认证)是Shiro权限控制的第一步,用来告诉系统你就是你。

在提交认证的时候,我们需要给系统提交两个信息:

Principals:是一个表示用户的唯一属性,可以是用户名,邮箱之类的。

Credentials:是证明用户身份的证书,可以是密码或者指纹之类的。

认证主要分为三步:

1、收集认证信息

2、提交认证信息

3、如果认证成功,则允许访问,否则就拒绝访问或者重试。

收集认证信息

入门的例子中,使用了一个UsernamePasswordToken来收集用户的用户名和密码,用来登陆。

这个类支持最简单的用户名和密码登陆。实现了org.apache.shiro.authc.AuthenticationToken接口。

AuthenticationToken接口是认证系统的基础,只有getCredentials(),getPrincipal()两个方法用来获取基本的认证信息。

HostAuthenticationToken和RememberMeAuthenticationToken是它的两个子接口。

HostAuthenticationToken只有一个getHost()方法用来获取请求的地址信息。

RememberMeAuthenticationToken只有一个isRememberMe()方法用来标记用户是否需要记住我。

然后还有两个子类UsernamePasswordToken和CasToken提供了基本的实现。其中CasToken已经被废弃了。

例:

//Example using most common scenario of username/password pair:
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//"Remember Me" built-in: 
token.setRememberMe(true);

提交认证信息:

收集好认证信息之后,保存为AuthenticationToken的一个实例,我们需要提交这个认证信息来进行认证。

进行认证前,我们首先需要获取当前用户(Subject)。然后调用login方法来进行登陆。

例:

Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);

处理结果:

如果登陆成功的话,则不会有什么异常,此时如果调用isAuthenticated()方法,则会返回true。

如果登陆失败的话,则被抛出异常,在SHiro中,提供了很多异常类,可以用来捕捉具体的异常。

例子:

try {
    currentUser.login(token);
} catch ( UnknownAccountException uae ) { ...
} catch ( IncorrectCredentialsException ice ) { ...
} catch ( LockedAccountException lae ) { ...
} catch ( ExcessiveAttemptsException eae ) { ...
} ... catch your own ...
} catch ( AuthenticationException ae ) {
    //unexpected error?
}

//No problems, continue on as expected...

可以看到,前面出现了Remembered和Authenticated。那这两种有什么区别呢?

需要注意的是,这是两种互斥的情况,当我们在登陆的时候,登陆成功之后,我们Authenticated返回的是true。

选择Remeber me的时候,我们下次可以不登陆直接访问,而我们下次登陆之后,Remembered返回的是true,但是Authenticated返回的是false。

在登陆之后,我们可以可以用logout()方法来注销。这时候用户的信息都会被清空,包括保存在Cookie中的RemeberMe信息还有session也会被无效。

上面我们就简单的讲述了一下在代码中实现登陆验证的流程。具体实例可以参考入门的例子。

那么,在Shrio内部是怎么样的一个认证流程呢?大概可以用下图来概括:

第一步:在代码中调用login方法,传递构造好的AuthenticationToken实例。

第二步:通过一个DelegatingSubject来分发认证请求给SecurityManager

第三步:SecurityManager容器会把接收到的token简单的转发给它内部的认证器实例通过调用认证器的authenticate(token)方法。

这通常是一个ModularRealmAuthenticator实例,用来支持多个Realm。

第四步:如果有定义多个Realm则ModularRealmAuthenticator会初始化一个支持多个Realm的认证器,通过配置的AuthenticationStrategy。

第五步:每一个配置的Realm都会被检测是否支持提交的AuthenticationToken,如果支持的话就会调用getAuthenticationInfo方法,从Realm中获取数据来跟提交的token进行验证。

验证器

在SecurityManager中,默认使用ModularRealmAuthenticator实例,它不仅仅支持单Realm还支持多个Realm。

如果实在单个Realm的情况下,ModualrRealmAuthenticator会直接调用这个Realm来尝试验证。

如果我们需要定义自己的验证器的话,可以通过在配置文件的[main]中如下定义:

[main]
...
authenticator = com.foo.bar.CustomAuthenticator
securityManager.authenticator = $authenticator

验证策略

如果是只有一个Realms的时候,则不需要验证策略。

如果是有两个以上的Realm的时候,ModularRealmAuthenticator依赖于AuthenticationStrategy组件来决定认证成功或者失败的条件,

比如是一个成功就成功还是要都成功才是成功之类的。。。。

在Shiro里面已经有三个认证策略的实现

AtLeastOneSuccessfulStrategy:只要有一个或一个以上的认证成功就表示认证成功

FirstSuccessfulStrategy;只有第一个认证成功的时候才表示认证成功

AllSuccessfulStrategy:只有所有的都认证成功的时候才表示认证成功。

在ModularRealmAuthenticator默认使用AtLeastOneSuccessfulStrategy的验证策略。当然也可以通过下面的方式定义其他的验证策略。

[main]
...
authcStrategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy
securityManager.authenticator.authenticationStrategy = $authcStrategy
...

Realm认证顺序

在定义了多个Realm的时候,如果有多个Realm支持当前的AuthenticationToken,则会依次调用Realm的getAuthenticationInfo方法。

调用的顺序分为两种:

隐式的:

如果在SecurityManager中定义了如下的几个Realm,则会按照他们定义的顺序去调用。

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

此时的效果就等于下面的语句。

securityManager.realms = $blahRealm, $fooRealm, $barRealm、

显示的:

就如上面的一样,如果在securityManager.realms中配置的时候,改变realm的配置顺序,则会按照这个配置顺序来调用,这就是显示的配置。

blahRealm = com.company.blah.Realm
...
fooRealm = com.company.foo.Realm
...
barRealm = com.company.another.Realm
securityManager.realms = $fooRealm, $barRealm, $blahRealm
...

域认证

前面说了下Realm的认证策略和认证顺序,那么,在Realm认证的时候,究竟发生什么事情了呢?

supporting AuthenticationTokens

在Realm被用来尝试认证登陆的时候首先会调用supports方法,来检测是否能解析这个AuthenticationToken,决定是否进行认证。

Handing supported AuthenticationTokens

如果这个realm支持提交的AuthenticationTokens的话,解析器会调用Realm的getAuthenticationINfo(token)方法,来尝试认证,

这个方法大概做了下面这些事情:

1、检测token中的唯一用户标识

2、基于唯一标识 去数据源中查找

3、确保提供的证书跟数据源中保存的一样

4、如果证书一样,就封装一个AuthenticationINfo实例返回

5、如果证书不匹配,则抛出一个AuthenticationException异常

下面,我们来看看Shrio提供的Realm的类结构:

org.apache.shiro.realm.Realm(I):base
  org.apache.shiro.realm.CachingRealm(Abstract):提供缓存支持
    org.apache.shiro.realm.AuthenticatingRealm(Abstract):提供认证支持
      org.apache.shiro.realm.AuthorizingRealm(Abstract):提供授权支持
        org.apache.shiro.realm.SimpleAccountRealm(C):简单的用户名密码支持
          org.apache.shiro.realm.text.TextConfigurationRealm(C):支持Text文件的简单用户名密码支持
            org.apache.shiro.realm.text.IniRealm(C):通过INI文件的简单用户名密码支持(默认使用这个)
            org.apache.shiro.realm.text.PropertiesRealm(C):支持属性文件的简单用户名密码支持
        org.apache.shiro.realm.jdbc.JdbcRealm(C):支持通过JDBC认证

 在系统默认的情况下,系统使用的是IniRealm这个实现,可以从INI配置文件的[users]和[roles]两个节中读取用户信息和权限信息。

如果我们需要定义自己的Realm实现的话,一般都是继承AuthorizingRealm。

稍候,我们将简单介绍下如果从db中来实现认证。

证书匹配

前面我们说过,Realm需要去匹配用户提交的证书跟数据源中存储的证书是否匹配,如果匹配的话就认为是认证成功。

在获得用户唯一标识后,系统回去Realm会去检索用户的证书,然后通过CredentialsMatcher来检测证书是否匹配。

Shrio中也提供了一些证书的匹配器可以直接拿来使用,使用下面的方法变更默认的匹配器:

[main]
...
customMatcher = com.company.shiro.realm.CustomCredentialsMatcher
myRealm = com.company.shiro.realm.MyRealm
myRealm.credentialsMatcher = $customMatcher
...

Simple Equality Check

默认情况下所有提供的Realm实现都是使用SimpleCredentialsMatcher来检测证书是否匹配。

不过一般情况下都不需要变更,因为默认的就足够了。

Hashing Credentials

相比使用原始的数据存储起来,拿来匹配,我们更愿意将证书加密进行存储来进行匹配。那么如何使用呢?

在Shiro中提供了几个HashedCredentialsMatcher的子类,用来实现这个功能。包括MD5、SHA-256等等的加密方式。

我们可以通过下面的配置方式:

[main]
...
credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
# base64 encoding, not hex in this example:
credentialsMatcher.storedCredentialsHexEncoded = false
credentialsMatcher.hashIterations = 1024
# This next property is only needed in Shiro 1.0.  Remove it in 1.1 and later:
credentialsMatcher.hashSalted = true

...
myRealm = com.company.....
myRealm.credentialsMatcher = $credentialsMatcher
...

需要注意的是,这种情况下在Realm的实现中需要返回一个SaltedAuthenticationInfo,而不是普通的AuthenticationInfo,因为在用户提交认证的时候,需要获取相同的salt来进行加密,进行匹配认证。

那这个salt(盐)是用来干嘛的呢?

这是因为在进行MD5之类加密的时候,还是可以进行破解的,但是如果加入一个变量来进行加密之后,就基本上是无法破解了(不知道这个SALT的情况下)。

下面我们就做一个通过JDBC来认证的登陆认证程序。点此看源码

首先我们需要一个用户表,脚本如下:

CREATE DATABASE `db_shiro` 

USE `db_shiro`;

DROP TABLE IF EXISTS `users`;

CREATE TABLE `users` (
  `id` int(4) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

insert  into `users`(`id`,`username`,`password`) values (1,'fuwh','123456');

我们需要定义一个shiro_jdbc.ini文件如下

[main]
dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
dataSource.driverClass=com.mysql.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://localhost:3306/db_shiro
dataSource.user=root
dataSource.password=rootadmin

;this is comment read from mysql
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource=$dataSource
securityManager.realms=$jdbcRealm

编写登陆认证代码:

package com.fuwh.demo;

import org.apache.shiro.SecurityUtils;
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 ShiroDemo02 {
    
    private static Logger log=LoggerFactory.getLogger(ShiroDemo02.class);
    public static void main(String[] args) {
        //取得SecurityManager工厂
        Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro_jdbc.ini");
        //取得SecurityManager实例
        SecurityManager securityManager=factory.getInstance();
        //将securityManager绑定到SecurityUtil
        SecurityUtils.setSecurityManager(securityManager);

        /*    至此为止,简单的从mysql数据库读取realm信息的shiro环境就配置好了    */

        //取得当前用户
        Subject currentUser=SecurityUtils.getSubject();
        
        //使用shiro来进行登陆验证
        if(!currentUser.isAuthenticated()) {
            UsernamePasswordToken token=new UsernamePasswordToken("fuwh","123456");
            try {
                currentUser.login(token);
                log.info("登陆成功!!!");
            } catch (Exception e) {
                e.printStackTrace();
                log.error("认证失败...");
            }
        }
        
        currentUser.logout();
    }
}

执行结果:

2017-08-26 15:16:03,357 [main] INFO  [com.mchange.v2.log.MLog] - MLog clients using log4j logging.
2017-08-26 15:16:04,107 [main] INFO  [com.mchange.v2.c3p0.C3P0Registry] - Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10]
2017-08-26 15:16:04,436 [main] INFO  [org.apache.shiro.config.IniSecurityManagerFactory] - Realms have been explicitly set on the SecurityManager instance - auto-setting of realms will not occur.
2017-08-26 15:16:04,560 [main] INFO  [com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource] - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hgetj59q83i1dm1es8ork|67f89fa3, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hgetj59q83i1dm1es8ork|67f89fa3, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/db_shiro, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
2017-08-26 15:16:05,040 [main] INFO  [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler...
2017-08-26 15:16:05,056 [main] INFO  [com.fuwh.demo.ShiroDemo02] - 登陆成功!!!

此时,已经可以从表中去验证登陆了。那处理流程又是什么样的呢?

首先我们使用shiro_jdbc.ini来初始化了SecurityManager,在配置文件中,我们定义了连接池信息,还有jdbcRealm,同时在SecurityManager中指定了realm为定义的JjdbcRealm,这时候,其实shiro使用的SecurityManager是一个RealmSecurityManager的实例。而当我们登陆的时候,则会通过配置的jdbcRealm来从数据库中取得用户信息来进行认证。那是怎么取得呢?我们明明没有写sql什么的。

其实,看org.apache.shiro.realm.jdbc.JdbcRealm的源码可以看到,在这个类里面定义了很多的静态sql变量,点此查看点此查看sql内容

其中比较重要的是AuthenticationQuery这个字段,默认情况下它的值是等于Default_Authentication_query。

Default_Authentication_Query="select password from users where username=?";

所以在默认情况下,它会从users这个表中通过username这个key来查找password。从而拿来跟我们提交的密码来进行匹配验证。

如果我们不想使用默认的数据库,默认的表名,默认的列名的话,也可以通过在配置文件中重写AuthenticationQuery的值来个性化sql文。

首先修改新建一个表members:

USE `db_shiro`;

DROP TABLE IF EXISTS `members`;

CREATE TABLE `members` (
  `id` INT(4) NOT NULL AUTO_INCREMENT,
  `userName` VARCHAR(20) DEFAULT NULL,
  `pass` VARCHAR(100) DEFAULT NULL,
  UNIQUE KEY `id` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;


INSERT  INTO `members`(`id`,`userName`,`pass`) VALUES (1,'fuwh','123');

然后修改shiro_jdbc_sql.ini配置文件:

[main]
dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
dataSource.driverClass=com.mysql.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://localhost:3306/db_shiro
dataSource.user=root
dataSource.password=rootadmin

;this is comment read from mysql
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource=$dataSource
jdbcRealm.authenticationQuery=select pass from members where userName=? 
securityManager.realms=$jdbcRealm

修改认证程序的配置文件:

package com.fuwh.demo;

import org.apache.shiro.SecurityUtils;
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 ShiroDemoSql02 {
    
    private static Logger log=LoggerFactory.getLogger(ShiroDemoSql02.class);
    public static void main(String[] args) {
        //取得SecurityManager工厂
        Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro_jdbc_sql.ini");
        //取得SecurityManager实例
        SecurityManager securityManager=factory.getInstance();
        //将securityManager绑定到SecurityUtil
        SecurityUtils.setSecurityManager(securityManager);

        /*    至此为止,简单的从mysql数据库读取realm信息的shiro环境就配置好了    */
        
        //取得当前用户
        Subject currentUser=SecurityUtils.getSubject();
        
        //使用shiro来进行登陆验证
        if(!currentUser.isAuthenticated()) {
            UsernamePasswordToken token=new UsernamePasswordToken("fuwh","123");
            try {
                currentUser.login(token);
                log.info("登陆成功!!!");
            } catch (Exception e) {
                e.printStackTrace();
                log.error("认证失败...");
            }
        }
        
        currentUser.logout();
    }
}

后面我们会讲到角色认证也是同样的道理。

自定义Realm

上面我们使用的是Shiro提供的默认的Realm,下面我们自定义一个从数据库中读取信息的Realm,通过继承AuthorizingRealm。

package com.fuwh.realm;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import com.fuwh.util.DbUtil;

public class MyJdbcRealm extends AuthorizingRealm{

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // TODO Auto-generated method stub
        Connection conn=DbUtil.getConnection();
        String sql="select * from members2 where username=?";
        try {
            PreparedStatement ps=conn.prepareStatement(sql);
            ps.setString(1, token.getPrincipal().toString());
            ResultSet rs=ps.executeQuery();
            while(rs.next()) {
                AuthenticationInfo info=new SimpleAuthenticationInfo(rs.getString("username"),rs.getString("password"),"salt");
                return info;
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return null;
    }
    
}

修改配置文件

[main]
myJdbcRealm=com.fuwh.realm.MyJdbcRealm
securityManager.realms=$myJdbcRealm

编写登陆类:

package com.fuwh.demo;

import org.apache.shiro.SecurityUtils;
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 ShiroDemoMySql02 {
    
    private static Logger log=LoggerFactory.getLogger(ShiroDemoMySql02.class);
    public static void main(String[] args) {
        //取得SecurityManager工厂
        Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro_jdbc_my_sql.ini");
        //取得SecurityManager实例
        SecurityManager securityManager=factory.getInstance();
        //将securityManager绑定到SecurityUtil
        SecurityUtils.setSecurityManager(securityManager);

        /*    至此为止,简单的从mysql数据库读取realm信息的shiro环境就配置好了    */
        
        //取得当前用户
        Subject currentUser=SecurityUtils.getSubject();
        
        //使用shiro来进行登陆验证
        if(!currentUser.isAuthenticated()) {
            UsernamePasswordToken token=new UsernamePasswordToken("fuwh","123");
            try {
                currentUser.login(token);
                log.info("登陆成功!!!");
            } catch (Exception e) {
                e.printStackTrace();
                log.error("认证失败...");
            }
        }
        
        currentUser.logout();
    }
}

 那如果我们定义了多个Realm呢?

在编写一个Realm类:

package com.fuwh.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class MyJdbcRealm2 extends AuthorizingRealm{
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // TODO Auto-generated method stub
        return    new SimpleAuthenticationInfo(token.getPrincipal(),"1234","salt");
    } 
}

修改shiro_jdbc_my_sql_2.ini配置文件:

[main]
myJdbcRealm=com.fuwh.realm.MyJdbcRealm
myJdbcRealm2=com.fuwh.realm.MyJdbcRealm2
authStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
securityManager.realms=$myJdbcRealm,$myJdbcRealm2
securityManager.authenticator.authenticationStrategy=$authStrategy

修改登陆类:

package com.fuwh.demo;

import org.apache.shiro.SecurityUtils;
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 ShiroDemoMySql02_2 {
    
    private static Logger log=LoggerFactory.getLogger(ShiroDemoMySql02_2.class);
    public static void main(String[] args) {
        //取得SecurityManager工厂
        Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro_jdbc_my_sql_2.ini");
        //取得SecurityManager实例
        SecurityManager securityManager=factory.getInstance();
        //将securityManager绑定到SecurityUtil
        SecurityUtils.setSecurityManager(securityManager);

        /*    至此为止,简单的从mysql数据库读取realm信息的shiro环境就配置好了    */
        
        //取得当前用户
        Subject currentUser=SecurityUtils.getSubject();
        
        //使用shiro来进行登陆验证
        if(!currentUser.isAuthenticated()) {
            UsernamePasswordToken token=new UsernamePasswordToken("fuwh","123");
            try {
                currentUser.login(token);
                log.info("登陆成功!!!");
            } catch (Exception e) {
                e.printStackTrace();
                log.error("认证失败...");
            }
        }
        
        currentUser.logout();
    }
}

上面的例子中使用的认证策略是只有一个成功就认为是成功。

在Shiro中还提供了下面几个策略:

FirstSuccessfulStrategy:只有第一个认证成功的信息会被用

AllSuccessfulStrategy:只有当所有的都成功的时候才认为是认证成功。

源码地址:https://github.com/oukafu/shiro

原文地址:https://www.cnblogs.com/zerotomax/p/7420100.html