shiro学习笔记
创始人
2025-05-28 06:21:10
0

目录

Shiro

概述

shiro是什么

与 SpringSecurity 的对比

基本功能

介绍

原理

基本使用

环境搭建

引入依赖

ini文件

登录认证

登录认证概念

登录认证基本流程

代码实现

角色授权

授权概念

授权方式

授权流程

代码实现

Shiro使用MD5加密,Md5Hash类

自定义登陆认证,继承AuthenticatingRealm类

Shiro整合springboot

环境准备(依赖、yml、sql、entity/mapper/service/serviceImpl)

登录认证实现

service编写获取用户信息方法

自定义登陆认证,realm

编写shiro配置类

controller编写登录方法

启动测试

前端页面,shiro整合Thymeleaf

多个 realm 的认证策略设置

实现原理

代码实现

remember me

代码实现

退出登陆

代码实现

授权、角色认证

后端接口服务注解

授权验证-获取角色进行验证

授权验证-获取权限进行验证

前端页面授权验证


Shiro

概述

shiro是什么

Apache Shiro 是一个功能强大且易于使用的 Java 安全(权限)框架。Shiro 可以完成:认证、授权、加密、会话管理、与 Web 集成、缓存 等。借助 Shiro 您可以快速轻松地保护任何应用程序——从最小的移动应用程序到最大的 Web 和企业应用程序。

自 2003 年以来,框架格局发生了相当大的变化,因此今天仍然有很多系统在使用 Shiro。这与 Shiro 的特性密不可分。

  • 易于使用:使用 Shiro 构建系统安全框架非常简单。就算第一次接触也可以快速掌握。
  • 全面:Shiro 包含系统安全框架需要的功能,满足安全需求的“一站式服务”。
  • 灵活:Shiro 可以在任何应用程序环境中工作。虽然它可以在 Web、EJB 和 IoC 环境中工作,但不需要依赖它们。Shiro 也没有强制要求任何规范,甚至没有很多依赖项。
  • 强力支持 Web:Shiro 具有出色的 Web 应用程序支持,可以基于应用程序 URL 和 Web 协议(例如 REST)创建灵活的安全策略,同时还提供一组 JSP 库来控制页面输出。
  • 兼容性强:Shiro 的设计模式使其易于与其他框架和应用程序集成。Shiro 与 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 等框架无缝集成。
  • 社区支持:Shiro 是 Apache 软件基金会的一个开源项目,有完备的社区支持,文档支持。如果需要,像 Katasoft 这样的商业公司也会提供专业的支持和服务。

与 SpringSecurity 的对比

  1. Spring Security 基于 Spring 开发,项目若使用 Spring 作为基础,配合 Spring Security 做权限更加方便,而 Shiro 需要和 Spring 进行整合开发;
  2. Spring Security 功能比 Shiro 更加丰富些,例如安全维护方面;
  3. Spring Security 社区资源相对比 Shiro 更加丰富;
  4. Shiro 的配置和使用比较简单,Spring Security 上手复杂些;
  5. Shiro 依赖性低,不需要任何框架和容器,可以独立运行.Spring Security 依赖 Spring 容器;
  6. shiro 不仅仅可以使用在 web 中,它可以工作在任何应用环境中。在集群会话时 Shiro 最重要的一个好处或许就是它的会话是独立于容器的。

基本功能

 

介绍

  1. Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
  2. Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用 户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户 对某个资源是否具有某个权限;
  3. Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的 所有 信息都在会话中;会话可以是普通 JavaSE 环境,也可以是 Web 环境的;
  4. Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  5. Web Support:Web 支持,可以非常容易的集成到 Web 环境;
  6. Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可 以提高效率;
  7. Concurrency:Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  8. Testing:提供测试支持;
  9. Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  10. Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了

原理

Shiro架构(外部)

从外部来看 Shiro ,即从应用程序角度的来观察如何使用Shiro 完成工作

