欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

基于springboot的shiro基本操作

时间:2023-08-03
文章目录

Shiro执行逻辑Shiro的组织架构Shiro身份验证

演示Realm配置 Shiro授权

授权方式演示 Springboot集成Shiro

拦截用户请求认证用户密码加密授权 Shiro执行逻辑

Application Code:编写的应用程序Subject: 主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;Realm:领域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

最简单的一个Shiro应用

应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。 Shiro的组织架构

由图看出,Security Manager是最复杂的系统

Security Manager内部

Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;Authorizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所以呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能Cryptography:密码模块,Shiro 提供了一些常见的加密组件用于如密码加密 / 解密的。

领域Realm的实现

可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm

Shiro身份验证

身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来表明他就是他本人,如提供身份证,用户名 / 密码来证明。

shiro中进行身份验证,需要提供principals(身份)和credentials(证明)

身份告诉shiro你是什么用户,证明用来证明你是这个身份。

最常见的principals和credentials就是用户名和密码了

principals = 用户名/邮箱/手机号等等

credentials = 密码/验证码/数字凭证等等

下面利用springboot环境来演示Shiro如何进行身份验证

演示

准备环境依赖

org.apache.shiro shiro-core 1.2.2

准备用户身份

采用shiro.ini配置文件指定两个subject,一个用户名为wang,密码123,一个为zhang,密码123

[users]wang = 123zhang = 123

测试代码

