认证(Authentication)其实是一个身份认证的过程,也就是饱受吐槽的“证明你自己是你自己”。用户需要向Shiro提交principals以及credentials以验证它们是否是应用所需。
Principals:事实上是主体的标识,它可以是任何东西,身份证、手机号、邮箱等等。
Credentials:通常是只有主体知道的秘密值,常见的例如密码、指纹、生物虹膜信息等。
在此我们需要引入一些Maven依赖:
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.4.0version>
dependency>
喜欢什么版本自己定,这不重要。
认证的过程可以笼统地概括为3步:
1. 获取主体的Principals和Credentials;
2. 提交上述信息;
3. 认证成功则授予相应权限,否则重试或者阻塞。
逐步仔细分析:
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
token.setRememberMe(true);
这个例子中我们使用了UsernamePasswordToken ,这是最常见的用户名/密码令牌,它是 org.apache.shiro.authc.AuthenticationToken的一个接口实现。这里Shiro是不关心用户信息从何而来,无论是HTML页面或者SwingUI界面。采集信息和Shiro是解耦的。
Subject user=SecurityUtils.getSubject();
user.login(token);
第一行表示获取当前操作的主体,第二行表示使用令牌进行登录操作。
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.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:域,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的默认实现:
//它是一个接口
package org.apache.shiro.authc;
public interface Authenticator {
AuthenticationInfo authenticate(AuthenticationToken var1) throws AuthenticationException;
}
如果验证成功,将返回AuthenticationInfo验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的AuthenticationException实现。
SecurityManager接口继承了Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm进行验证,验证规则通过AuthenticationStrategy接口指定,默认提供的实现:
ModularRealmAuthenticator默认使用AtLeastOneSuccessfulStrategy策略。