image-20220927225903324

  • Shiro 架构
  • Subject:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject。Subject代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;与Subject的所有交互都会委托给SecurityManager;Subject其实是一个门面,SecurityManager才是实际的执行者;
  • SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且其管理着所有Subject;可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC中DispatcherServlet的角色
  • Realm:Realm提供待验证数据的比对值,即安全数据源,可以理解为数据的源头,可以是数据库,文件等。Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource

shiro架构(内部)

image-20220927230118046

  • Subject:任何可以与应用交互的“用户”。通常我们会将Subject对象理解为一个用户,同样的它也有可能是一个三方程序,它是一个抽象的概念,可以理解为任何与系统交互的“东西”都是Subject。
  • SecurityManager:相当于SpringMVC中的DispatcherServlet;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证、授权、会话及缓存的管理。
  • Authenticator:负责Subject认证,是一个扩展点,可以自定义实现;可以使用认证策略(AuthenticationStrategy),即什么情况下算用户认证通过了;
  • Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
  • Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的Realm;
  • SessionManager:管理Session生命周期的组件;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境
  • CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密。

基本使用

环境搭建

引入依赖

shiro核心包和通用日志包

org.apache.shiroshiro-core1.9.0commons-loggingcommons-logging1.2

ini文件

resources目录下创建ini文件: 

[users]
zhangsan=1234
lisi=123

登录认证

登录认证概念

  1. 身份验证:一般需要提供如身份ID等一些标识信息来表明登录者的身份,如提供email,用户名/密码来证明。
  2. 在shiro中,用户需要提供principals(身份)和credentials(证明)给shiro,从而应用能验证用户身份:
  3. principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/邮箱/手机号。
  4. credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
  5. 最常见的principals和credentials组合就是用户名/密码

登录认证基本流程

  1. 收集用户身份/凭证,即如用户名/密码
  2. 调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException异常,根据异常提示用户错误信息;否则登录成功
  3. 登录认证。创建自定义的 Realm 类,继承 org.apache.shiro.realm.AuthenticatingRealm类,实现 doGetAuthenticationInfo() 方法

image-20220927225324731

代码实现

四步走:

  1. 初始化获取SecurityManager
  2. 获取subject对象
  3. 创建token对象,web应用用户名密码从页面传递
  4. 完成登录
@Testvoid login(){
//        1.初始化获取SecurityManagerIniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);
//        2.获取subject对象Subject subject = SecurityUtils.getSubject();
//        3.创建token对象,web应用用户名密码从页面传递UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "1234");
//        4.完成登录。try{subject.login(usernamePasswordToken);System.out.println("登陆成功");}catch(UnknownAccountException e){e.printStackTrace();System.out.println("用户不存在");}catch(IncorrectCredentialException e){e.printStackTrace();System.out.println("密码错误");}catch(AuthenticationException e){e.printStackTrace();System.out.println("其他原因登录失败");}}

 

 

角色授权

授权概念

  1. 授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面 操作等)。在授权中需了解的几个关键对象:主体(Subject)资源(Resource)权限 (Permission)角色(Role)
  2. 主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权 后才允许访问相应的资源。
  3. 资源(Resource):在应用中用户可以访问的URL,比如访问 JSP 页面、查看/编辑 某些 数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
  4. 权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户 有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用 户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控 制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允 许。
  5. Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限, 即实例级别的)
  6. 角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限

授权方式

编程式:

subject.hasRole("admin")

注解式:

@RequiresRoles("admin")

JSP/GSP 标签:


授权流程

  1. 首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给 Authorizer;
  2. Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
  3. 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
  4. Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole* 会返回 true,否则返回false表示授权失败

image-20220927224247790

代码实现

添加角色和权限:

 

