`
jinnianshilongnian
  • 浏览: 21436881 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2405505
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:2998097
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5631764
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:257641
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1593311
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:249015
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5847914
Group-logo
跟我学Nginx+Lua开...
浏览量:698257
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:780611
社区版块
存档分类
最新评论

第二章 身份验证——《跟我学Shiro》

阅读更多

 

目录贴: 跟我学Shiro目录贴

 

身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。

shiro中,用户需要提供principals (身份)和credentials(证明)shiro,从而应用能验证用户身份:

principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。

credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

最常见的principalscredentials组合就是用户名/密码了。接下来先进行一个基本的身份认证。

 

另外两个相关的概念是之前提到的SubjectRealm,分别是主体及验证主体的数据源。

 

2.2  环境准备

本文使用Maven构建,因此需要一点Maven知识。首先准备环境依赖: 

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.9</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.2</version>
    </dependency>
</dependencies> 

添加junitcommon-loggingshiro-core依赖即可。

 

2.3  登录/退出

1、首先准备一些用户身份/凭据(shiro.ini)

[users]
zhang=123
wang=123

此处使用ini配置文件,通过[users]指定了两个主体:zhang/123wang/123

  

2、测试用例(com.github.zhangkaitao.shiro.chapter2.LoginLogoutTest) 

@Test
public void testHelloworld() {
    //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
    Factory<org.apache.shiro.mgt.SecurityManager> factory =
            new IniSecurityManagerFactory("classpath:shiro.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、身份验证失败
    }

    Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录

    //6、退出
    subject.logout();
}
 

2.1、首先通过new IniSecurityManagerFactory并指定一个ini配置文件来创建一个SecurityManager工厂;

2.2、接着获取SecurityManager并绑定到SecurityUtils,这是一个全局设置,设置一次即可;

2.3、通过SecurityUtils得到Subject,其会自动绑定到当前线程;如果在web环境在请求结束时需要解除绑定;然后获取身份验证的Token,如用户名/密码;

2.4、调用subject.login方法进行登录,其会自动委托给SecurityManager.login方法进行登录;

2.5、如果身份验证失败请捕获AuthenticationException或其子类,常见的如: DisabledAccountException(禁用的帐号)、LockedAccountException(锁定的帐号)、UnknownAccountException(错误的帐号)、ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException (错误的凭证)、ExpiredCredentialsException(过期的凭证)等,具体请查看其继承关系;对于页面的错误消息展示,最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;

2.6、最后可以调用subject.logout退出,其会自动委托给SecurityManager.logout方法退出。

 

从如上代码可总结出身份验证的步骤:

1、收集用户身份/凭证,即如用户名/密码;

2、调用Subject.login进行登录,如果失败将得到相应的AuthenticationException异常,根据异常提示用户错误信息;否则登录成功;

3、最后调用Subject.logout进行退出操作。

 

如上测试的几个问题:

1、用户名/密码硬编码在ini配置文件,以后需要改成如数据库存储,且密码需要加密存储;

2、用户身份Token可能不仅仅是用户名/密码,也可能还有其他的,如登录时允许用户名/邮箱/手机号同时登录。 

 

2.4  身份认证流程

流程如下:

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

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

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

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

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

 

2.5  Realm

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

 

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

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

 

Realm配置

1、自定义Realm实现(com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1):  

public class MyRealm1 implements Realm {
    @Override
    public String getName() {
        return "myrealm1";
    }
    @Override
    public boolean supports(AuthenticationToken token) {
        //仅支持UsernamePasswordToken类型的Token
        return token instanceof UsernamePasswordToken; 
    }
    @Override
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        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());
    }
} 

 

2、ini配置文件指定自定义Realm实现(shiro-realm.ini)  

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

通过$name来引入之前的realm定义

 

3、测试用例请参考com.github.zhangkaitao.shiro.chapter2.LoginLogoutTesttestCustomRealm测试方法,只需要把之前的shiro.ini配置文件改成shiro-realm.ini即可。

 

Realm配置

1、ini配置文件(shiro-multi-realm.ini)  

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

securityManager会按照realms指定的顺序进行身份认证。此处我们使用显示指定顺序的方式指定了Realm的顺序,如果删除“securityManager.realms=$myRealm1,$myRealm2”,那么securityManager会按照realm声明的顺序进行使用(即无需设置realms属性,其会自动发现),当我们显示指定realm后,其他没有指定realm将被忽略,如“securityManager.realms=$myRealm1”,那么myRealm2不会被自动设置进去。

 

2、测试用例请参考com.github.zhangkaitao.shiro.chapter2.LoginLogoutTesttestCustomMultiRealm测试方法。

 

Shiro默认提供的Realm

以后一般继承AuthorizingRealm(授权)即可;其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。其中主要默认实现如下:

org.apache.shiro.realm.text.IniRealm[users]部分指定用户名/密码及其角色;[roles]部分指定角色即权限信息;

org.apache.shiro.realm.text.PropertiesRealm user.username=password,role1,role2指定用户名/密码及其角色;role.role1=permission1,permission2指定角色及权限信息;

org.apache.shiro.realm.jdbc.JdbcRealm通过sql查询相应的信息,如“select password from users where username = ?”获取用户密码,“select password, password_salt from users where username = ?”获取用户密码及盐;“select role_name from user_roles where username = ?”获取用户角色;“select permission from roles_permissions where role_name = ?”获取角色对应的权限信息;也可以调用相应的api进行自定义sql

 

JDBC Realm使用

1、数据库及依赖

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

本文将使用mysql数据库及druid连接池; 

 

2、到数据库shiro下建三张表:users(用户名/密码)、user_roles(用户/角色)、roles_permissions(角色/权限),具体请参照shiro-example-chapter2/sql/shiro.sql;并添加一个用户记录,用户名/密码为zhang/123

 

3、ini配置(shiro-jdbc-realm.ini) 

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 

1、变量名=全限定类名会自动创建一个类实例

2、变量名.属性= 自动调用相应的setter方法进行赋值

3$变量名 引用之前的一个对象实例 

4、测试代码请参照com.github.zhangkaitao.shiro.chapter2.LoginLogoutTesttestJDBCRealm方法,和之前的没什么区别。

 

2.6  AuthenticatorAuthenticationStrategy

Authenticator的职责是验证用户帐号,是Shiro API中身份验证核心的入口点: 

public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
            throws AuthenticationException; 

如果验证成功,将返回AuthenticationInfo验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的AuthenticationException实现。

 

SecurityManager接口继承了Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm进行验证,验证规则通过AuthenticationStrategy接口指定,默认提供的实现:

FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;

AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份验证成功的认证信息;

AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。

 

ModularRealmAuthenticator默认使用AtLeastOneSuccessfulStrategy策略。

 

假设我们有三个realm

myRealm1 用户名/密码为zhang/123时成功,且返回身份/凭据为zhang/123

myRealm2 用户名/密码为wang/123时成功,且返回身份/凭据为wang/123

myRealm3 用户名/密码为zhang/123时成功,且返回身份/凭据为zhang@163.com/123,和myRealm1不同的是返回时的身份变了;

 

1、ini配置文件(shiro-authenticator-all-success.ini) 

#指定securityManager的authenticator实现
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator

#指定securityManager.authenticator的authenticationStrategy
allSuccessfulStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy
myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1
myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2
myRealm3=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm3
securityManager.realms=$myRealm1,$myRealm3

 

2、测试代码(com.github.zhangkaitao.shiro.chapter2.AuthenticatorTest

2.1、首先通用化登录逻辑 

    private void login(String configFile) {
        //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
        Factory<org.apache.shiro.mgt.SecurityManager> factory =
                new IniSecurityManagerFactory(configFile);

        //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");

        subject.login(token);
    }

 

2.2、测试AllSuccessfulStrategy成功:    

    @Test
    public void testAllSuccessfulStrategyWithSuccess() {
        login("classpath:shiro-authenticator-all-success.ini");
        Subject subject = SecurityUtils.getSubject();

        //得到一个身份集合,其包含了Realm验证成功的身份信息
        PrincipalCollection principalCollection = subject.getPrincipals();
        Assert.assertEquals(2, principalCollection.asList().size());
    } 

PrincipalCollection包含了zhangzhang@163.com身份信息。

 

2.3、测试AllSuccessfulStrategy失败:

    @Test(expected = UnknownAccountException.class)
    public void testAllSuccessfulStrategyWithFail() {
        login("classpath:shiro-authenticator-all-fail.ini");
        Subject subject = SecurityUtils.getSubject();
} 

shiro-authenticator-all-fail.inishiro-authenticator-all-success.ini不同的配置是使用了securityManager.realms=$myRealm1,$myRealm2;即myRealm验证失败。

 

对于AtLeastOneSuccessfulStrategyFirstSuccessfulStrategy的区别,请参照testAtLeastOneSuccessfulStrategyWithSuccesstestFirstOneSuccessfulStrategyWithSuccess测试方法。唯一不同点一个是返回所有验证成功的Realm的认证信息;另一个是只返回第一个验证成功的Realm的认证信息。

 

自定义AuthenticationStrategy实现,首先看其API

//在所有Realm验证之前调用
AuthenticationInfo beforeAllAttempts(
Collection<? extends Realm> realms, AuthenticationToken token) 
throws AuthenticationException;
//在每个Realm之前调用
AuthenticationInfo beforeAttempt(
Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) 
throws AuthenticationException;
//在每个Realm之后调用
AuthenticationInfo afterAttempt(
Realm realm, AuthenticationToken token, 
AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t)
throws AuthenticationException;
//在所有Realm之后调用
AuthenticationInfo afterAllAttempts(
AuthenticationToken token, AuthenticationInfo aggregate) 
throws AuthenticationException; 

因为每个AuthenticationStrategy实例都是无状态的,所有每次都通过接口将相应的认证信息传入下一次流程;通过如上接口可以进行如合并/返回第一个验证成功的认证信息。

 

自定义实现时一般继承org.apache.shiro.authc.pam.AbstractAuthenticationStrategy即可,具体可以参考代码com.github.zhangkaitao.shiro.chapter2.authenticator.strategy包下OnlyOneAuthenticatorStrategy AtLeastTwoAuthenticatorStrategy

 

到此基本的身份验证就搞定了,对于AuthenticationToken AuthenticationInfoRealm的详细使用后续章节再陆续介绍。

 

示例源代码:https://github.com/zhangkaitao/shiro-example;可加群134755960探讨Spring/Shiro技术。

 

  • 大小: 65.5 KB
  • 大小: 38.3 KB
106
12
分享到:
评论
48 楼 one2threexm 2015-09-14  
a757956132 写道
为什么运行例子的时候会报java.lang.IllegalArgumentException: Line argument must contain a key and a value.  Only one string token was found.
有谁知道怎么解决吗


用utf-8编码保存shiro.ini会报错,我改用ANSI保存就正常了
47 楼 wlwlwlwl015 2015-09-09  
菜鸟提个几个问题。。
1、JdbcRealm是怎么找到users表的。。表名怎么指定?
2、JdbcRealm是怎么自己知道用username字段当作principals匹配?用password字段当作credentials来匹配?字段名是如何指定的?我若换成uname、pwd这种字段名还能匹配上吗?
3、这是数据库只有1条数据,若有数万条的话,是如何匹配?我看代码中给个token就开始login了。。

感觉这个框架很神奇。。初学请多指教。。
46 楼 szp010203 2015-08-25  
就一个字 牛
45 楼 a757956132 2015-08-05  
为什么运行例子的时候会报java.lang.IllegalArgumentException: Line argument must contain a key and a value.  Only one string token was found.
有谁知道怎么解决吗

44 楼 a757956132 2015-08-05  
为什么我的一直报:
org.apache.shiro.authc.AuthenticationException: Authentication failed for token submission [org.apache.shiro.authc.UsernamePasswordToken - zhang, rememberMe=false].  Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException).

Caused by: java.lang.IllegalStateException: Configuration error:  No realms have been configured!  One or more realms must be present to execute an authentication attempt.
这个错误呢?
43 楼 在世界的中心呼喚愛 2015-07-05  
在世界的中心呼喚愛 写道
luobaolin2009 写道
为什么运行例子的时候会报java.lang.IllegalArgumentException: Line argument must contain a key and a value.  Only one string token was found.
有谁知道怎么解决吗

解决?

我解决了。。。
42 楼 在世界的中心呼喚愛 2015-07-05  
luobaolin2009 写道
为什么运行例子的时候会报java.lang.IllegalArgumentException: Line argument must contain a key and a value.  Only one string token was found.
有谁知道怎么解决吗

解决?
41 楼 ouqidong2015 2015-06-15  
请问那个demo的代码在哪里下载?
40 楼 zkpking 2015-04-17  
ma860709 写道
那些配置文件,要不就是
org.apache.shiro.config.UnresolveableReferenceException: The object with id [myRealm1] has not yet been defined and therefore cannot be referenced.  Please ensure objects are defined in the order in which they should be created and made available for future reference.
要不就是
java.lang.IllegalArgumentException: Line argument must contain a key and a value.  Only one string token was found.

求解救~


加博主的群加了几个都加不进去,这里是 .ini文件格式的问题,目前除了从博主的文件里拷贝修改外(不是次次都成功),我是直接上 .txt文件解决的,完全可以读取,但是一旦建立.ini来写配置,各种出错,好奇葩,顺便我说下我的环境 win7 64,myeclipse ,从异常看是无法正确识别.ini里的键值对,还有debug能看到读取.ini文件名都会有问题  
39 楼 zqb666kkk 2015-01-29  
liuguxing 写道
对权限框架没什么研究,一直有个疑惑:一般系统的权限无非就是登陆,路径权限验证,精细点的有按钮权限先不讨论,这个好像自己都可以实现,登陆很简单,路径权限自己实现个Filter就可以了,用个框架,代码、配置这么复杂有什么好处???



智商呢?  开发快啊
38 楼 liuguxing 2015-01-28  
对权限框架没什么研究,一直有个疑惑:一般系统的权限无非就是登陆,路径权限验证,精细点的有按钮权限先不讨论,这个好像自己都可以实现,登陆很简单,路径权限自己实现个Filter就可以了,用个框架,代码、配置这么复杂有什么好处???
37 楼 徐茂才 2015-01-08  
luobaolin2009 写道
为什么运行例子的时候会报java.lang.IllegalArgumentException: Line argument must contain a key and a value.  Only one string token was found.
有谁知道怎么解决吗

应该是ini文件中的key没有设置val值,因为是密码为空?
36 楼 yangsong158 2015-01-03  
不可多得的好文。
结合代码示例一起看,简单易学,易懂。
不过,小小的提点意见,MyRealm3类的代码没有贴上来。
当然,如果结合案例代码一起看是没问题的。
不过,不看案例代码,就不知道MyRealm3上哪儿去了。
35 楼 jonsvien 2014-12-10  
dcz1001 写道
shiro锁定账户这块怎么玩的?

user是哪一个类的实例?这个用户状态是从哪里来的?
34 楼 liu.anxin 2014-10-25  
如果想放过指定的 ip, 比如如果是通过本地访问的, 就不校验直接放过. 要怎么设置??
33 楼 luobaolin2009 2014-08-27  
为什么运行例子的时候会报java.lang.IllegalArgumentException: Line argument must contain a key and a value.  Only one string token was found.
有谁知道怎么解决吗
32 楼 zdzb123 2014-08-27  
在测试自定义realm时报错了,提示还需要添加commons-beanutils。我用的shiro是1.2.3的版本
31 楼 OnTheRoad_lee 2014-07-03  
敢问楼主,这些教程,不管是文字说明部分,还是代码部分,都是LZ一个字一个字,一个代码一个代码敲出来的?
30 楼 zzone 2014-05-28  
zzone 写道
涛ge,你的类图什么工具做的

自问自答 haha  原来的用的是yFiles 画出来还挺好看。直观、美观兼具。
29 楼 zzone 2014-05-28  
涛ge,你的类图什么工具做的

相关推荐

Global site tag (gtag.js) - Google Analytics