身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。

关键对象

  • Subject:主体

访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;

  • Principal:身份信息

是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。

  • credential:凭证信息

是只有主体自己知道的安全信息,如密码、证书等。

认证流程

image-20211120165337295

认证的开发

1. 创建项目并引入依赖

1
2
3
4
5
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>

2. 引入shiro配置文件并加入如下配置

image-20211120162935812

1
2
3
4
[users]
kylin=123
zhangsan=123456
lisi=789

3.开发认证代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 测试用户认证
*/
public class TestAuthenticator {
public static void main(String[] args) {
//1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3.SecurityUtils 给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject主体
Subject subject = SecurityUtils.getSubject();
//5.创建令牌 //正确--无异常 错误--抛异常
UsernamePasswordToken token = new UsernamePasswordToken("kylin", "123");

System.out.println("认证状态" + subject.isAuthenticated());
//用户认证
subject.login(token);
System.out.println("认证状态" + subject.isAuthenticated());
}
}

image-20211120163303754

  • UnknownAccountException(未知账户异常,用户名错误)

  • IncorrectCredentialsException(不正确的凭证异常,密码错误)

  • DisabledAccountException(帐号被禁用)

  • LockedAccountException(帐号被锁定)

  • ExcessiveAttemptsException(登录失败次数过多)

  • ExpiredCredentialsException(凭证过期)等

自定义Realm

上边的程序使用的是Shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm。

1.shiro提供的Realm

image-20211120165304506

2.根据认证源码认证使用的是SimpleAccountRealm

image-20211120170001269

SimpleAccountRealm的部分源码中有两个方法一个是 认证 一个是 授权

  • SimpleAccountRealm—->doGetAuthenticationInfo用户名校验
  • AuthenticatingRealm—->assertCredentialsMatch密码校验
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 SimpleAccountRealm extends AuthorizingRealm {
//.......省略
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
SimpleAccount account = getUser(upToken.getUsername());

if (account != null) {

if (account.isLocked()) {
throw new LockedAccountException("Account [" + account + "] is locked.");
}
if (account.isCredentialsExpired()) {
String msg = "The credentials for account [" + account + "] are expired";
throw new ExpiredCredentialsException(msg);
}

}

return account;
}

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = getUsername(principals);
USERS_LOCK.readLock().lock();
try {
return this.users.get(username);
} finally {
USERS_LOCK.readLock().unlock();
}
}
}

image-20211120215907709

所以密码的验证是shiro自动调用assertCredentialsMatch来验证的,我们自用将doGetAuthenticationInfo中获取的认证信息改成数据库中获取即可。继承AuthorizingRealm类,就可以从数据库中进行认证。

image-20211120170001269

SimpleAccountRealm—->继承AuthorizingRealm—->继承AuthenticatingRealm

  • 自定义认证继承AuthenticatingRealm实现doGetAuthenticationInfo方法
  • 自定义授权继承AuthorizingRealm实现doGetAuthorizationInfo方法

image-20211121102133198

3.自定义realm

自定义认证继承AuthenticatingRealm实现doGetAuthenticationInfo方法(继承AuthorizingRealm实现也行,因为AuthorizingRealm继承了AuthenticatingRealm)

image-20211121103836005

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 自定义realm实现 将认证/授权数据的来源转为数据库的实现
*/
public class CustomerRealm extends AuthenticatingRealm {

//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//在token中获取用户名
Object principal = authenticationToken.getPrincipal();
System.out.println(principal);
//根据身份信息使用jdbc mybatis查询相关数据库
if ("kylin".equals(principal)) {
//参数1:返回数据库正确的用户名
// 参数2:返回数据库中正确密码
// 参数3:提供当前realm的名字 this.getName();
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, "123", this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
  • Object principal = authenticationToken.getPrincipal();在token中获取用户名
  • SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, "123", this.getName());

new SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)

  1. 参数1:返回数据库正确的用户名
  2. 参数2:返回数据库中正确密码
  3. 参数3:提供当前realm的名字 this.getName();

通过SimpleAuthenticationInfo构造了以下认证信息

  • 用户名:kylin
  • 密码凭证:123

4.使用自定义Realm认证

image-20211121104618911

使用自定Realm进行认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 使用自定义realm
*/
public class TestCustomerRealmAuthenticator {
public static void main(String[] args) {

//创建securityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//设置自定义realm
defaultSecurityManager.setRealm(new CustomerRealm());
//将安全工具类设置安全工具类
SecurityUtils.setSecurityManager(defaultSecurityManager);
//通过安全工具类获取subject
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken token = new UsernamePasswordToken("kylin", "123");

System.out.println("认证状态" + subject.isAuthenticated());
//用户认证
subject.login(token);
System.out.println("认证状态" + subject.isAuthenticated());

}
}

此时UsernamePasswordToken

  • 用户名:kylin
  • 密码凭证:123

和我们在自定义中构造的认证信息一致。所以认证成功!

image-20211121105012170

以后我们就能在doGetAuthenticationInfo中根据principal用户名再数据库中查找数据,构建成SimpleAuthenticationInfo就能进行认证!