import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.config.IniSecurityManagerFactory;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.subject.Subject;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTestclass DemoApplicationTests { @Test public void MainTest(){// 初始化SecurityManager工厂,这里用ini文件作为realm IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");// 利用工厂产生一个SecurityManager实例 SecurityManager securityManager = factory.getInstance();// 将sm实例绑定到shiro提供的工具类中,下面的操作要利用此工具类 SecurityUtils.setSecurityManager(securityManager);// 获得主体,这里的主体是ini文件中的两个用户 Subject subject = SecurityUtils.getSubject();// 利用shiro提供的类创建一个虚拟用户用来登录 UsernamePasswordToken token = new UsernamePasswordToken("wang", "123"); try {// 对身份进行验证 subject.login(token); } catch (AuthenticationException e) { System.out.println("用户名和密码错误,错误信息:"+e.getMessage()); }// 判断用户是否验证通过,通过返回true,不通过返回false boolean authenticated = subject.isAuthenticated(); if(authenticated) System.out.println("登录成功"); else System.out.println("登录失败");// 退出登录 subject.logout(); }}

对29行错误信息的解释

如果身份验证失败请捕获 AuthenticationException 或其子类,常见的如: DisabledAccountException(禁用的帐号)、LockedAccountException(锁定的帐号)、UnknownAccountException(错误的帐号)、ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException (错误的凭证)、ExpiredCredentialsException(过期的凭证)等,具体请查看其继承关系;对于页面的错误消息展示,最好使用如 “用户名 / 密码错误” 而不是 “用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;

从如上代码可总结出身份验证的步骤:

收集用户身份 / 凭证,即如用户名 / 密码;调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户错误信息;否则登录成功;最后调用 Subject.logout 进行退出操作。

如上测试的几个问题:

用户名 / 密码硬编码在 ini 配置文件,以后需要改成如数据库存储,且密码需要加密存储;用户身份 Token 可能不仅仅是用户名 / 密码,也可能还有其他的,如登录时允许用户名 / 邮箱 / 手机号同时登录。

从代码可以看出这些操作好像是subject完成的,但实际上,全部是subject委托给securityManager完成的,而SecurityManager对于身份验证的操作,会交给自己的子模块Authenticator完成,Authenticator将获取到的token(用户信息及凭证)交给realm,realm如果没有抛出异常,那么身份就验证成功了。

可以看出,Realm是验证身份的最终站,因此,我们可以配置多个Realm来多次验证Authenticator交过来的token

Realm配置

Realm是安全数据源,我们刚刚的演示中使用了ini文件作为数据源,那么我们使用的Realm在shiro中就是

org.apache.shiro.realm.text.IniRealm

shiro中定义了Realm接口,他是所有Realm的父类接口,代码如下

String getName(); //返回一个唯一的Realm名字boolean supports(AuthenticationToken token); //判断此Realm是否支持此TokenAuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException; //根据Token获取认证信息

我们可以自定义Realm,然后通过ini配置文件指定securityManager的Realm实现,这样便可实现多Realm验证

创建一个shiro-multi-realm.ini文件,假设现在我们自定义了两个Realm

那么可以通过下面的配置文件进行多Realm配置

#声明一个realmmyRealm1 = com.whr.realm.MyRealm1myRealm2 = com.whr.realm.MyRealm2#$符号表示引用上面已经定义好的对象#指定securityManager的realms实现,这一行可删除,删除后将按照上面声明的顺序加载securityManager.realms=$myRealm1,$myRealm2

securityManager 会按照 realms 指定的顺序进行身份认证。此处我们使用显示指定顺序的方式指定了 Realm 的顺序,如果删除 “securityManager.realms=$myRealm1,$myRealm2”,那么securityManager 会按照 realm 声明的顺序进行使用(即无需设置 realms 属性,其会自动发现),当我们显示指定 realm 后,其他没有指定 realm 将被忽略,如 “securityManager.realms=$myRealm1”,那么 myRealm2 不会被自动设置进去。

Shiro提供的Realm

以后一般继承 AuthorizingRealm(授权)即可;其继承了 AuthenticatingRealm(即身份验证),而且也间接继承了 CachingRealm(带有缓存实现)。

Shiro授权

为什么需要授权,因为在页面中,有些资源我们不想让没有权限的人看到,例如在一个后台管理系统登录窗口页面,我们会有几个角色,例如:管理员(admin),用户(user)等等,那么管理员和用户能看到的页面和进行的操作一定是不同的。这时我们就需要对不同的角色进行授权。

在授权中需了解的几个关键对象:

主体(Subject)即访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。

资源(Resource) 在应用中用户可以访问的URL,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。

权限(Permission) 安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如: 访问用户列表页面 查看/新增/修改/删除用户数据(即很多时候都是 CRUD(增查改删)式权限控制)打印文档等。如上可以看出,权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。所以后续还需要把权限赋予给用户,即定义哪个用户允许在某个资源上做什么操作(权限),Shiro 不会去做这件事情,而是由实现人员提供。

角色(Role)。 角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。

授权方式

Shiro 支持三种方式的授权:

编程式:通过写 if/else 授权代码块完成:

Subject subject = SecurityUtils.getSubject();if(subject.hasRole(“admin”)) { //有权限} else { //无权限}

注解式:通过在执行的 Java 方法上放置相应的注解完成:

@RequiresRoles("admin")public void hello() { //有权限}

没有权限将抛出相应的异常;

JSP/GSP 标签:在 JSP/GSP 页面通过相应的标签完成:

演示

基于角色的演示控制

[users]wang = 123 ,admin,userzhang = 123 ,user

给wang用户设置了两个角色,admin和user,zhang用户只有user角色

import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.config.IniSecurityManagerFactory;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.subject.Subject;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTestclass DemoApplicationTests { static { SecurityManager securityManager = new IniSecurityManagerFactory("classpath:shiro.ini"). getInstance(); SecurityUtils.setSecurityManager(securityManager); } @Test public void MainTest(){ Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken zhang = new UsernamePasswordToken("zhang", "123"); subject.login(zhang); if(subject.hasRole("admin")){ System.out.println("have permission"); }else{ System.out.println("don't have permission"); }// 退出登录 subject.logout(); }}

上述示例只演示了测试此用户是否拥有某一角色,下面给此角色赋予权限

[users]zhang=123,admin,userwang=123,user[roles]admin = admin:create,admin:delete,admin:updateuser = user:read

@Test public void MainTest(){ Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken zhang = new UsernamePasswordToken("zhang", "123"); subject.login(zhang); if(subject.hasRole("user")){ System.out.println("have role");// 判断此用户是否拥有删除权限,没有则报错 try{ subject.checkPermission("admin:delete"); }catch(UnauthorizedException e){ System.out.println(e.getMessage()); } }else{ System.out.println("don't have role"); }// 退出登录 subject.logout(); }

//报错信息Subject does not have permission [admin:delete]

Springboot集成Shiro

开始之前,所有页面结构

在pom.xml中添加如下三个依赖

org.apache.shiro shiro-core 1.2.2 org.apache.shiro shiro-spring 1.7.1 org.springframework.boot spring-boot-starter-thymeleaf

在yml文件中进行配置

spring: thymeleaf: #配置模板路径,默认是templates,可以不用配置 prefix: classpath:/templates/ suffix: .html cache: false #关闭页面缓存

自定义一个MyShiroConfig

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class MyShiroConfig {// 利用securityManager对象来注册shiroFilter @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean (@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();// 关联SecurityManager shiroFilter.setSecurityManager(defaultWebSecurityManager); return shiroFilter; }// 注册SecurityManager对象,其中底层的realm用自己的实现 @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 关联realm securityManager.setRealm(userRealm); return securityManager; }// 注册realm,这个realm是自定义的 @Bean public UserRealm userRealm(){ return new UserRealm(); }}

自定义一个Realm

import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.realm.AuthenticatingRealm;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;public class UserRealm extends AuthorizingRealm {// 给用户授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行doGetAuthorizationInfo-PrincipalCollection"); return null; }// 认证用户的token @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行doGetAuthorizationInfo-AuthenticationToken"); return null; }}

上面的代码自定义了UserRealm,这个说明了SecurityManager需要realm时的访问对象,然后定义了一个过滤器,这个过滤器可以拦截用户的请求并进行页面的跳转

在前端控制器中拿到前端发来的数据后,封装成token,交付给SecurityManager对象,sm再去Realm中去验证用户的身份及权限。

例如,我们拥有下面的一个Controller控制器

@RequestMapping("/userLogin") public String login(String username, String password, Model model){// 获取当前登录的用户 Subject subject = SecurityUtils.getSubject();// 封装用户的登录数据 UsernamePasswordToken token = new UsernamePasswordToken(username, password); try{// 这里的login将会把token交付给securityManager,sm再去UserRealm中认证用户 subject.login(token);// 登录成功跳转 return "index"; }catch (UnknownAccountException e){ //用户名不存在 model.addAttribute("msg","用户名不存在"); return "login"; }catch (IncorrectCredentialsException e){ //密码不正确 model.addAttribute("msg","密码错误"); return "login"; } }

当用户发起/userLogin请求时,前端将用户名和密码封装成一个token,同时调用subject的login方法,会将token交给SM对象,SM对象再访问UserRealm中去验证用户身份

拦截用户请求

如果想要拦截用户请求,那么需要在MyShiroConfig类中修改shiroFilter进行配置

@Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean (@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(defaultWebSecurityManager);// 添加shiro的内置过滤器,对请求进行拦截 Map filterMap = new linkedHashMap<>(); filterMap.put("/user Map filterMap = new linkedHashMap<>();// 配置权限,配置权限一定要在认证前才能生效 filterMap.put("/user/add","perms[user:add]");// 给delete请求设置只有拥有权限才能访问,此时会跳转到realm中 filterMap.put("/user/delete","perms[user:delete]"); filterMap.put("/userpublic class UserRealm extends AuthorizingRealm {// 给用户授权,每次过滤器拦截到perms[user:add]都会跳转到此进行授权认证 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行doGetAuthorizationInfo-PrincipalCollection"); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();// 如果此用户有角色,只需要addRole即可// simpleAuthorizationInfo.addRole("role"); Subject subject = SecurityUtils.getSubject();// 通过下面这两个方法都可以拿到认证用户方法传来的perms String perms = (String) subject.getPrincipal();// String perms = principalCollection.getPrimaryPrincipal();// 将此权限赋予给此用户 simpleAuthorizationInfo.addStringPermission(perms); return simpleAuthorizationInfo; }// 认证用户的交付过来的token @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行doGetAuthorizationInfo-AuthenticationToken");// 假定的用户名和密码。实际开发中去数据库中取 String username = "root"; String password = "123456";// 假定用户只有add权限没有delete权限 String perms = "user:add"; UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; if (!token.getUsername().equals(username)) { return null; }// 将用户的权限perms通过此返回值传输到doGetAuthorizationInfo方法中,实际开发中传输对象 return new SimpleAuthenticationInfo(perms,password,""); }}

到此springboot的基本操作就演示完毕了,在jsp的页面中,shiro可以利用标签进行一些测试,在thymeleaf中也可以利用标签,只不过需要加载新的jar包,是thymeleaf和shiro整合的jar包,具体的我没有研究,具体用到再说。

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。