文章目录
引言:Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
第一个shiro程序认证
身份认证,就是判断一个用户是否为合法用户的处理过程。
最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。
- 需求:
- 根据配置文件的用户密码进行授权登录
- 文件:
- resources 下新建 .ini 配置文件
- TestAuthenticator 主要实现类
- Maven项目并导入shiro依赖
shiro中认证的关键对象
- Subject:主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
- Principal:身份信息
相当于用户名的二次包装
。是主体(subject)进行身份认证的标识,标识必须具有唯一性
,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
- credential:凭证信息
相当于口令的二次包装
。是只有主体自己知道的安全信息,如密码、证书等。
认证流程
- 认证流程:主体Subject将principal身份信息和credetial凭证信息打包成一个令牌token,通过安全管理器SecurityManager进行验证,调用认证器,认证器调用Realm的数据
- 认证=【主体+令牌(身份信息+凭证信息)-> shiro容器->实现】
认证的开发
1. 创建Maven项目并引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
2. 引入shiro配置文件
shiro 的配置文件 是以.ini为后缀的文件
.ini 支持复杂数据格式
,用来学习shiro书写我们系统中相关权限数据
没有固定的命名和路径。建议放在resources目录下,以shiro.ini命名
以键=值的形式存储
[users]
xiaochen=123
zhangsan=456
3.开发Authenticator认证代码TestAuthenticator
思路:
-
【获取容器】开发Shiro代码第一步首先要创建安全管理器SecurityManager对象(org.apache.shiro.mgt)
- SecurityManager接口只继承了三个,我们要深入使用,所以选择创建SecurityManager的底层的DefaultSecurityManager
-
【加载数据】把一个新创建的realm对象指定到配置文件,然后set到安全管理器中
-
【存放令牌信息】获取对象和加载配置后,存放令牌信息需要使用到全局安全工具类SecurityUtils
- 需要将安全管理器设置到全局安全工具类中
- 获取关键对象subject
- 创建令牌,并设置身份信息和凭证信息
-
登录+登录异常处理
代码:
public class TestAuthenticator {
public static void main(String[] args) {
//1.创建SecurityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm数据域
//把一个新创建的realm对象指定到配置文件,然后set到安全管理器中
defaultSecurityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3.将安装工具类中设置默认安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
//4.获取主体关键对象
Subject subject = SecurityUtils.getSubject();
//创建token令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen1", "123");
try {
//登录操作
System.out.println("认证状态:"+subject.isAuthenticated());
subject.login(token);//用户登录
System.out.println("认证状态:"+subject.isAuthenticated());
System.out.println("登录成功~~");
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!!");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误!!!");
}
}
}
- 异常错误:
- DisabledAccountException(帐号被禁用)
- LockedAccountException(帐号被锁定)
- ExcessiveAttemptsException(登录失败次数过多)
- ExpiredCredentialsException(凭证过期)等
右键执行即可,认证结果效果如上
如何自定义Realm
引言:第一个shiro程序使用的是Shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息。而我们大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm。
源码分析
-
需求: 自定义一个Realm来读取数据库信息
-
分析用户名和密码校验的源码:
- 最终执行用户名比较的类是:SimpleAccountRealm的doGetAuthenticationInfo,在这个方法中完成用户名的校验
- 最终密码校验比较的类是:AuthenticatingRealm中的assertCredentialsMatch(返回时自动完成的)
-
结果结论:
-
在
AuthenticatingRealm
中认证realm
是doGetAuthenticationInfo
方法 -
在
AuthorizingRealm
中授权realm
是doGetAuthorizationInfo
方法 -
所以需要自己定义一个类继承AuthorizingRealm来覆盖以上两个方法
-
1.shiro提供的Realm类以下的所有实现类
2.源码认证使用的是SimpleAccountRealm
- 删掉不必要的实现类,做处理后得到以下的类关系图
SimpleAccountRealm的部分源码中有两个方法一个是 认证 一个是 授权
:
-
在
AuthenticatingRealm
中认证realm
是doGetAuthenticationInfo
方法 -
在
AuthorizingRealm
中授权realm
是doGetAuthorizationInfo
方法
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();
}
}
}
自定义Realm的实现
-
需求:
- 在第一个shiro程序的基础上把系统.ini配置文件换成自定义的realm进行读取数据
-
文件:
- 新建realm目录并新建CustomerRealm(自定义Realm)配置类
- 新建TestCustomerRealmAuthenticator(主要实现类)
-
思路:
- 继承AuthorizingRealm,Overrid本类和父类的两个方法
CustomerRealm(自定义Realm)配置类
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//在token中获取用户名
String principal = (String) token.getPrincipal();
System.out.println(principal);
//根据身份信息使用jdbc/mybatis等查询相关数据库,在后续springboot整合之后再进行替换
//目前暂时使用假数据模拟通过,假设数据库中有这个token信息
//返回一个SimpleAuthenticationInfo,在实际中会返回具体的用户和密码信息
//我们目前也是做假数据进行模拟返回
if ("xiaozhang".equals(principal)){
//模拟返回的假数据
//参数一:返回数据库中正确的用户名
//参数二:返回数据库中正确的密码
//参数三:提供当前realm的名字,是底层自动生成的,目前我们不需要考虑
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"123456",this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
TestCustomerRealmAuthenticator(主要实现类)
public class TestCustomerRealmAuthenticator {
public static void main(String[] args) {
//获得容器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//设置自定义realm
defaultSecurityManager.setRealm(new CustomerRealm());
//把容器设置到全局安全工具类中,才能获取subject的信息
SecurityUtils.setSecurityManager(defaultSecurityManager);
//通过安全工具类获取subject
Subject subject = SecurityUtils.getSubject();
//创建token。因为是demo,没有前台输入信息,所以我们需要自己new一个token令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaozhang", "123456");
//登录
try {
subject.login(token);
System.out.println(subject.isAuthenticated());
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误");
}
}
}
测试结果:
—end