Shiro的三大核心组件
Shiro是Apache下的一个开源项目,我们称之为Apache Shiro。它是一个很易用与Java项目的的安全框架,提供了认证、授权、加密、会话管理,与 Spring Security 一样都是做一个权限的安全框架,但是与Spring Security 相比,在于 Shiro 使用了比较简单易懂易于使用的授权方式。
了解shiro的组成,主要看以下几个类
SecurityManager:
Shiro的核心是ScurityManager,它是负责安全认证与授权的,Shiro本身已经实现了所有的细节,我们在使用的时候可以完全把它当做黑盒使用。
SecurityUtils:
本质上是一个工厂,类似Spring中的ApplicationContext,
Subject:
Subject是有点难理解的,有些地方理解为user,其实不然,Subject中文翻译是项目,在下面代码中会表现的很清楚
Realm:
在Shiro中,进行的授权和认证就是由它来操作的,我已开始对授权和认证不是很理解,也不是很明白在代码操作的时候命名在认证的时候,可以获取到授权的信息,为什么还要在进行授权的操作,下面对授权和认证做一下解释:所谓的认证,他相当于人的身份证,就是可以证明你身份的证件,在应用中,就是拿着当前登录的用户的名称与数据库中查询,看是当前登录的用户是否在数据库存在,如果存在,好,说明你是你自己。而所谓的授权,就相当于当你购买火车票的时候,如果你买的硬座,你就只能去硬座的车厢,而不能去软卧等其他车厢,这就相当于限制了你的能力,而在应用中也相当于如此,在授权的时候,会从数据库将你的权限信息获取到,交给Shiro,如果此时你要去访问指定的页面的时候,会首先对你的权限进行校验,看你是否有该权限,如果有就可以访问,没有则不可以访问。
Subject
Subject直译为主题,项目。但是在Shiro框架当中,这个类主要是用来与类似用户客户这样的对象打交道。我们在进行授权鉴权的所有操作都是围绕Subject(用户)展开的,在当前应用的任何地方都可以通过SecurityUtils
的静态方法getSubject()
轻松的拿到当前认证(登录)的用户。
SecurityManager
SecurityManager安全管理器,是shiro当中最核心的组件,管理着当前应用当中所有的安全操作,包括Subject。我们围绕着Subject所做的所有操作都需要通过SecurityManager进行交互。可以类似理解为SpringMvc当中的前端控制器
Realms
在认证、授权内部实现机制中都有提到,最终处理都将交给Realms进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO。配置Shiro我们至少需要一个Realms
shiro中内置的FilterChain
-
anon:所有url都都可以匿名访问;
-
authc: 需要认证才能进行访问;
-
user:配置记住我或认证通过可以访问;
这三个基本的概念简答理解后就可以开始配置和使用Shiro了,其实Shiro最基本的使用非常简单,加入依赖后只需要配置两个Bean,再继承一个抽象类实现两个方法即可。
简单实例
前提准备
-
数据库建表
由于我们是需要通过shiro做一个简单的权限控制demo,因此考虑基于rbac去建表。参考链接:
https://blog.csdn.net/qq_35206244/article/details/9962725 -
项目创建
创建一个普通的springboot项目,并在resource下建几个简单的测试页面,配置好数据库连接的properties文件。这里我是用jpa。你也可以考虑MybatisPlus
-
maven导入
这没什么好说的,直接导入即可
-
开始配置
先编写一个homeController
?
@Controller
public class HomeController {
@RequestMapping({ "/", "index" })
public String index() {
return "/index";
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
return "/login";
}
?
继承shiro
集成shiro大概分这么一个步骤:
?
(a) pom.xml中添加Shiro依赖;
?
(b) 注入Shiro Factory和SecurityManager。
?
(c) 身份认证
?
(d) 权限控制
按此四步骤可完成一般的登录验证
(a)步骤在上述前提准备中pom.xml中已经导入
(b)注入Shiro Factory和SecurityManager
Springboot项目就是基于注解去注入,首先我们要新建ShiroConfiguration.java
@Configuration public class ShiroConfiguration {@Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { System.out.println("ShiroConfiguration.shiroFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置SecuritManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置退出过滤器,其中的具体代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/**", "authc"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index"); // 未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); return securityManager; }
}
(c)身份认证
这一步主要要依赖Realms来实现。
首先由前端传来的用户登录信息在对应的Controller当中被接收存入token当中,Realm从token当中获取用户信息,判断是否存在用户,然后对账号做数据验证,判断密码是否正确
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("MyShiroRealm.doGetAuthenticationInfo()"); // 获取用户的输入帐号 String username = (String) token.getPrincipal(); System.out.println(token.getCredentials()); // 通过username从数据库中查找 User对象,如果找到,没找到. // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 UserInfo userInfo = userInfoService.findByUsername(username); System.out.println("----->>userInfo=" + userInfo); if (userInfo == null) { return null; }SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, // 用户名 userInfo.getPassword(), // 密码 ByteSource.Util.bytes(userInfo.getCredentialsSalt()), // salt=username+salt getName() // realm name ); return authenticationInfo; }
此过程结束之后会将结果返回,认证结束。认证结束之后securityManager根据认证结果判断其跳转位置。
而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo (),重写获取用户信息的方法。其中doGetAuthenticationInfo主要是用来进行身份认证的,交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配。
至于doGetAuthorizationInfo()是权限控制,当访问到页面的时候,使用了相应的注解或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。
方法级别权限
这样就可实现简单的基于url的权限控制,至于要做到更细粒度的权限控制就需要继续配置。
注解式的权限控制需要配置两个Bean,第一个是AdvisorAutoProxyCreator,代理生成器,需要借助SpringAOP来扫描@RequiresRoles和@RequiresPermissions等注解,生成代理类实现功能增强,从而实现权限控制。需要配合AuthorizationAttributeSourceAdvisor一起使用,否则权限注解无效。
配置完上面两个Bean之后我们就可以使用注解来控制权限了,Shiro中的权限注解有很多,我们最常用的其实就两个,@RequiresRoles和@RequiresPermissions,前者是角色验证,后者是权限验证。他们都可以传入两个参数,value是必须的,可以传入一个字符数组,表示一个或多个角色(权限),另一个参数logical有两个值可选,AND和OR,默认为AND,表示这组角色(权限)是必须都有还是仅需要一个就能访问。
Shiro的验证与授权实现
认证执行过程:
1、通过ini配置文件创建securityManager
2、调用subject.login方法主体提交认证,提交的token
3、securityManager进行认证,securityManager最终由ModularRealmAuthenticator进行认证。
4、ModularRealmAuthenticator调用IniRealm(给realm传入token) 去ini配置文件中查询用户信息
5、IniRealm根据输入的token(UsernamePasswordToken)从 shiro.ini查询用户信息,根据账号查询用户信息(账号和密码)
如果查询到用户信息,就给ModularRealmAuthenticator返回用户信息(账号和密码)如果查询不到,就给ModularRealmAuthenticator返回null
6、ModularRealmAuthenticator接收IniRealm返回Authentication认证信息
如果返回的认证信息是null,ModularRealmAuthenticator抛出异常(org.apache.shiro.authc.UnknownAccountException)如果返回的认证信息不是null(说明inirealm找到了用户),对IniRealm返回用户密码 (在ini文件中存在) 和 token中的密码 进行对比,如果不一致抛出异常(org.apache.shiro.authc.IncorrectCredentialsException)
授权执行过程:
1、对subject进行授权,调用方法isPermitted("permission串")
2、SecurityManager执行授权,通过ModularRealmAuthorizer执行授权
3、ModularRealmAuthorizer执行realm(自定义的Realm)从数据库查询权限数据
调用realm的授权方法:doGetAuthorizationInfo
4、realm从数据库查询权限数据,返回ModularRealmAuthorizer
5、ModularRealmAuthorizer调用PermissionResolver进行权限串比对
6、如果比对后,isPermitted中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则 没有权限,抛出异常。
shiro核心原理简述
shiro的核心是javaservlet规范中的filter,通过配置拦截器,使用拦截器链来拦截请求,如果允许访问,则通过。通常情况下,系统的登录、退出会配置拦截器。登录的时候,调用subject.login(token),token是用户验证信息,这个时候会在Realm中doGetAuthenticationInfo方法中进行认证。这个时候会把用户提交的验证信息与数据库中存储的认证信息进行比较,一致则允许访问,并在浏览器种下此次回话的cookie,在服务器端存储session信息。退出的时候,调用subject.logout(),会清除回话信息
附demo项目地址:
https://gitee.com/myinfo_xu/shiro-simple-demo
参考文章:
1.https://blog.csdn.net/u014695188/article/details/52347372?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-1.control&spm=1001.2101.3001.4242