一般情况下会赋予用户角色而不是权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限

    @Testvoid testLogin(){//        1.初始化获取SecurityManagerIniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);
//        2.获取subject对象Subject subject = SecurityUtils.getSubject();
//        3.创建token对象,web应用用户名密码从页面传递UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "z3");
//        4.完成登录subject.login(usernamePasswordToken);System.out.println(usernamePasswordToken);//判断角色System.out.println("当前subject是否拥有此角色:"+subject.hasRole("role1"));
//判断权限方法1System.out.println("当前subject是否拥有此权限:"+subject.isPermitted("user:insert"));    //判断权限方法2,无此权限直接抛出权限异常AuthenticationException       subject.checkPermission("user:update");    

 

 

Shiro使用MD5加密,Md5Hash类

实际系统开发中,一些敏感信息需要进行加密,比如说用户的密码。Shiro 内嵌很多常用的加密算法,比如 MD5 加密。Shiro 可以很简单的使用信息加密。

MD5为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。MD5与对称和非对称加密算法不同,这两种密码是防止信息被窃取,而摘要算法的目标是用于证明原文的完整性。

虽然在04年被证明可以被破解,因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途,但是MD5可以通过多次加密确保安全性。

@Test
void testMD5(){
//密码明文String t= "z3";String password = t;
//使用md5加密Md5Hash md5Hash = new Md5Hash(password);
//md5Hash.toHex()是转成16进制字符串System.out.println("md5加密 = " + md5Hash.toHex());    //a61d1457beb4684e254ce60379c8ae7b
//带盐的md5加密。盐就是在密码明文后拼接字符串,然后再加密。Md5Hash saltMd5Hash = new Md5Hash(password, "salt");System.out.println("带盐的md5加密 = " + saltMd5Hash);
//带盐的多次md5加密。为了避免被破解,可以多次迭代加密Md5Hash saltMd5Hash3 = new Md5Hash(password, "salt", 3);System.out.println("带盐的3次md5加密 = " + saltMd5Hash3);//使用Md5Hash的父类SimpleHash加密。SimpleHash simpleHash = new SimpleHash("MD5", password, "salt", 3);System.out.println("父类的三次带盐加密 = " + simpleHash);assert simpleHash.equals(saltMd5Hash3);
}

 

 

自定义登陆认证,继承AuthenticatingRealm类

Shiro 默认的登录认证是不带加密的,如果想要实现加密认证需要自定义登录认证, 自定义 Realm。

Realm:Realm提供待验证数据的比对值,即安全数据源,可以理解为数据的源头,可以是数据库,文件等。 Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource

登录认证基本流程

  1. 收集用户身份/凭证,即如用户名/密码
  2. 调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException异常,根据异常提示用户错误信息;否则登录成功
  3. 登录认证。创建自定义的 Realm 类,继承AuthenticatingRealm类,实现 doGetAuthenticationInfo() 方法

获取进行对比的信息,认证逻辑还是按照Shiro的底层认证逻辑完成认证: 

public class MyRealm extends AuthenticatingRealm {/***自定义的登录认证方法,Shiro的login方法底层会调用该类的认证方法完成登录认证*需要配置自定义的realm生效,在ini文件中配置,或者Springboot中配置*该方法只是获取进行对比的信息,认证逻辑还是按照Shiro的底层认证逻辑完成认证* @param token 令牌* @return {@link AuthenticationInfo}* @throws AuthenticationException 身份验证异常*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//        1 获取身份信息(用户名)String principal = token.getPrincipal().toString();//        2 获取凭证信息(密码)String credentials = new String((char[]) token.getCredentials());System.out.println("认证用户信息:" + principal+"---"+ credentials);    //认证用户信息:zhangsan---z3//        3 比对数据库里查到的用户名和token里获得的用户名。这里用“zhangsan”假装是数据库查到的用户名if("zhangsan".equals(principal)) {String t= "7174f64b13022acd3c56e2781e098a5f";String pwd = t;//"zhangsan"三次带盐md5加密后的密文return new SimpleAuthenticationInfo(token.getPrincipal(),pwd,    //数据库里查到的密码ByteSource.Util.bytes("salt"),     //shiro工具类获取md5加盐信息principal);}
//        4 创建封装校验逻辑对象return null;}
}

添加配置信息,让shiro知晓你使用的是自定义的Realm

#在shiro.ini中添加配置信息
[main]md5CredentialsMatcher=org.apache.shiro.authc.cre 
dential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3
myrealm=com.atguigu.shirotest.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f,role1, role2
lisi=l4 
[roles]
role1=user:insert,user:select

启动测试:

 

 

Shiro整合springboot

环境准备(依赖、yml、sql、entity/mapper/service/serviceImpl)

 数据库表

在shirodb数据库下创建表: 

create table user
(id   bigint auto_increment comment '编号'primary key,name varchar(30) null comment '用户名',pwd  varchar(50) null comment '密码',rid  bigint      null comment '角色编号'
)comment '用户表' charset = utf8;

 

  • 引入依赖
org.springframework.bootspring-boot-starter-parent2.2.1.RELEASE
org.apache.shiroshiro-spring-boot-web-starter1.9.0com.baomidoumybatis-plus-boot-starter3.0.5mysqlmysql-connector-java5.1.46org.projectlomboklombokorg.springframework.bootspring-boot-starter-thymeleaforg.springframework.bootspring-boot-starter-web

  • yml配置shiro登录接口
# mybatis配置
mybatis-plus:configuration:# 日志log-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: classpath:mapper/*.xmlspring:
# 数据库配置datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/shirodb?characterEncoding=utf-8&useSSL=falsepassword: passwordusername: username
# controller的@ResponseBody将方法返回对象转换后的json格式jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8    #东八区shiro:
# 登录接口loginUrl: /myController/login
  • 启动类
@SpringBootApplication
@MapperScan("com.fate.shiro.mapper")
public class ShiroApplication {public static void main(String[] args) {SpringApplication.run(ShiroApplication.class, args);}
}

实体类与mapper/service

mapper继承BaseMapper,serverice继承IService。 

登录认证实现

service编写获取用户信息方法

@Service
public class UserServiceImpl extends ServiceImplimplements UserService{/*** 根据用户名得到用户信息** @param name 名字* @return {@link User}*/@Overridepublic User getUserInfoByName(String name) {LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getName,name);return baseMapper.selectOne(queryWrapper);}
}

