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.LoginLogoutTest 的 testJDBCRealm 方法,和之前的没什么区别。
Authenticator 及 AuthenticationStrategy
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 包含了 zhang 和 zhang@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.ini 与 shiro-authenticator-all-success.ini 不同的配置是使用了 securityManager.realms=$myRealm1,$myRealm2;即 myRealm 验证失败。
对 于 AtLeastOneSuccessfulStrategy 和 FirstSuccessfulStrategy 的区别, 请参照 testAtLeastOneSuccessfulStrategyWithSuccess和testFirstOneSuccessfulStrategyWithSuccess测试方法。唯一不同点一个是返回所有验证成功的 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 、AuthenticationInfo 和 Realm 的 详细使用后续章节再陆续介绍