Shiro小计
Shiro
shiro暂不支持springboot 3.x
认证过程
流程如下:
- 首先调用 Subject.login(token) 进行登录,其会自动委托给 Security Manager,调用之前必须通过 SecurityUtils.setSecurityManager() 设置;
- SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
- Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
- Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
- Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。
授权过程
使用自定义realm
public class MyRealm extends AuthorizingRealm{
/**
* 模拟数据库数据
*/
Map<String, String> userMap = new HashMap<>(16);
{
userMap.put("wmyskxz", "123456");
super.setName("myRealm"); // 设置自定义Realm的名称,取什么无所谓..
}
/**
* 授权
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String userName = (String) principalCollection.getPrimaryPrincipal();
// 从数据库获取角色和权限数据
Set<String> roles = getRolesByUserName(userName);
Set<String> permissions = getPermissionsByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setStringPermissions(permissions);
simpleAuthorizationInfo.setRoles(roles);
return simpleAuthorizationInfo;
}
/**
* 模拟从数据库中获取权限数据
*
* @param userName
* @return
*/
private Set<String> getPermissionsByUserName(String userName) {
Set<String> permissions = new HashSet<>();
permissions.add("user:delete");
permissions.add("user:add");
return permissions;
}
/**
* 模拟从数据库中获取角色数据
*
* @param userName
* @return
*/
private Set<String> getRolesByUserName(String userName) {
Set<String> roles = new HashSet<>();
roles.add("admin");
roles.add("user");
return roles;
}
/**
* 认证
*
* @param authenticationToken 主体传过来的认证信息
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 1.从主体传过来的认证信息中,获得用户名
String userName = (String) authenticationToken.getPrincipal();
// 2.通过用户名到数据库中获取凭证
String password = getPasswordByUserName(userName);
if (password == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("wmyskxz", password, "myRealm");
return authenticationInfo;
}
/**
* 模拟从数据库取凭证的过程
*
* @param userName
* @return
*/
private String getPasswordByUserName(String userName) {
return userMap.get(userName);
}
}
测试
@Test
public void testAuthentication() {
MyRealm myRealm = new MyRealm(); // 实现自己的 Realm 实例
// 1.构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(myRealm);
// 2.主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager); // 设置SecurityManager环境
Subject subject = SecurityUtils.getSubject(); // 获取当前主体
UsernamePasswordToken token = new UsernamePasswordToken("wmyskxz", "123456");
subject.login(token); // 登录
// subject.isAuthenticated()方法返回一个boolean值,用于判断用户是否认证成功
System.out.println("isAuthenticated:" + subject.isAuthenticated()); // 输出true
// 判断subject是否具有admin和user两个角色权限,如没有则会报错
subject.checkRoles("admin", "user");
// subject.checkRole("xxx"); // 报错
// 判断subject是否具有user:add权限
subject.checkPermission("user:add");
}
Shiro加密
数据库中的密码一般不使用明文存储,所以我们使用非对称加密,人话就是不可逆的加密方式,md5就是其中一种,123456 MD5加密后是e10adc3949ba59abbe56e057f20f883e虽然无法从这段字符串反加密得到123456但是可以将123456以同样的加密方式得到这段字符串,就得到了密码
解决
既然相同的密码 md5 一样,那么我们就让我们的原始密码再加一个随机数,然后再进行 md5 加密,这个随机数就是我们说的盐(salt)
Shiro框架加密使用
String password = "123456";
String salt = new SecureRandomNumberGenerator().nextBytes().toString();
int times = 2; // 加密次数:2
String alogrithmName = "md5"; // 加密算法
String encodePassword = new SimpleHash(alogrithmName, password, salt, times).toString();
System.out.printf("原始密码是 %s , 盐是: %s, 运算次数是: %d, 运算出来的密文是:%s ",password,salt,times,encodePassword);
输出
原始密码是 123456 , 盐是: f5GQZsuWjnL9z585JjLrbQ==, 运算次数是: 2, 运算出来的密文是:55fee80f73537cefd6b3c9a920993c25
Shiro三大核心组件
即Subject
、SecurityManager
和 Realm
- Subject: 为
认证主体
。应用代码直接交互的对象是Subject,Subject代表了当前的用户。包含Principals
和Credentials
两个信息。 - SecurityManager:为
安全管理员
。是Shiro架构的核心。与Subject的所有交互都会委托给SecurityManager, Subject相当于是一个门面,而SecurityManager才是真正的执行者。它负责与Shiro 的其他组件进行交互。 - Realm:是
一个域
。充当了Shiro与应用安全数据间的“桥梁”。Shiro从Realm中获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm中获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能过进行,可以把Realm看成DataSource,即安全数据源。
Shiro架构
Authentication
: 身份认证、登录,验证用户是不是拥有相应的身份;Authorization
:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限!Session Manager
: 会话管理,即用户登录后就是第-次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通的JavaSE环境,也可以是Web环境;Cryptography
: 加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;Web Support
: Web支持,可以非常容易的集成到Web环境;Caching
: 缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率Concurrency
: Shiro支持多线程应用的并发验证,即,如在一个线程中开启另一个线程,能把权限自动的传播过去Testing
:提供测试支持;RunAs
:允许一个用户假装为另-一个用户(如果他们允许)的身份进行访问;Remember Me
:记住我,这个是非常常见的功能,即一-次登录后, 下次再来的话不用登录了
内部架构
Subject
: 任何可以与应用交互的用户;Security Manager
:相当于SpringMVC中的DispatcherSerlet
; 是Shiro的心脏
, 所有具体的交互都通过Security Manager
进行控制,它管理者所有的Subject, 且负责进行认证,授权,会话,及缓存的管理。Authenticator
:负责Subject
认证, 是-一个扩展点,可以自定义实现;可以使用认证策略
(Authentication Strategy),即什么情况下算用户认证通过了;Authorizer
:授权器,即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中 的那些功能;Realm
: 可以有-一个或者多个的realm, 可以认为是安全实体数据源,即用于获取安全实体的,可以用JDBC实现,也可以是内存实现等等,由用户提供;所以- -般在应用中都需要实现自己的realmSessionManager
:管理Session生 命周期的组件,而Shiro并不仅仅可以用在Web环境,也可以用在普通的JavaSE环境中CacheManager
: 缓存控制器,来管理如用户,角色,权限等缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能;Cryptography
:密码模块,Shiro 提高了一些常见的加密组件用于密码加密, 解密等