Shiro之身份验证
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的身份认证流程,如果还没有明白,可以看看下图:
首先调用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(); } }