自定义登陆认证,realm

在realm包下: 

Realm:Realm提供待验证数据的比对值,即安全数据源,可以理解为数据的源头,可以是数据库,文件等。 Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource 

注意:

这里继承的是 AuthorizingRealm类 ,之前基本使用里继承的是AuthenticatingRealm类。

AuthorizingRealm是AuthenticatingRealm的子类,在自定义登录方法的基础上,多了一个自定义授权的方法。

@Component
public class MyRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;/*** 自定义授权** @param principals 权限* @return {@link AuthorizationInfo}*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}/*** 自定义身份验证** @param token 令牌* @return {@link AuthenticationInfo}* @throws AuthenticationException 身份验证异常*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//        1. 获取传来token的用户身份信息(用户名)。Principal译为当事人、校长、首要的String name = token.getPrincipal().toString();
//        2. 调用业务层获取用户信息User user = userService.getUserInfoByName(name);
//        3. 如果根据token里的用户名从数据库里查到了数据,将数据封装返回if (user != null) {return new SimpleAuthenticationInfo(token.getPrincipal(),    user.getPwd(),    //数据库的密码ByteSource.Util.bytes("salt"),    //shiro工具类获取md5加盐信息token.getPrincipal().toString());}return null;}
}

编写shiro配置类

@Slf4j
@Configuration
public class ShiroConfig {@Autowiredprivate MyRealm myRealm;//配置SecurityManager。SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且其管理着所有Subject;可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC中DispatcherServlet的角色@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager() {
//        1. 创建DefaultWebSecurityManager对象DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//        2. 创建加密对象,配置相关属性HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//          2.1  加密,md5迭代三次matcher.setHashAlgorithmName("md5");matcher.setHashIterations(3);
//        3. 将加密对象存储到myRealm中myRealm.setCredentialsMatcher(matcher);
//        4. 将myRealm存入DefaultWebSecurityManager对象defaultWebSecurityManager.setRealm(myRealm);
//        5. 返回DefaultWebSecurityManagerlog.info("DefaultWebSecurityManager 初始化成功");return defaultWebSecurityManager;}//配置shiro内置过滤器拦截范围@Beanpublic DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();
//        设置无需认证即可访问的资源。defaultShiroFilterChainDefinition.addPathDefinition("/myController/userLogin","anon");defaultShiroFilterChainDefinition.addPathDefinition("/myController/login","anon");
//        设置需要认证的拦截范围defaultShiroFilterChainDefinition.addPathDefinition("/**","authc");return defaultShiroFilterChainDefinition;}}

controller编写登录方法

@RestController
@RequestMapping("myController")
public class MyController {/*** 登录* @param username 用户名* @param password 密码* @return {@link String}*/@GetMapping("userLogin")public String login(@RequestParam("username") String username, @RequestParam("password") String password,HttpSession 
session){//1.获取主体对象subjectSubject subject = SecurityUtils.getSubject();//2.封装用户名和密码到tokenUsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);//3.调用subject.login()登录验证try{subject.login(usernamePasswordToken);session.setAttribute("user",token.getPrincipal().toString()); return "登陆成功";}catch (Exception e) {e.printStackTrace();return "登陆失败";}}
}

