上篇中,咱们开始探究了 RuoYi 项目是怎么进行登录信息传输、验证码校验、暗码校验,以及暗码存储安全性方案。咱们了解到,整个的验证完成是围绕 Shiro 结构进行的,而数据的传输安全性,RuoYi 是没有考虑的,假如咱们做的是要求安全等级比较高的项目,需求考虑选用 https
协议,并对要害数据进行加密后传输,一般会运用非对称暗码算法进行加解密。
本篇,咱们首要会针对 Shiro 结构做一个简略的扩展了解,然后再初窥 RuoYi 的菜单、权限功用。
Shiro
Shiro 的官网为 Apache Shiro,GitHub 仓库为 Shiro。
Shiro 的自我介绍为:Apache Shiro™ 是一个功用强大且易于运用的 Java 安全结构,可履行身份验证、授权、加密和会话管理。运用 Shiro 易于了解的 API,你能够快速轻松地维护任何运用程序——从最小的移动运用程序到最大的 web 和企业运用程序。
Shiro 高层架构,如下图:
Shiro 有三个首要概念:Subject
、SecurityManager
和 Realms
。这几个概念,咱们在上篇中对 RuoYi 的登录剖析中已有所接触。
三大概念,官方大体的介绍如下:
-
Subject
:本质上是当前履行用户的特定于安全的“视图”,它代表的能够是人,也能够表示第三方服务、守护程序帐户、cron job 或任何类似的东西。 -
SecurityManager
:SecurityManager
是 Shiro 架构的中心,它充当了一种“维护伞”目标,和谐其内部安全组件,这些组件共同构成一个目标图。但是,一旦为一个运用程序装备了SecurityManager
及其内部目标图,它一般就不存在了,运用程序开发人员几乎一切的时间都在运用Subject
API。 -
Realms
:Realms
充当 Shiro 和运用程序安全数据之间的“桥梁”或“连接器”。当需求与安全相关数据(如用户帐户)进行实际交互以履行身份验证(登录)和授权(拜访操控)时,Shiro 会从为运用程序装备的一个或多个Realms
中查找许多这些内容。从这个意义上讲,Realm 本质上是一个特定于安全的 DAO。
从以上的概念描绘中,能够看出来,Subject
代表认证相关的人或者运用;SecurityManager
是用于认证的中心及桥梁;Realms
则代表用于认证的相关数据及认证办法的供给者。
再看一下 Shiro 的具体架构,如下图:
这张图能够看到更多关于 SecurityManager
的组成部分,以及常见的 Realms
的认证数据来历,具体咱们不再展开。
RuoYi 里的 Shiro
RuoYi 项目里对接运用 Shiro 的代码,放在项目 ruoyi-framework
中, 包为 com.ruoyi.framework.shiro
。其间触及界说的 Realms 完成类 UserRealm
,将类继承自 AuthorizingRealm
,AuthorizingRealm
是一个笼统类,其间笼统办法为:
/**
* Retrieves the AuthorizationInfo for the given principals from the underlying data store. When returning
* an instance from this method, you might want to consider using an instance of
* {@link org.apache.shiro.authz.SimpleAuthorizationInfo SimpleAuthorizationInfo}, as it is suitable in most cases.
*
* @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
* @return the AuthorizationInfo associated with this principals.
* @see org.apache.shiro.authz.SimpleAuthorizationInfo
*/
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
同时,AuthorizingRealm
还持续自 AuthenticatingRealm
,它也是一个笼统类,其笼统办法为:
/**
* Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given
* authentication token.
* <p/>
* For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing
* more and letting Shiro do the rest. But in some systems, this method could actually perform EIS specific
* log-in logic in addition to just retrieving data - it is up to the Realm implementation.
* <p/>
* A {@code null} return value means that no account could be associated with the specified token.
*
* @param token the authentication token containing the user's principal and credentials.
* @return an {@link AuthenticationInfo} object containing account data resulting from the
* authentication ONLY if the lookup is successful (i.e. account exists and is valid, etc.)
* @throws AuthenticationException if there is an error acquiring data or performing
* realm-specific authentication logic for the specified <tt>token</tt>
*/
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
UserRealm
对两个笼统办法的完成别离如下:
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0)
{
SysUser user = ShiroUtils.getSysUser();
// 人物列表
Set<String> roles = new HashSet<String>();
// 功用列表
Set<String> menus = new HashSet<String>();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 管理员拥有一切权限
if (user.isAdmin())
{
info.addRole("admin");
info.addStringPermission("*:*:*");
}
else
{
roles = roleService.selectRoleKeys(user.getUserId());
menus = menuService.selectPermsByUserId(user.getUserId());
// 人物参加AuthorizationInfo认证目标
info.setRoles(roles);
// 权限参加AuthorizationInfo认证目标
info.setStringPermissions(menus);
}
return info;
}
/**
* 登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
{
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
String password = "";
if (upToken.getPassword() != null)
{
password = new String(upToken.getPassword());
}
SysUser user = null;
try
{
user = loginService.login(username, password);
}
catch (CaptchaException e)
{
throw new AuthenticationException(e.getMessage(), e);
}
catch (UserNotExistsException e)
{
throw new UnknownAccountException(e.getMessage(), e);
}
catch (UserPasswordNotMatchException e)
{
throw new IncorrectCredentialsException(e.getMessage(), e);
}
catch (UserPasswordRetryLimitExceedException e)
{
throw new ExcessiveAttemptsException(e.getMessage(), e);
}
catch (UserBlockedException e)
{
throw new LockedAccountException(e.getMessage(), e);
}
catch (RoleBlockedException e)
{
throw new LockedAccountException(e.getMessage(), e);
}
catch (Exception e)
{
log.info("对用户[" + username + "]进行登录验证..验证未经过{}", e.getMessage());
throw new AuthenticationException(e.getMessage(), e);
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}
能够看到,doGetAuthorizationInfo(PrincipalCollection arg0)
代码便是取得一个 SysUser
目标,并给定相应的菜单和用户人物。也便是说用户和权限和菜单绑定都是在这儿完成的,而其间最中心的用户数据是从从而来呢?持续看以下中心的一句代码,并盯梢进去:
SysUser user = ShiroUtils.getSysUser();
public static SysUser getSysUser()
{
SysUser user = null;
Object obj = getSubject().getPrincipal();
if (StringUtils.isNotNull(obj))
{
user = new SysUser();
BeanUtils.copyBeanProp(user, obj);
}
return user;
}
能够看到,用户信息来历于 getPrincipal()
,而它来自于 getProject()
,持续跟进,能够找到:
public class ShiroUtils
{
public static Subject getSubject()
{
return SecurityUtils.getSubject();
}
...
跟进,找到 SecurityUtils
类里的代码完成:
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
此处逻辑为:从 ThreadContext.getSubject()
中获取 Subject
,假如其为 null
则直接构建一个目标,并存入 ThreadContext
中。进入 buildSubject()
的逻辑,代码如下:
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
咱们再跟进 UserRealm
另一个完成的办法 doGetAuthenticationInfo(AuthenticationToken token)
,能够看到此办法完成了真正的登录认证,将待认证信息与认证源的数据进行认证对比,承认用户是否能够认证经过。
这个时候咱们重新回到 RuoYi 事务中的登录办法 ajaxLogin
,调查登录事务到底是怎么经过 Shiro 结构完成的:
UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
Subject subject = SecurityUtils.getSubject();
try
{
subject.login(token);
return success();
}
这儿能够很明晰的看到,此处运用了上文提到的办法 SecurityUtils.getSubject()
来生成 Subject
,然后对生成的 Subject
目标履行登录操作。那此处逻辑就很明晰了:在项目初始化装备时,SecurityManager
就已经与 UserRealm
提前绑定,当用户触发到登录时,代码中运用 SecurityManager
生成了一个 Subject
目标,再经过 Subject
界说的 login
办法进行了登录操作,而 login
办法的调用,终究会运转到咱们上面剖析到的 UserRealm
中的 doGetAuthenticationInfo(AuthenticationToken token)
办法,终究登录成功的话,Shiro 结构会为 Subject
目标增加认证用户的相关信息。
经过以上的代码整理和剖析,咱们能比较明晰地体会到 Shiro 三大中心概念的用途。Realm
便是用来比较认证信息是否合法的,中心便是供给认证源用于对比;SecurityManager
与 Realm
提前绑定,供给认证 API 给事务运用,事务 Subject
的 login
办法,完成终究经过 SecurityManger
调用 Realm
中的认证办法进行登录,并赋予 Subject
目标相关数据,终究可经过 SecurityManager
取得用户相关数据目标 Subject
,并能从 Subject
中获取自己需求的各种用户信息。
初见 RBAC
在上面登录逻辑中,咱们看到在 UserRealm
中登录成功后,对用户进行菜单和权限的绑定操作。但比较奇怪的是这段代码,让人感觉疑惑:
roles = roleService.selectRoleKeys(user.getUserId());
menus = menuService.selectPermsByUserId(user.getUserId());
// 人物参加AuthorizationInfo认证目标
info.setRoles(roles);
// 权限参加AuthorizationInfo认证目标
info.setStringPermissions(menus);
咱们很明显的能够看到 info
目标的两个办法为 setRoles()
(设置人物)、SetStringPermissions()
(设置权限),但作者在这处对设置权限函数增加的数据,命名为 menus
(菜单)。咱们只能有一个开始猜测:RuoYi 体系中,是否没有针对 API 级别的权限操控,而只是针对菜单级进行了操控呢?
别的,SysRole
都有些什么特点,又是怎么对用户操作进行权限操控的呢?菜单是什么样的数据结构,又是怎么进行拜访操控的呢?这些 RBAC 相关疑问的解答,咱们将在下篇展开。
小结
(读太多文字人会太累,所以本篇在 RBAC 预备展开时戛但是止)本篇咱们简略的扩展了一下 Shiro 结构的相关知识,并结合 Shiro 结构简略剖析了 RuoYi 项目怎么对接运用的 Shiro 结构。
别的,咱们还在对接代码中发现了 RBAC 比较中心的权限、菜单等数据。RBAC 神秘的面纱才刚揭开,咱们下一篇持续。本篇就到这儿,比心,❤。