认证(Authentication)其实是一个身份认证的过程,也就是饱受吐槽的“证明你自己是你自己”。用户需要向Shiro提交principals以及credentials以验证它们是否是应用所需。
Principals:事实上是主体的标识,它可以是任何东西,身份证、手机号、邮箱等等。
Credentials:通常是只有主体知道的秘密值,常见的例如密码、指纹、生物虹膜信息等。
在此我们需要引入一些Maven依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
喜欢什么版本自己定,这不重要。
认证的过程可以笼统地概括为3步:
1. 获取主体的Principals和Credentials;
2. 提交上述信息;
3. 认证成功则授予相应权限,否则重试或者阻塞。
逐步仔细分析:
1. Collect the Subject’s principals and credentials
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
token.setRememberMe(true);
这个例子中我们使用了UsernamePasswordToken ,这是最常见的用户名/密码令牌,它是 org.apache.shiro.authc.AuthenticationToken的一个接口实现。这里Shiro是不关心用户信息从何而来,无论是HTML页面或者SwingUI界面。采集信息和Shiro是解耦的。
2. Submit the principals and credentials
Subject user=SecurityUtils.getSubject();
user.login(token);
第一行表示获取当前操作的主体,第二行表示使用令牌进行登录操作。
3. Handling Success or Failure
try{
user.login(token);
}catch(UnknownAccountException uae){
...
}catch(IncorrectCredentialsException ice){
...
}catch(LockedAccountExcpetion lae){
...
}catch(ExcessiveAttemptsException eae){
...
}catch(AuthenticationException ae){
...
}
如果没有异常,那么user应该被认证,这时,调用user的isAuthenticated()方法,应当返回true。
将以上三步整合。
//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();
验证流程
1. 应用调用user的login()方法;
2. Subject作为一个委托主体委托给应用的Security Manager,本质上使交由securityManager.login(token),从这里开始验证过程才算真正开始;
3. Authenticator是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现;
4. Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
5. Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
Realm
Realm:域,Shiro从Realm获取安全数据,这意味着,Security Manager要验证用户身份时,需要从中获得用户,Realm可以作为安全数据源。
realm是一个接口,内部方法如下:
String getName(); //返回一个唯一的Realm名字
boolean supports(AuthenticationToken token); //判断此Realm是否支持此Token
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException; //根据Token获取认证信息
Shiro提供了一些默认实现,当然它也可以自定义实现,我们可以随手码一个:
public MyRealm implements Realm{
@Override
public boolean support(AuthenticationToken token){
return token instanceOf UsernamePasswordToken;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken user= (UsernamePasswordToken) authenticationToken;
String userLogin=user.getUsername();
char[] password=user.getPassword();
User loginResult=null;
try {
//从service层获取一个entity
loginResult=userService.login(userLogin,new String(password));
}catch (EnterInfoErrorException | NotFoundException e){
e.printStackTrace();
throw new AuthenticationException(e.getMessage());
}
return new SimpleAuthenticationInfo(loginResult,user.getPassword(),this.getName());
}
}
Shiro默认实现的Realm
一般来说,只需要继承AuthorizingRealm即可,如图所示,它的父类是AuthenticationRealm,祖父类是CachingRealm,后者带有缓存实现,必须指出,它们都是抽象类。以下是Realm的默认实现:
- org.apache.shiro.realm.text.IniRealm:[users]部分指定用户名/密码及其角色;[roles]部分指定角色即权限信息;
- org.apache.shiro.realm.text.PropertiesRealm:user.username=password,role1,role2指定用户名/密码,role.role1=permission1,perimission2指定角色及权限信息;
- 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;
Authenticator及AuthenticationStrategy
//它是一个接口
package org.apache.shiro.authc;
public interface Authenticator {
AuthenticationInfo authenticate(AuthenticationToken var1) throws AuthenticationException;
}
如果验证成功,将返回AuthenticationInfo验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的AuthenticationException实现。
SecurityManager接口继承了Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm进行验证,验证规则通过AuthenticationStrategy接口指定,默认提供的实现:
- FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;
- AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份验证成功的认证信息;
- AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
ModularRealmAuthenticator默认使用AtLeastOneSuccessfulStrategy策略。