启动测试

 

数据库里存的也是“张三” 和md5三次加密后的“z3”。

 

前端页面,shiro整合Thymeleaf

编写登录页面login.html

 



Title

Shiro 登录认证


用户名:
密码:

添加登陆成功后的主页面main界面




 
Title
 


Shiro 登录认证后主页面

登录用户为:

controller登录方法添加session、添加跳转页面方法

 

//跳转登录页面
@GetMapping("login") 
public String login(){ 
//跳转到login页面,因为没有@ResponseBody,所以会把返回值当成页面名称去查找,如果没有回报404错误return "login"; 
}
//登录认证
@GetMapping("userLogin")
public String userLogin(String name, String pwd, HttpSession 
session){
//1 获取  Subject 对象Subject subject = SecurityUtils.getSubject();
//2 封装请求数据到 token 对象中AuthenticationToken token = new UsernamePasswordToken(name,pwd); 
//3 调用  login 方法进行登录认证try {subject.login(token);session.setAttribute("user",token.getPrincipal().toString()); 
//跳转到main页面return "main";} catch (AuthenticationException e) { e.printStackTrace();System.out.println("登录失败"); return "登录失败";} 
}

启动测试:

 

 

 

多个 realm 的认证策略设置

实现原理

当应用程序配置多个 Realm 时,例如:用户名密码校验、手机号验证码校验等等。 Shiro 的 模块化认证器ModularRealmAuthenticator 会使用内部的AuthenticationStrategy 组件判断认证是成功还是失败
AuthenticationStrategy 是一个无状态的组件,它在身份验证尝试中被询问 4 次(这 4 次交互所需的任何必要的状态将被作为方法参数):

  1. 在所有 Realm 被调用之前
  2. 在调用 Realm 的 getAuthenticationInfo 方法之前
  3. 在调用 Realm 的 getAuthenticationInfo 方法之后
  4. 在所有 Realm 被调用之后

认证策略的另外一项工作就是聚合所有 Realm 的结果信息封装至一个AuthenticationInfo 实例中,并将此信息返回,以此作为 Subject 的身份信息。


Shiro 中定义了 3 种认证策略的实现:

AuthenticationStrategy class描述
AtLeastOneSuccessfulStrategy只要有一个(或更多)的 Realm 验证成功,那么认证将视为成功
FirstSuccessfulStrategy第一个 Realm 验证成功,整体认证将视为成功,且后续 Realm 将被忽略
AllSuccessfulStrategy所有 Realm 成功,认证才视为成功

ModularRealmAuthenticator 内置的认证策略默认实现是 AtLeastOneSuccessfulStrategy 方式。可以通过配置修改策略

代码实现

