Shiro实现身份验证

Shiro实现身份验证

身份验证,即在应用中谁能证明他是他本人,一般提供如他们的身份ID、用户名、密码等来证明。

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

  • principals: 身份,即主体的标识属性,可以是任何东西,如用户名、邮箱,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名、密码等。
  • credentials: 证明、凭证,即只有主体知道的安全值,如密码、数字证书等。

入门案例(登录退出)

shiro.ini

使用ini配置文件来模拟数据库中的数据,实际是应该从数据库中读取安全数据。

1
2
[users]
tycoding=123

AuthenticationTest.java 测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class AuthenticationTest {

@Test
public void loginLogoutTest(){
//1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini");

//2、得到SecurityManager实例,并绑定给SecurityUtils
SecurityManager securityManager = (SecurityManager) factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);

//3、得到Subject及创建用户名、密码身份的Token
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("tycoding","123");

try{
//4、登录,即身份验证
subject.login(token);
}catch (AuthenticationException e){
//5、身份验证失败
e.printStackTrace();
}

//判断用户是否已登录
//用户登录成功将返回true,否则返回false并抛出异常
System.out.println(subject.isAuthenticated());

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

解释:

  1. 首先需要创建SecurityManager工厂,这里使用ini配置文件来初始化SecurityManager工厂,以后使用Spring时,可通过将SecurityManager注入到Spring容器中,就不再用加载ini配置文件的方式。
  2. 通过工厂类获取到SecurityManager实例并绑定到SecurityUtils,这是一个全局设置,设置一次即可。
  3. 通过SecurityUtils得到Subject,其会自动绑定到当前线程;然后获取身份验证的Token,如用户名、密码等。
  4. 调用subject.login(token)方法登录,其会自动委托给SecurityManager.login()方法进行登录。
  5. 如果登录失败请捕获AuthenticationException或其子类,常见的异常有:DisabledAccountException(禁用的账号)、LockedAccountException(锁定的账号)、UnknownAccountException(错误的账号)、ExcessiveAttemptsException(登录失败次数过多)等。
  6. 最后调用subject.logout()退出,其会自动委托给SecurityManager.logout()退出。

身份认证流程

解释:

  1. 首先调用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

Realm: 域,Shiro从Realm中获取安全数据(如用户、角色、权限);SecurityManager要验证用户身份,就需要从Realm中获取相应的用户进行比较以确定用户身份是否合法,

org.apache.shiro.realm.Realm接口中有如下方法:

1
2
3
4
5
6
String getName(); //返回一个唯一的Realm名字

boolean supports(AuthenticationToken token); //判断此Realm是否支持此token

//根据Token获取认证信息
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

自定义Realm实现

MyRealm.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class MyRealm extends AuthorizingRealm {

@Override
public String getName() {
return "myRealm";
}

//用于授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}

//用于身份验证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

System.out.println("进入自定义realm...");

//得到用户名
Object principal = token.getPrincipal();

//得到密码,这个密码应该是从数据库中读取到的,而不是我们主动调用getCredentials()方法得到密码,这里仅为了测试

//得到密码
Object credentials = token.getCredentials();
// Object credentials = "12";

try{
//如果身份验证失败就返回null,并抛出异常

//如果身份验证成功就返回正确的信息(AuthenticationInfo的实现类)
return new SimpleAuthenticationInfo(principal,credentials,getName());

}catch (AuthenticationException e){
e.printStackTrace();
return null;
}
}
}

注释:

为了更清晰的展示自定义Realm参与了身份验证,特意打印了一行sys

其中密码应该是从数据库中读取的,这里从我们模拟的ini配置文件中读取安全信息(比如调用了service层获取的密码)。而token.getCredentials()是获取用户输入的密码。

注意:

注意最后的返回值类型是SimpleAuthenticationInfo(principal,credentials,realmName),它是AuthenticationInfo接口的一个实现类。它会返回校验结果:

  • 校验成功:返回包含安全数据的SimpleAuthenticationInfo实例对象。
  • 校验失败:返回null。

在实际中,我们应该从数据库中读取用户安全信息,我们也并不需要手动调用getCredentials()方法,因为在返回SimpleAuthenticationInfo时,Shiro会自动调用getCredentials()方法获取用户输入的密码,并与在数据库中读取到的密码进行比较,并将结果返回。

shiro-realm.ini

1
2
3
4
5
6
7
8
[main]
#声明一个realm
myRealm=com.shiro.MyRealm
#指定securityManager的Realm实现,使用$name来引入之前的realm定义
securityManager.realms=$myRealm

[users]
tycoding=123

注释:

这里我们使用ini来模拟数据库数据,并在其中指定自动realm实现,在使用Spring后,我们会将自定义realm实现注入到Spring配置文件中。

多Realm配置

1
2
3
4
5
6
7
8
9
10
[main]
#声明一个realm
myRealm=com.shiro.MyRealm
myRealm2=com.shiro.MyRealm2

#指定securityManager的Realm实现,使用$name来引入之前的realm定义
securityManager.realms=$myRealm,$myRealm2

[users]
tycoding=123


交流

如果大家有兴趣,欢迎大家加入我的Java交流群:671017003 ,一起交流学习Java技术。博主目前一直在自学JAVA中,技术有限,如果可以,会尽力给大家提供一些帮助,或是一些学习方法,当然群里的大佬都会积极给新手答疑的。所以,别犹豫,快来加入我们吧!


联系

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

坚持原创技术分享,您的支持将鼓励我继续创作!