@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){ 
//1 创建  defaultWebSecurityManager 对象DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//2 创建认证对象,并设置认证策略ModularRealmAuthenticator modularRealmAuthenticator = new 
ModularRealmAuthenticator();modularRealmAuthenticator.setAuthenticationStrategy(new 
AllSuccessfulStrategy());defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator) 
;
//3 封装  myRealm 集合List list = new ArrayList<>(); list.add(myRealm);list.add(myRealm2);
//4 将 myRealm 存入 defaultWebSecurityManager 对象defaultWebSecurityManager.setRealms(list);
//5 返回return defaultWebSecurityManager;
}

remember me

Shiro 提供了记住我(RememberMe)的功能,比如访问一些网站时,关闭了浏览器, 下次再打开时还是能记住你是谁, 下次访问时无需再登录即可访问。

基本流程

  1. 首先在登录页面选中 RememberMe 然后登录成功;如果是浏览器登录,一般会 把 RememberMe 的 Cookie 写到客户端并保存下来;
  2. 关闭浏览器再重新打开;会发现浏览器还是记住你的;
  3. 访问一般的网页服务器端,仍然知道你是谁,且能正常访问;
  4. 但是,如果我们访问电商平台时,如果要查看我的订单或进行支付时,此时还是需要再进行身份认证的,以确保当前用户还是你。

代码实现

修改配置类

@Configuration
public class ShiroConfig {@Autowiredprivate MyRealm myRealm;//配置 SecurityManager@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager() {
//1 创建  defaultWebSecurityManager 对象DefaultWebSecurityManager defaultWebSecurityManager = newDefaultWebSecurityManager();
//2 创建加密对象,并设置相关属性 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//2.1 采用  md5 加密matcher.setHashAlgorithmName("md5");
//2.2 迭代加密次数matcher.setHashIterations(3);
//3 将加密对象存储到  myRealm 中myRealm.setCredentialsMatcher(matcher);
//4 将  myRealm 存入 defaultWebSecurityManager 对象 defaultWebSecurityManager.setRealm(myRealm);
//4.5 设置  rememberMedefaultWebSecurityManager.setRememberMeManager(rememberMeManager());
//5 返回return defaultWebSecurityManager;}//cookie 属性设置public SimpleCookie rememberMeCookie() {SimpleCookie cookie = new SimpleCookie("rememberMe");
//设置跨域
//cookie.setDomain(domain);cookie.setPath("/");cookie.setHttpOnly(true);cookie.setMaxAge(30 * 24 * 60 * 60);return cookie;}//创建 Shiro 的  cookie 管理对象public CookieRememberMeManager rememberMeManager() {CookieRememberMeManager cookieRememberMeManager = newCookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());return cookieRememberMeManager;}//配置 Shiro 内置过滤器拦截范围@Beanpublic DefaultShiroFilterChainDefinitionshiroFilterChainDefinition() {DefaultShiroFilterChainDefinition definition = newDefaultShiroFilterChainDefinition();
//设置不认证可以访问的资源definition.addPathDefinition("/myController/userLogin", "anon");definition.addPathDefinition("/myController/login", "anon");
//设置需要进行登录认证的拦截范围definition.addPathDefinition("/**", "authc");
//添加存在用户的过滤器(rememberMe) definition.addPathDefinition("/**", "user");return definition;}
}

修改controller

@GetMapping("userLogin")public String userLogin(String name, String pwd, @RequestParam(defaultValue ="false") boolean rememberMe, HttpSession session) {
//1 获取  Subject 对象Subject subject = SecurityUtils.getSubject();
//2 封装请求数据到  token 对象中AuthenticationToken token = new UsernamePasswordToken(name, pwd, rememberMe);
//3 调用  login 方法进行登录认证try {subject.login(token);session.setAttribute("user", token.getPrincipal().toString());return "main";} catch (AuthenticationException e) {e.printStackTrace();System.out.println("登录失败");return "登录失败";}}//登录认证验证  rememberMe@GetMapping("userLoginRm")public String userLogin(HttpSession session) {session.setAttribute("user", "rememberMe");return "main";}

改造login页面



Title

Shiro 登录认证


用户名:
密码:
记住用户:

退出登陆

用户登录后,配套的有登出操作。直接通过Shiro过滤器即可实现登出

代码实现

  1. 修改main.html

Shiro 登录认证后主页面


登录用户为:
登出
  1. 配置类中添加logout过滤器
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){ 
DefaultShiroFilterChainDefinition definition = new 
DefaultShiroFilterChainDefinition();
//设置不认证可以访问的资源
definition.addPathDefinition("/myController/userLogin","anon"); 
definition.addPathDefinition("/myController/login","anon"); 
//配置登出过滤器
definition.addPathDefinition("/logout","logout"); 
//设置需要进行登录认证的拦截范围
definition.addPathDefinition("/**","authc"); 
//添加存在用户的过滤器(rememberMe) 
definition.addPathDefinition("/**","user"); 
return  definition;
}

授权、角色认证

用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进判

这个工具就是Realm的doGetAuthorizationInfo方法进行判断。触发权限判断的有两种:

  1. 在页面中通过shiro:属性判断
  2. 在接口服务中通过注解@Requires进行判断

后端接口服务注解

通过给接口服务方法添加注解可以实现权限校验,可以加在控制器方法上,也可以加
在业务方法上,一般加在控制器方法上。常用注解如下:

  1. @RequiresAuthentication
    验证用户是否登录,等同于方法subject.isAuthenticated()

  2. @RequiresUser
    验证用户是否被记忆:
    登录认证成功subject.isAuthenticated()为true
    登录后被记忆subject.isRemembered()为true

  3. @RequiresGuest
    验证是否是一个guest的请求,是否是游客的请求
    此时subject.getPrincipal()为null

  4. @RequiresRoles
    验证subject是否有相应角色,有角色访问方法,没有则会抛出异常
    AuthorizationException。
    例如:@RequiresRoles(“aRoleName”)
    void someMethod();
    只有subject有aRoleName角色才能访问方法someMethod()

  5. @RequiresPermissions
    验证subject是否有相应权限,有权限访问方法,没有则会抛出异常
    AuthorizationException。
    例如:

    @RequiresPermissions (“file:read”,”wite:aFile.txt”)
    void someMethod();
    subject必须同时含有file:read和wite:aFile.txt权限才能访问方someMethod()

授权验证-获取角色进行验证

  1. 修改MyRealm

    @Override
    protected AuthorizationInfo 
    doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("进入自定义授权方法");
    //1 创建对象,存储当前登录的用户的权限和角色SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 
    //2 存储角色info.addRole("admin"); 
    //返回return info; 
    }
    
  2. 添加数据库表

    CREATE TABLE `role` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', `name` VARCHAR(30) DEFAULT NULL COMMENT '角色名',`desc` VARCHAR(50) DEFAULT NULL COMMENT '描述',`realname` VARCHAR(20) DEFAULT NULL COMMENT '角色显示名', PRIMARY KEY (`id`)
    ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色表';CREATE TABLE `role_user` (
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', 
    `uid` BIGINT(20)  DEFAULT NULL COMMENT '用户  id',
    `rid` BIGINT(20)  DEFAULT NULL COMMENT '角色  id', 
    PRIMARY KEY (`id`)
    ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色用户映射表';
    
  3. mapper

    @Repository
    public interface UserMapper extends BaseMapper {
    @Select("SELECT NAME FROM role WHERE id IN (SELECT rid FROM 
    role_user WHERE uid=(SELECT id FROM USER WHERE NAME=#{principal}))")
    List getUserRoleInfoMapper(@Param("principal") String 
    principal);
    }
    
  4. service

    @Override
    public List getUserRoleInfo(String principal) { 
    return userMapper.getUserRoleInfoMapper(principal);
    }
    
  5. MyRealm 方法改造

    @Override
    protected AuthorizationInfo 
    doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("进入自定义授权方法"); 
    //获取当前用户身份信息
    String principal = 
    principalCollection.getPrimaryPrincipal().toString();
    //调用接口方法获取用户的角色信息
    List roles = userService.getUserRoleInfo(principal); 
    System.out.println("当前用户角色信息:"+roles);
    //创建对象,存储当前登录的用户的权限和角色
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 
    //存储角色
    }
    info.addRoles(roles);
    //返回 
    return info;
    }
    

授权验证-获取权限进行验证

  1. 添加数据库表

    CREATE TABLE `permissions` (
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', 
    `name` VARCHAR(30) DEFAULT NULL COMMENT '权限名',
    `info` VARCHAR(30) DEFAULT NULL COMMENT '权限信息', 
    `desc` VARCHAR(50) DEFAULT NULL COMMENT '描述', 
    PRIMARY KEY (`id`)
    ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='权限表';CREATE TABLE `role_ps` (
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', 
    `rid` BIGINT(20)  DEFAULT NULL COMMENT '角色  id',
    `pid` BIGINT(20)  DEFAULT NULL COMMENT '权限  id', 
    PRIMARY KEY (`id`)
    ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色权限映射表';
  2. mapper

    @Select({
    ""
    })
    List getUserPermissionInfoMapper(@Param("roles")List 
    roles);
    
  3. service

    @Override
    public List getUserPermissionInfo(List roles) { 
    return userMapper.getUserPermissionInfoMapper(roles);
    }
    
  4. MyRealm

    //自定义授权方法:获取当前登录用户权限信息,返回给 Shiro 用来进行授权对比
    @Override
    protected AuthorizationInfo 
    doGetAuthorizationInfo(PrincipalCollection principalCollection) { 
    System.out.println("进入自定义授权方法");
    //获取当前用户身份信息
    String principal = 
    principalCollection.getPrimaryPrincipal().toString();//调用接口方法获取用户的角色信息
    List roles = userService.getUserRoleInfo(principal); 
    System.out.println("当前用户角色信息:"+roles);
    //调用接口方法获取用户角色的权限信息
    List permissions = 
    userService.getUserPermissionInfo(roles);
    System.out.println("当前用户权限信息:"+permissions); 
    //创建对象,存储当前登录的用户的权限和角色
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 
    //存储角色
    info.addRoles(roles);
    //存储权限信息
    info.addStringPermissions(permissions);
    //返回 
    return info;
    }
    
  5. controller

    //登录认证验证权限
    @RequiresPermissions("user:delete") 
    @GetMapping("userPermissions")
    @ResponseBody
    public String userLoginPermissions() { 
    System.out.println("登录认证验证权限"); 
    return "验证权限成功";
    }
    
  6. main.html

    
    

    Shiro 登录认证后主页面


    登录用户为:
    登出
    测试授权-角色验证
    测试授权-权限验证

前端页面授权验证

添加依赖


com.github.theborakompanioni 
thymeleaf-extras-shiro 
2.0.0

配置类

用于解析 thymeleaf 中的 shiro:相关属性

@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect(); 
}

Thymeleaf 中常用的 shiro:属性

  1. guest 标签

    
    
    

    用户没有身份验证时显示相应信息,即游客访问信息。

  2. user 标签

     
    

    用户已经身份验证/记住我登录后显示相应的信息。

  3. authenticated 标签

     
    
    

    用户已经身份验证通过,即 Subject.login 登录成功,不是记住我登录的。

  4. notAuthenticated 标签

     
    
    

    用户已经身份验证通过,即没有调用 Subject.login 进行登录,包括记住我自动登录的
    也属于未进行身份验证。

  5. principal 标签

    
    
    

    相当于((User)Subject.getPrincipals()).getUsername()。

  6. lacksPermission 标签

     
    
    

    如果当前 Subject 没有权限将显示 body 体内容。

  7. hasRole 标签

     
    
    

    如果当前 Subject 有角色将显示 body 体内容。

  8. hasAnyRoles 标签

     
    
    

    如果当前 Subject 有任意一个角色(或的关系)将显示 body 体内容。

  9. lacksRole 标签

     
    
    

    如果当前 Subject 没有角色将显示 body 体内容。

  10. hasPermission 标签

     
    
    

    如果当前 Subject 有权限将显示 body 体内容

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...