• Apache shiro框架


    一、初识shiro框架

    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

    一、主要功能

    三个核心组件:Subject, SecurityManager 和 Realms.

    Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。

    Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。

    SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

    Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

    从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。

    Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果系统默认的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

    基本功能点

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

    特点

    • 易于理解的 Java Security API
    • 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等)
    • 对角色的简单的签权(访问控制),支持细粒度的签权
    • 支持一级缓存,以提升应用程序的性能
    • 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境
    • 异构客户端会话访问
    • 非常简单的加密 API
    • 不跟任何的框架或者容器捆绑,可以独立运行

    二、基本使用

    1、环境准备

    1、Shiro不依赖容器,创建maven工程 / springboot项目都可

    2、添加shiro依赖

    1. <dependency>
    2. <groupId>org.apache.shirogroupId>
    3. <artifactId>shiro-coreartifactId>
    4. <version>1.4.1version>
    5. dependency>

    2、INI 文件

    Shiro 获取权限相关信息可以通过数据库获取,也可以通过 ini 配置文件获取

    1. [users]
    2. zhangsan=z3
    3. lisi=l4

    3、登录认证

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

    登录认证基本流程

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

    登录认证实例

    创建测试类,获取认证对象,进行登录认证,如下:

    1. package com.syh.shiro.text;
    2. import org.apache.shiro.SecurityUtils;
    3. import org.apache.shiro.authc.*;
    4. import org.apache.shiro.config.IniSecurityManagerFactory;
    5. import org.apache.shiro.mgt.SecurityManager;
    6. import org.apache.shiro.subject.Subject;
    7. public class Role{
    8. public static void main(String[] args) {
    9. //1 初始化获取 SecurityManager
    10. IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:static/shiro.ini");
    11. SecurityManager securityManager = factory.getInstance();
    12. SecurityUtils.setSecurityManager(securityManager);
    13. //2 获取 Subject 对象
    14. Subject subject = SecurityUtils.getSubject();
    15. //3 创建 token 对象,web 应用用户名密码从页面传递
    16. AuthenticationToken token = new UsernamePasswordToken("zhangsan","z3");
    17. //4 完成登录
    18. try {
    19. subject.login(token);
    20. System.out.println("登录成功");
    21. }
    22. catch (UnknownAccountException e) {
    23. e.printStackTrace();
    24. System.out.println("用户不存在");
    25. }
    26. catch (IncorrectCredentialsException e) {
    27. e.printStackTrace();
    28. System.out.println("密码错误");
    29. }
    30. catch (AuthenticationException ae) {
    31. //unexpected condition? error?
    32. }
    33. }
    34. }

    身份认证流程

    • 首先调用 Subject.login(token) 进行登录,其会自动委托给 SecurityManager
    • SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份 验证;
    • Authenticator 才是真正的身份验证者,Shiro API 中核心的身份 认证入口点,此 处可以自定义插入自己的实现;
    • Authenticator 可能会委托给相应的 AuthenticationStrategy 进 行多 Realm 身份 验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
    • Authenticator 会把相应的 token 传入 Realm,从 Realm 获取 身份验证信息,如 果没有返回/抛出异常表示身份验证失败了。此处 可以配置多个Realm,将按照相应的顺序 及策略进行访问。

    4、角色、授权

    授权概念

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

    授权方式

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

    (2)注解式:通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相 应的异 常 

     

    授权流程 

    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表示授权失败

    授权实例

    1、获取角色信息

    (1)给shiro.ini增加角色配置

    1. [users]
    2. zhangsan=z3,role1,role2
    3. lisi=l4

    (2)上述示例代码中添加代码,沟通过hasRole()判断用户是否有指定角色

    1. try {
    2. subject.login(token);
    3. System.out.println("登录成功");
    4. // 沟通过hasRole()判断用户是否有指定角色
    5. boolean role1 = subject.hasRole("role1");
    6. System.out.println("是否拥有此角色:" + role1);
    7. }

    2、判断权限信息信息

    (1)给shiro.ini增加权限配置 

    1. [roles]
    2. role1=user:insert,user:select

    (2)给例子添加代码,判断用户是否有指定权限

    1. //判断权限信息信息
    2. boolean isPermitted = subject.isPermitted("user:insert");
    3. System.out.println("是否拥有此权限:"+isPermitted);
    4. //也可以用 checkPermission 方法,但没有返回值,没权限抛 AuthenticationException
    5. subject.checkPermission("user:select");

    5、Shiro 加密

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

    1. package com.syh.shiro.text;
    2. import org.apache.shiro.crypto.hash.Md5Hash;
    3. import org.apache.shiro.crypto.hash.SimpleHash;
    4. public class ShiroJM {
    5. public static void main(String[] args) {
    6. //密码明文
    7. String password = "z3";
    8. //使用 md5 加密
    9. Md5Hash md5Hash = new Md5Hash(password);
    10. System.out.println("md5 加密:"+md5Hash.toHex());
    11. //带盐的 md5 加密,盐就是在密码明文后拼接新字符串,然后再进行加密
    12. Md5Hash md5Hash2 = new Md5Hash(password,"salt");
    13. System.out.println("md5 带盐加密:"+md5Hash2.toHex());
    14. //为了保证安全,避免被破解还可以多次迭代加密,保证数据安全
    15. Md5Hash md5Hash3 = new Md5Hash(password,"salt",3);
    16. System.out.println("md5 带盐三次加密:"+md5Hash3.toHex());
    17. //使用父类实现加密
    18. SimpleHash simpleHash = new SimpleHash("MD5",password,"salt",3);
    19. System.out.println("父类带盐三次加密:"+simpleHash.toHex());
    20. }
    21. }

    6、Shiro 自定义登录认证

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

     (1)自定义登录认证

    1. package com.syh.shiro.text;
    2. import org.apache.shiro.authc.AuthenticationException;
    3. import org.apache.shiro.authc.AuthenticationInfo;
    4. import org.apache.shiro.authc.AuthenticationToken;
    5. import org.apache.shiro.authc.SimpleAuthenticationInfo;
    6. import org.apache.shiro.realm.AuthenticatingRealm;
    7. import org.apache.shiro.util.ByteSource;
    8. import org.springframework.stereotype.Component;
    9. @Component
    10. public class MyRealm extends AuthenticatingRealm {
    11. //自定义的登录认证方法,Shiro 的 login 方法底层会调用该类的认证方法完成登录认证
    12. //需要配置自定义的 realm 生效,在 ini 文件中配置,或 Springboot 中配置
    13. //该方法只是获取进行对比的信息,认证逻辑还是按照 Shiro 的底层认证逻辑完成认证
    14. protected AuthenticationInfo doGetAuthenticationInfo(
    15. AuthenticationToken authenticationToken) throws
    16. AuthenticationException {
    17. //1 获取身份信息
    18. String principal = authenticationToken.getPrincipal().toString();
    19. //2 获取凭证信息
    20. String password = new String((char[])
    21. authenticationToken.getCredentials());
    22. System.out.println("认证用户信息:"+principal+"---"+password);
    23. //3 获取数据库中存储的用户信息
    24. if(principal.equals("zhangsan")){
    25. //3.1 数据库存储的加盐迭代 3 次密码
    26. String pwdInfo = "7174f64b13022acd3c56e2781e098a5f";
    27. //3.2 创建封装了校验逻辑的对象,将要比较的数据给该对象
    28. AuthenticationInfo info = new SimpleAuthenticationInfo(
    29. authenticationToken.getPrincipal(),
    30. pwdInfo,
    31. ByteSource.Util.bytes("salt"),
    32. authenticationToken.getPrincipal().toString());
    33. return info;
    34. }
    35. return null;
    36. }
    37. }

    (2)在shiro.ini中添加配置信息

    配置信息中有些为自定义登录认证配置类的路径信息

    1. [main]
    2. md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
    3. md5CredentialsMatcher.hashIterations=3
    4. myrealm=com.syh.shiro.text.MyRealm
    5. myrealm.credentialsMatcher=$md5CredentialsMatcher
    6. securityManager.realms=$myrealm
    7. [users]
    8. zhangsan=7174f64b13022acd3c56e2781e098a5f,role1,role2
    9. lisi=l4
    10. [roles]
    11. role1=user:insert,user:select

    (3)启动登录认证示例

    三、Spring Boot 整合 shiro

    1、创建springboot项目

    (1)添加依赖

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.bootgroupId>
    4. <artifactId>spring-boot-starter-webartifactId>
    5. dependency>
    6. <dependency>
    7. <groupId>com.baomidougroupId>
    8. <artifactId>mybatis-plus-boot-starterartifactId>
    9. <version>3.5.2version>
    10. dependency>
    11. <dependency>
    12. <groupId>mysqlgroupId>
    13. <artifactId>mysql-connector-javaartifactId>
    14. <scope>runtimescope>
    15. dependency>
    16. <dependency>
    17. <groupId>org.projectlombokgroupId>
    18. <artifactId>lombokartifactId>
    19. <optional>trueoptional>
    20. dependency>
    21. <dependency>
    22. <groupId>org.springframework.bootgroupId>
    23. <artifactId>spring-boot-starter-testartifactId>
    24. <scope>testscope>
    25. dependency>
    26. <dependency>
    27. <groupId>org.apache.shirogroupId>
    28. <artifactId>shiro-springartifactId>
    29. <version>1.4.1version>
    30. dependency>
    31. dependencies>

    (2)目录结构

     

    2、登录认证实现

    访问数据库获取用户信息,实现登录认证.

     (1)数据库

    1. SET NAMES utf8mb4;
    2. SET FOREIGN_KEY_CHECKS = 0;
    3. -- ----------------------------
    4. -- Table structure for user
    5. -- ----------------------------
    6. DROP TABLE IF EXISTS `user`;
    7. CREATE TABLE `user` (
    8. `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
    9. `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
    10. `pwd` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
    11. `r_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色编号',
    12. PRIMARY KEY (`id`) USING BTREE
    13. ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
    14. -- ----------------------------
    15. -- Records of user
    16. -- ----------------------------
    17. INSERT INTO `user` VALUES ('11', 'zhangsan', '7174f64b13022acd3c56e2781e098a5f', '1111');
    18. SET FOREIGN_KEY_CHECKS = 1;

    (2)实体类

    1. @Data
    2. @NoArgsConstructor
    3. @AllArgsConstructor
    4. @TableName("user")
    5. public class User {
    6. @TableId("id")
    7. private Integer id;
    8. @TableField("name")
    9. private String name;
    10. @TableField("pwd")
    11. private String pwd;
    12. @TableField("rid")
    13. private Integer rid;
    14. }

    (3)service业务

    1. @Service
    2. public class UserServiceImpl implements UserService {
    3. @Autowired
    4. private UserMapper userMapper;
    5. /* 根据用户名称获取用户信息 */
    6. @Override
    7. public User getUserInfoByName(String name) {
    8. LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
    9. wrapper.eq(User::getName, name);
    10. return userMapper.selectOne(wrapper);
    11. }
    12. }

    (4)自定义realm

    1. /*
    2. * 自定义认证授权方法
    3. * */
    4. @Component
    5. public class MyRealm extends AuthorizingRealm {
    6. @Autowired
    7. private UserService userService;
    8. //自定义授权方法
    9. @Override
    10. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    11. return null;
    12. }
    13. //自定义登录认证方法
    14. @Override
    15. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    16. //1 获取用户身份信息
    17. String name = token.getPrincipal().toString();
    18. //2 调用业务层获取用户信息(数据库中)
    19. User user = userService.getUserInfoByName(name);
    20. //3 判断并将数据完成封装
    21. if(user!=null){
    22. AuthenticationInfo info = new SimpleAuthenticationInfo(
    23. token.getPrincipal(),
    24. user.getPwd(),
    25. ByteSource.Util.bytes("salt"),
    26. token.getPrincipal().toString()
    27. );
    28. return info;
    29. }
    30. return null;
    31. }
    32. }

    (5)编写配置类

    1. @Configuration
    2. public class ShiroConfig {
    3. @Autowired
    4. private MyRealm myRealm;
    5. //配置 SecurityManager
    6. @Bean
    7. public DefaultWebSecurityManager defaultWebSecurityManager(){
    8. //1 创建 defaultWebSecurityManager 对象
    9. DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
    10. //2 创建加密对象,并设置相关属性
    11. HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
    12. //2.1 采用 md5 加密
    13. matcher.setHashAlgorithmName("md5");
    14. //2.2 迭代加密次数
    15. matcher.setHashIterations(3);
    16. //3 将加密对象存储到 myRealm 中
    17. myRealm.setCredentialsMatcher(matcher);
    18. //4 将 myRealm 存入 defaultWebSecurityManager 对象
    19. defaultWebSecurityManager.setRealm(myRealm);
    20. //5 返回
    21. return defaultWebSecurityManager;
    22. }
    23. // 过滤器
    24. @Bean
    25. public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
    26. ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
    27. shiroFilter.setSecurityManager(securityManager);
    28. // 拦截配置
    29. Map filter = new LinkedHashMap<>();
    30. // 登录接口放行
    31. filter.put("/api/UserController/userLogin", "anon");
    32. // 需要拦截的资源
    33. filter.put("/**", "authc");
    34. shiroFilter.setFilterChainDefinitionMap(filter);
    35. return shiroFilter;
    36. }
    37. /*
    38. * 以后会用到的几个:
    39. * anon 无需认证即可访问
    40. * logout 退出清除HTTPSession数据
    41. * authc 需要认证才可访问
    42. * user user是介于,anon和authc直之间的。
    43. * 换句话来说:而“/authenticated= user”
    44. * 表示访问该地址的用户是身份验证通过或RememberMe 登录的都可以。
    45. * */
    46. }

    (6)实现登录接口

    1. @RestController
    2. @RequestMapping("/api/UserController")
    3. @Slf4j
    4. public class UserController {
    5. // http://localhost:8080/api/UserController/userLogin?name=zhangsan&pwd=z3
    6. @GetMapping("/userLogin")
    7. public String userLogin(String name, String pwd){
    8. //1 获取 Subject 对象
    9. Subject subject = SecurityUtils.getSubject();
    10. //2 封装请求数据到 token 对象中
    11. AuthenticationToken token = new UsernamePasswordToken(name, pwd);
    12. //3 调用 login 方法进行登录认证
    13. try {
    14. subject.login(token);
    15. return "登录成功";
    16. } catch (AuthenticationException e) {
    17. log.error("登录失败");
    18. return "登录失败";
    19. }
    20. }
    21. }

    3、多个 realm 的认证策略设置

    多个realm实现原理

    Realm验证,有时候会存在多Realm,不同的角色会有不同的验证逻辑,这个时候会需要多Realm。Shiro 的 ModularRealmAuthenticator 会使用内部的 AuthenticationStrategy 组件判断认 证是成功还是失败。

    AuthenticationStrategy 是一个无状态的组件,它在身份验证尝试中被询问 4 次(这 4 次交互所需的任何必要的状态将被作为方法参数):

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

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

    Shiro 中定义了 3 种认证odularRealmAuthenticat策略的实现,Mor 内置的认证策略默认实现是 AtLeastOneSuccessfulStrategy 方式。可以通过配置修改策略。

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

    多个realm代码实现

    标题2已经配置一个realm名字为MyRealm,下面继续创建Customer2Realm

    1. @Component
    2. public class Customer2Realm extends AuthorizingRealm {
    3. @Autowired
    4. private UserService userService;
    5. //授权
    6. @Override
    7. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    8. return null;
    9. }
    10. //认证
    11. @Override
    12. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    13. // userName
    14. String principal = (String) authenticationToken.getPrincipal();
    15. // password
    16. // String credentials = (String) authenticationToken.getCredentials();
    17. // 根据用户名获取用户信息
    18. User user = userService.getUserInfoByName("zhangsan1");
    19. if(user != null){
    20. System.out.println("app认证成功");
    21. return new SimpleAuthenticationInfo(user, user.getPwd(), ByteSource.Util.bytes("salt"), principal);
    22. }else{
    23. return null;
    24. }
    25. }
    26. }

     配置 SecurityManager,添加Customer2Realm(新添加3、5、5.1步)

    1. // 认证授权方法一
    2. @Autowired
    3. private MyRealm myRealm;
    4. // 认证授权方法二
    5. @Autowired
    6. private Customer2Realm customer2Realm;
    7. @Bean
    8. public DefaultWebSecurityManager defaultWebSecurityManager() {
    9. //1 创建 defaultWebSecurityManager 对象
    10. DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
    11. //2 创建认证对象,并设置认证策略
    12. ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
    13. //3 只要有一个(或更多)的 Realm 验证成功,那么认证将视为成功 默认值
    14. modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
    15. defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);
    16. //3 创建加密对象,并设置相关属性
    17. HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
    18. //3.1 采用 md5 加密
    19. matcher.setHashAlgorithmName("md5");
    20. //3.2 迭代加密次数
    21. matcher.setHashIterations(3);
    22. //4 将加密对象存储到 myRealm 中
    23. myRealm.setCredentialsMatcher(matcher);
    24. //5 封装 myRealm 集合
    25. List list = new ArrayList<>();
    26. list.add(myRealm);
    27. //5.1 封装customer2Realm
    28. list.add(customer2Realm);
    29. //6 将 myRealm 存入 defaultWebSecurityManager 对象
    30. defaultWebSecurityManager.setRealms(list);
    31. //6.5 设置 rememberMe(Shiro 提供了记住我(RememberMe)的功能)
    32. defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
    33. //7 返回
    34. return defaultWebSecurityManager;
    35. }

    4、remember me(记住我功能)

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

    基本流程

    (1) 首先在登录页面选中 RememberMe 然后登录成功;如果是浏览器登录,一般会 把 RememberMe 的 Cookie 写到客户端并保存下来;

    (2) 关闭浏览器再重新打开;会发现浏览器还是记住你的;

    (3) 访问一般的网页服务器端,仍然知道你是谁,且能正常访问;

    (4) 但是,如果我们访问电商平台时,如果要查看我的订单或进行支付时,此时还 是需要再进行身份认证的,以确保当前用户还是你。

    代码实现

    (1)修改配置类 ShiroConfig,在defaultWebSecurityManager方法放的6步后面添加代码,并且添加两个方法;在shiroFilterFactoryBean方法中添加过滤条件

    1. @Configuration
    2. public class ShiroConfig {
    3. @Autowired
    4. private MyRealm myRealm;
    5. //配置 SecurityManager
    6. @Bean
    7. public DefaultWebSecurityManager defaultWebSecurityManager() {
    8. //1 创建 defaultWebSecurityManager 对象
    9. DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
    10. //2 创建认证对象,并设置认证策略
    11. ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
    12. modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
    13. defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);
    14. //3 创建加密对象,并设置相关属性
    15. HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
    16. //3.1 采用 md5 加密
    17. matcher.setHashAlgorithmName("md5");
    18. //3.2 迭代加密次数
    19. matcher.setHashIterations(3);
    20. //4 将加密对象存储到 myRealm 中
    21. myRealm.setCredentialsMatcher(matcher);
    22. //5 封装 myRealm 集合
    23. List list = new ArrayList<>();
    24. list.add(myRealm);
    25. //6 将 myRealm 存入 defaultWebSecurityManager 对象
    26. defaultWebSecurityManager.setRealms(list);
    27. //6.5 设置 rememberMe(Shiro 提供了记住我(RememberMe)的功能)
    28. defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
    29. //7 返回
    30. return defaultWebSecurityManager;
    31. }
    32. //cookie 属性设置
    33. public SimpleCookie rememberMeCookie(){
    34. SimpleCookie cookie = new SimpleCookie("rememberMe");
    35. //设置跨域
    36. //cookie.setDomain(domain);
    37. cookie.setPath("/");
    38. cookie.setHttpOnly(true);
    39. cookie.setMaxAge(30*24*60*60);
    40. return cookie;
    41. }
    42. //创建 Shiro 的 cookie 管理对象
    43. public CookieRememberMeManager rememberMeManager(){
    44. CookieRememberMeManager cookieRememberMeManager = new
    45. CookieRememberMeManager();
    46. cookieRememberMeManager.setCookie(rememberMeCookie());
    47. cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());
    48. return cookieRememberMeManager;
    49. }
    50. //配置 Shiro 内置过滤器拦截范围
    51. @Bean
    52. public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
    53. ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
    54. shiroFilter.setSecurityManager(securityManager);
    55. // 拦截配置
    56. Map filter = new LinkedHashMap<>();
    57. // 登录接口放行
    58. //设置需要进行登录认证的拦截范围
    59. filter.put("/**", "authc");
    60. //添加存在用户的过滤器(rememberMe)
    61. filter.put("/**", "user");
    62. shiroFilter.setFilterChainDefinitionMap(filter);
    63. return shiroFilter;
    64. }
    65. }

    (2)修改 controller,新增一个访问参数

    1. package com.syh.shiro.controller;
    2. import com.syh.shiro.Utils.JWT.JwtUtil;
    3. import com.syh.shiro.Utils.relevant.RespStaticEnum;
    4. import com.syh.shiro.Utils.relevant.ResultUtils;
    5. import lombok.extern.slf4j.Slf4j;
    6. import org.apache.shiro.SecurityUtils;
    7. import org.apache.shiro.authc.AuthenticationException;
    8. import org.apache.shiro.authc.AuthenticationToken;
    9. import org.apache.shiro.authc.UsernamePasswordToken;
    10. import org.apache.shiro.subject.Subject;
    11. import org.springframework.web.bind.annotation.*;
    12. import javax.servlet.http.HttpServletRequest;
    13. import javax.servlet.http.HttpSession;
    14. @RestController
    15. @RequestMapping("/api/UserController")
    16. @Slf4j
    17. public class UserController {
    18. // http://localhost:8080/api/UserController/userLogin/true?name=zhangsan&pwd=z3
    19. @GetMapping("/userLogin/{rememberMe}")
    20. public ResultUtils userLogin(String name, String pwd, @PathVariable boolean rememberMe, HttpServletRequest request){
    21. //1 获取 Subject 对象
    22. Subject subject = SecurityUtils.getSubject();
    23. //2 封装请求数据到 token 对象中
    24. AuthenticationToken token = new UsernamePasswordToken(name, pwd, rememberMe);
    25. //3 调用 login 方法进行登录认证
    26. try {
    27. subject.login(token);
    28. String jwtToken = JwtUtil.getJwtToken(name, pwd);
    29. return ResultUtils.success(RespStaticEnum.LOGIN_SUCCESS, jwtToken);
    30. } catch (AuthenticationException e) {
    31. log.error("登录失败");
    32. return ResultUtils.fail(RespStaticEnum.LOGIN_ERROR);
    33. }
    34. }
    35. // http://localhost:8080/api/UserController/get
    36. /*
    37. * 测试接口
    38. * 清理好浏览器cookie,直接访问此方法,会被拦截;
    39. * 如果先访问登录接口,然后再访问此接口,就可以访问;
    40. * 此刻关闭浏览器,再打开,然后访问此接口,发现依旧可以访问,这表明浏览器记住了登陆者
    41. * */
    42. @GetMapping("/get")
    43. public String getStr(HttpSession session, HttpServletRequest request) {
    44. session.setAttribute("user", "rememberMe");
    45. return "ss";
    46. }
    47. }

    5、登出功能

    过滤器中添加如下配置

    1. // 配置登出,请求该地址,shiro清除HTTPSession数据,实现退出登录。
    2. filter.put("/api/UserController/logout", "logout");

    6、授权验证-->角色认证

    用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进行判断。这个工具就是Realm的doGetAuthorizationInfo方法进行判断。触发权限判断的有两种方式,这里使用在接口服务中通过注解@Requires****进行判断

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

    • @RequiresAuthentication 验证用户是否登录,等同于方法subject.isAuthenticated()
    • @RequiresUser 验证用户是否被记忆: 登录认证成功subject.isAuthenticated()为true 登录后被记忆subject.isRemembered()为true
    • @RequiresGuest 验证是否是一个guest的请求,是否是游客的请求 此时subject.getPrincipal()为null
    • @RequiresRoles 验证subject是否有相应角色,有角色访问方法,没有则会抛出异常 AuthorizationException。 例如:@RequiresRoles(“aRoleName”) void someMethod(); 只有subject有aRoleName角色才能访问方法someMethod()
    • @RequiresPermissions 验证subject是否有相应权限,有权限访问方法,没有则会抛出异常 AuthorizationException。 例如:@RequiresPermissions (“file:read”,”wite:aFile.txt”) void someMethod(); subject必须同时含有file:read和wite:aFile.txt权限才能访问方法someMethod()

    (1)数据库

    1. SET NAMES utf8mb4;
    2. SET FOREIGN_KEY_CHECKS = 0;
    3. -- ----------------------------
    4. -- Table structure for role
    5. -- ----------------------------
    6. DROP TABLE IF EXISTS `role`;
    7. CREATE TABLE `role` (
    8. `r_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
    9. `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名',
    10. `desc` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
    11. PRIMARY KEY (`r_id`) USING BTREE
    12. ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;
    13. -- ----------------------------
    14. -- Records of role
    15. -- ----------------------------
    16. INSERT INTO `role` VALUES ('1111', '管理员', '全部权限');
    17. SET FOREIGN_KEY_CHECKS = 1;

    (2)实体类

    1. @Getter
    2. @Setter
    3. @TableName("role")
    4. public class Role implements Serializable {
    5. private static final long serialVersionUID = 1L;
    6. /**
    7. * 角色id
    8. */
    9. @TableId("r_id")
    10. private String rId;
    11. /**
    12. * 角色名
    13. */
    14. @TableField("name")
    15. private String name;
    16. /**
    17. * 描述
    18. */
    19. @TableField("desc")
    20. private String desc;
    21. }

    (3)授权校验代码实现

    注解生效需要一些配置,在ShiroConfig配置文件中添加如下代码

    1. /*
    2. * 如下两个配置可以使shiro的权限校验注解生效
    3. * */
    4. @Bean
    5. public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
    6. DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    7. defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
    8. return defaultAdvisorAutoProxyCreator;
    9. }
    10. //AuthorizationAttributeSourceAdvisor 的作用是匹配所有类 匹配所有加认证注解的方法
    11. //具体看这篇博客:https://blog.csdn.net/wangjun5159/article/details/51889628
    12. @Bean
    13. public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Autowired DefaultWebSecurityManager securityManager) {
    14. AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    15. authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    16. return authorizationAttributeSourceAdvisor;
    17. }

    (4)MyRealm自定义授权方法

    1. @Component
    2. public class MyRealm extends AuthorizingRealm {
    3. @Autowired
    4. private UserService userService;
    5. @Autowired
    6. private IRoleService roleService;
    7. //自定义授权方法
    8. @Override
    9. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    10. //1 创建对象,存储当前登录的用户的权限和角色
    11. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    12. //2 获取用户身份信息
    13. String principal = principalCollection.getPrimaryPrincipal().toString();
    14. //3 获取用户对应的角色信息
    15. List roleList = roleService.getRoleName(principal);
    16. //4 存储角色
    17. info.addRoles(roleList);
    18. //5 返回
    19. return info;
    20. }
    21. //自定义登录认证方法
    22. @Override
    23. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    24. //1 获取用户身份信息
    25. String name = token.getPrincipal().toString();
    26. //2 调用业务层获取用户信息(数据库中)
    27. User user = userService.getUserInfoByName(name);
    28. //3 判断并将数据完成封装
    29. if(user!=null){
    30. AuthenticationInfo info = new SimpleAuthenticationInfo(
    31. token.getPrincipal(),
    32. user.getPwd(),
    33. ByteSource.Util.bytes("salt"),
    34. token.getPrincipal().toString()
    35. );
    36. return info;
    37. }
    38. return null;
    39. }
    40. }

    (5)controller角色授权验证接口

    1. /*
    2. * 角色授权验证接口
    3. * */
    4. // 1111:管理员角色
    5. @RequiresRoles("1111")
    6. @GetMapping("/userLoginRoles")
    7. public String userLoginRoles() {
    8. System.out.println("登录认证验证角色");
    9. return "验证角色成功";
    10. }

    (6)角色验证失败的返回信息

     数据库中只有1111角色,没有1111222角色

    6、授权验证-->角色的权限认证 

    (1)数据库

    1. SET NAMES utf8mb4;
    2. SET FOREIGN_KEY_CHECKS = 0;
    3. -- ----------------------------
    4. -- Table structure for permissions
    5. -- ----------------------------
    6. DROP TABLE IF EXISTS `permissions`;
    7. CREATE TABLE `permissions` (
    8. `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
    9. `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名',
    10. `info` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限信息',
    11. `desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
    12. PRIMARY KEY (`id`) USING BTREE
    13. ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;
    14. -- ----------------------------
    15. -- Records of permissions
    16. -- ----------------------------
    17. INSERT INTO `permissions` VALUES ('111111111', 'user:delete', '删除用户', '删除用户信息');
    18. INSERT INTO `permissions` VALUES ('1111111222', 'user:select', '查询用户信息', '查询用户信息');
    19. SET FOREIGN_KEY_CHECKS = 1;
    1. SET NAMES utf8mb4;
    2. SET FOREIGN_KEY_CHECKS = 0;
    3. -- ----------------------------
    4. -- Table structure for role_ps
    5. -- ----------------------------
    6. DROP TABLE IF EXISTS `role_ps`;
    7. CREATE TABLE `role_ps` (
    8. `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
    9. `rid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色 id',
    10. `pid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限 id',
    11. PRIMARY KEY (`id`) USING BTREE
    12. ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色权限映射表' ROW_FORMAT = Dynamic;
    13. -- ----------------------------
    14. -- Records of role_ps
    15. -- ----------------------------
    16. INSERT INTO `role_ps` VALUES ('sahksah', '1111', '111111111');
    17. SET FOREIGN_KEY_CHECKS = 1;

    (2)实体类

    1. @Getter
    2. @Setter
    3. @TableName("permissions")
    4. public class Permissions implements Serializable {
    5. private static final long serialVersionUID = 1L;
    6. /**
    7. * 编号
    8. */
    9. @TableId("id")
    10. private String id;
    11. /**
    12. * 权限名
    13. */
    14. @TableField("name")
    15. private String name;
    16. /**
    17. * 权限信息
    18. */
    19. @TableField("info")
    20. private String info;
    21. /**
    22. * 描述
    23. */
    24. @TableField("desc")
    25. private String desc;
    26. }
    1. @Getter
    2. @Setter
    3. @TableName("role_ps")
    4. public class RolePs implements Serializable {
    5. private static final long serialVersionUID = 1L;
    6. /**
    7. * 编号
    8. */
    9. @TableId("id")
    10. private String id;
    11. /**
    12. * 角色 id
    13. */
    14. @TableField("rid")
    15. private String rid;
    16. /**
    17. * 权限 id
    18. */
    19. @TableField("pid")
    20. private String pid;
    21. }

    (4)MyRealm授权方法中添加权限信息

    1. /*
    2. * 自定义认证授权方法
    3. * */
    4. @Component
    5. public class MyRealm extends AuthorizingRealm {
    6. // 用户
    7. @Autowired
    8. private UserService userService;
    9. // 角色
    10. @Autowired
    11. private IRoleService roleService;
    12. // 角色对应的权限
    13. @Autowired
    14. private IRolePsService rolePsService;
    15. //自定义授权方法
    16. @Override
    17. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    18. //1 创建对象,存储当前登录的用户的权限和角色
    19. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    20. //2 获取用户身份信息
    21. String principal = principalCollection.getPrimaryPrincipal().toString();
    22. //3 获取用户对应的角色信息
    23. List roleList = roleService.getRoleName(principal);
    24. //4 存储角色
    25. info.addRoles(roleList);
    26. //5 获取用户权限信息
    27. List jurisdictionList = rolePsService.getRolejurisdiction(roleList);
    28. //6 储存权限信息
    29. info.addStringPermissions(jurisdictionList);
    30. //7 返回
    31. return info;
    32. }
    33. //自定义登录认证方法
    34. @Override
    35. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    36. //1 获取用户身份信息
    37. String name = token.getPrincipal().toString();
    38. //2 调用业务层获取用户信息(数据库中)
    39. User user = userService.getUserInfoByName(name);
    40. //3 判断并将数据完成封装
    41. if(user!=null){
    42. AuthenticationInfo info = new SimpleAuthenticationInfo(
    43. token.getPrincipal(),
    44. user.getPwd(),
    45. ByteSource.Util.bytes("salt"),
    46. token.getPrincipal().toString()
    47. );
    48. return info;
    49. }
    50. return null;
    51. }
    52. }

    (5)controller角色的权限认证接口

    1. /*
    2. * 角色的权限验证接口
    3. * */
    4. @RequiresPermissions("user:delete")
    5. @GetMapping("/userJurisdiction")
    6. public String userJurisdiction() {
    7. System.out.println("角色权限验证");
    8. return "角色权限验证成功";
    9. }

    7、授权验证-异常处理

    集成到springboot全局异常处理中

    1. /*
    2. * 全局异常处理类
    3. * */
    4. @ControllerAdvice
    5. @Slf4j
    6. public class GlobalExceptionHandler {
    7. /*
    8. * 全局异常捕获
    9. * */
    10. @ResponseBody
    11. @ExceptionHandler(value = Exception.class)
    12. public ResultUtils javaExceptionHandler(Exception ex){
    13. return ResultUtils.fail(RespStaticEnum.FAIL, ex.getMessage());
    14. }
    15. /*
    16. * shiro框架权限校验失败异常捕获
    17. * */
    18. @ResponseBody
    19. @ExceptionHandler(UnauthorizedException.class)
    20. public ResultUtils unauthorizedException(Exception ex){
    21. log.error("无权限:" + ex.getLocalizedMessage());
    22. return ResultUtils.fail(RespStaticEnum.FAIL);
    23. }
    24. @ResponseBody
    25. @ExceptionHandler(AuthorizationException.class)
    26. public ResultUtils authorizationException(Exception ex){
    27. log.error("权限认证失败:" + ex.getLocalizedMessage());
    28. return ResultUtils.fail(RespStaticEnum.FAIL);
    29. }
    30. }

    8、Shiro整合EhCache,实现缓存

    springboot整合EhCache学习请移步(1条消息) EhCache缓存框架_SUN Y H的博客-CSDN博客

    添加依赖

    Shiro官方提供了shiro-ehcache,实现了整合EhCache作为Shiro的缓存工具。可以缓 存认证执行的Realm方法,减少对数据库的访问,提高认证效率

    1. <dependency>
    2. <groupId>org.apache.shirogroupId>
    3. <artifactId>shiro-ehcacheartifactId>
    4. <version>1.5.2version>
    5. dependency>
    6. <dependency>
    7. <groupId>commons-iogroupId>
    8. <artifactId>commons-ioartifactId>
    9. <version>2.7version>
    10. dependency>

    添加shiro配置文件

    在 resources 下添加配置文件 ehcache/ehcache-shiro.xml

    1. "1.0" encoding="UTF-8"?>
    2. <ehcache name="ehcache" updateCheck="false">
    3. <diskStore path="java.io.tmpdir"/>
    4. <defaultCache
    5. maxEntriesLocalHeap="1000"
    6. eternal="false"
    7. timeToIdleSeconds="3600"
    8. timeToLiveSeconds="3600"
    9. overflowToDisk="false">
    10. defaultCache>
    11. <cache name="loginRolePsCache"
    12. maxEntriesLocalHeap="2000"
    13. eternal="false"
    14. timeToIdleSeconds="600"
    15. timeToLiveSeconds="0"
    16. overflowToDisk="false"
    17. statistics="true"/>
    18. ehcache>

    修改配置类 ShiroConfig

    6.6步为新添加,其他为之前代码

    1. @Bean
    2. public DefaultWebSecurityManager defaultWebSecurityManager() {
    3. //1 创建 defaultWebSecurityManager 对象
    4. DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
    5. //2 创建认证对象,并设置认证策略
    6. ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
    7. //3 只要有一个(或更多)的 Realm 验证成功,那么认证将视为成功 默认值
    8. modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
    9. defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);
    10. //3 创建加密对象,并设置相关属性
    11. HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
    12. //3.1 采用 md5 加密
    13. matcher.setHashAlgorithmName("md5");
    14. //3.2 迭代加密次数
    15. matcher.setHashIterations(3);
    16. //4 将加密对象存储到 myRealm 中
    17. myRealm.setCredentialsMatcher(matcher);
    18. //5 封装 myRealm 集合
    19. List list = new ArrayList<>();
    20. list.add(myRealm);
    21. //5.1 封装customer2Realm
    22. list.add(customer2Realm);
    23. //6 将 myRealm 存入 defaultWebSecurityManager 对象
    24. defaultWebSecurityManager.setRealms(list);
    25. //6.5 设置 rememberMe(Shiro 提供了记住我(RememberMe)的功能)
    26. defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
    27. //6.6 设置缓存管理器
    28. defaultWebSecurityManager.setCacheManager(getEhCacheManager());
    29. //7 返回
    30. return defaultWebSecurityManager;
    31. }
    32. //缓存管理器
    33. public EhCacheManager getEhCacheManager(){
    34. EhCacheManager ehCacheManager = new EhCacheManager();
    35. InputStream is = null;
    36. try {
    37. is = ResourceUtils.getInputStreamForPath(
    38. "classpath:ehcache/ehcache-shiro.xml");
    39. } catch (IOException e) {
    40. e.printStackTrace();
    41. }
    42. CacheManager cacheManager = new CacheManager(is);
    43. ehCacheManager.setCacheManager(cacheManager);
    44. return ehCacheManager;
    45. }

    代码测试

    此处不贴运行结果图了,打开sql日志打印。登录之后,访问权限验证接口,第一次会查询数据库打印sql语句,第二次访问会走缓存,看不到sql语句打印。

    缓存更新问题

    当某个用户的权限被更新,应该同步更新EhCache中缓存

    9、会话管理

    SessionManager

    会话管理器,负责创建和管理用户的会话(Session)生命周期,它能够在任何环境中 在本地管理用户会话,即使没有Web/Servlet/EJB容器,也一样可以保存会话。默认情况 下,Shiro会检测当前环境中现有的会话机制(比如Servlet容器)进行适配,如果没有(比如独立应用程序或者非Web环境),它将会使用内置的企业会话管理器来提供相应的会 话管理服务,其中还涉及一个名为SessionDAO的对象。SessionDAO负责Session的持久化操 作(CRUD),允许Session数据写入到后端持久化数据库。

    会话管理实现

    SessionManager由SecurityManager管理。Shiro提供了三种实现

    • DefaultSessionManager:用于JavaSE环境
    • ServletContainerSessionManager:用于web环境,直接使用Servlet容器的会话
    • DefaultWebSessionManager:用于web环境,自己维护会话(不使用Servlet容器的
      会话管理)

    获得session方式

    1. 实现 :Session session = SecurityUtils.getSubject().getSession();session.setAttribute(“key”,”value”)
    2. 说明 Controller 中的 request,在 shiro 过滤器中的 doFilerInternal 方法,被包装成 ShiroHttpServletRequest;  SecurityManager 和 SessionManager 会话管理器决定 session 来源于 ServletRequest 还是由 Shiro 管理的会话;  无论是通过 request.getSession 或 subject.getSession 获取到 session,操作 session,两者都是等价的。

    四、shiro拦截器

    1、shiro默认拦截器

    Shiro内置了很多默认的拦截器,比如身份验证、授权等相关的。默认拦截器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举拦截器:

    注意:anon, authc, authcBasic, user 是第一组认证过滤器,perms, port, rest, roles, ssl 是第二组授权过滤器,要通过授权过滤器,就先要完成登陆认证操作(即先要完成认证才能前去寻找授权) 才能走第二组授权器(例如访问需要 roles 权限的 url,如果还没有登陆的话,会直接跳转到 shiroFilterFactoryBean.setLoginUrl(); 设置的 url
     

    shiro默认拦截器
    默认拦截器名拦截器类说明(括号里的表示默认值)
    身份验证相关的
    authc

    org.apache.shiro.web.filter.authc

    .FormAuthenticationFilter

    基于表单的拦截器;如“/**=authc”,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam:表单提交的用户名参数名( username);  passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe);  loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure);
    authcBasic

    org.apache.shiro.web.filter.authc

    .BasicHttpAuthenticationFilter

    Basic HTTP身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application);
    logout

    org.apache.shiro.web.filter.authc

    .LogoutFilter

    退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/);示例“/logout=logout”
    user

    org.apache.shiro.web.filter.authc

    .UserFilter

    用户拦截器,用户已经身份验证/记住我登录的都可;示例“/**=user”
    anon

    org.apache.shiro.web.filter.authc

    .AnonymousFilter

    匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例“/static/**=anon”
    授权相关的
    roles

    org.apache.shiro.web.filter.authz

    .RolesAuthorizationFilter

    角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例“/admin/**=roles[admin]”
    perms

    org.apache.shiro.web.filter.authz

    .PermissionsAuthorizationFilter

    权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例“/user/**=perms["user:create"]”
    port

    org.apache.shiro.web.filter.authz

    .PortFilter

    端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样
    rest

    org.apache.shiro.web.filter.authz

    .HttpMethodPermissionFilter

    rest风格拦截器,自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串;示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll);
    ssl

    org.apache.shiro.web.filter.authz

    .SslFilter

    SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样;
    其他
    noSessionCreation

    org.apache.shiro.web.filter.session

    .NoSessionCreationFilter

    不创建会话拦截器,调用 subject.getSession(false)不会有什么问题,但是如果 subject.getSession(true)将抛出 DisabledSessionException异常;

    2、自定义jwt拦截器

    学习该模块时,看的其他博主的帖子。试验了一下无一成功。应该是理解有误,然后放弃网上的方法,按照自己的理解写了这篇自定义jwt拦截器。

    自定义iwt拦截器

    (1)用到的jwt工具类

    (3条消息) jjwt工具类_SUN Y H的博客-CSDN博客

    (2)创建自定义拦截器,实现BasicHttpAuthenticationFilter

    1. import com.syh.shiro.Utils.JWT.JwtUtil;
    2. import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
    3. import org.springframework.http.HttpStatus;
    4. import org.springframework.web.bind.annotation.RequestMethod;
    5. import javax.servlet.ServletRequest;
    6. import javax.servlet.ServletResponse;
    7. import javax.servlet.http.HttpServletRequest;
    8. import javax.servlet.http.HttpServletResponse;
    9. /**
    10. * @author ojj
    11. * @title: JwtFilter
    12. * @projectName test
    13. * @description: 拦截器
    14. * @date 2022/1/6 14:57
    15. */
    16. public class JwtFilter extends BasicHttpAuthenticationFilter {
    17. /**
    18. * 拦截器的前置 最先执行的 这里只做了一个跨域设置
    19. */
    20. @Override
    21. protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
    22. HttpServletRequest req = (HttpServletRequest) request;
    23. HttpServletResponse res = (HttpServletResponse) response;
    24. res.setHeader("Access-control-Allow-Origin", req.getHeader("Origin"));
    25. res.setHeader("Access-control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
    26. res.setHeader("Access-control-Allow-Headers", req.getHeader("Access-Control-Request-Headers"));
    27. // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
    28. if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
    29. res.setStatus(HttpStatus.OK.value());
    30. return false;
    31. }
    32. return super.preHandle(request, response);
    33. }
    34. /**
    35. * preHandle 执行完之后会执行这个方法
    36. * 再这个方法中 我们根据条件判断去去执行isLoginAttempt和executeLogin方法
    37. */
    38. @Override
    39. protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    40. /**
    41. * 先去调用 isLoginAttempt方法 字面意思就是是否尝试登陆 如果为true
    42. * 执行executeLogin方法
    43. */
    44. if (isLoginAttempt(request, response)) {
    45. executeLogin(request, response);
    46. return true;
    47. }
    48. return false;
    49. }
    50. /**
    51. * 这里我们只是简单去做一个判断请求头中的token信息是否为空
    52. * 如果没有我们想要的请求头信息则直接返回false
    53. */
    54. @Override
    55. protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
    56. HttpServletRequest req = (HttpServletRequest) request;
    57. String token = req.getHeader("X-Token");
    58. return token != null;
    59. }
    60. /**
    61. * 执行登陆
    62. * 因为已经判断token不为空了,所以直接执行登陆逻辑
    63. * 将token放入JwtToken类中去
    64. * 然后getSubject方法是调用到了MyRealm的 执行方法 因为上面我是抛错的所有最后做个异常捕获就好了
    65. */
    66. @Override
    67. protected boolean executeLogin(ServletRequest request, ServletResponse response) {
    68. HttpServletRequest req = (HttpServletRequest) request;
    69. String jwtToken = req.getHeader("X-Token");
    70. // 校验token是否有效
    71. if (JwtUtil.checkToken(jwtToken)) {
    72. return true;
    73. } else {
    74. return false;
    75. }
    76. }
    77. }

    (3)ShiroConfig类中引入自定义jwt拦截器,并配置拦截路径

    1. //配置 Shiro 内置过滤器拦截范围
    2. @Bean
    3. public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
    4. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    5. shiroFilterFactoryBean.setSecurityManager(securityManager);
    6. // 拦截配置
    7. Map filterMap = new LinkedHashMap<>();
    8. //引入自定义拦截器,命名为jwt
    9. filterMap.put("jwt", new JwtFilter());
    10. shiroFilterFactoryBean.setFilters(filterMap);
    11. Map filterRuleMap=new LinkedHashMap<>();
    12. // 登录接口放行
    13. filterRuleMap.put("/api/UserController/userLogin/**", "anon");
    14. // 配置登出,请求该地址,shiro去清除session,实现退出登录。
    15. filterRuleMap.put("/api/UserController/logout", "logout");
    16. // 配置自定义jwt认证
    17. filterRuleMap.put("/api/UserController/jwtLan", "jwt");
    18. // 设置需要进行登录认证的拦截范围
    19. filterRuleMap.put("/**", "authc");
    20. // 添加存在用户的过滤器(rememberMe)
    21. filterRuleMap.put("/**", "user");
    22. shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
    23. return shiroFilterFactoryBean;
    24. }

    (4)登录接口将生成的token返回给前端

    1. //配置 Shiro 内置过滤器拦截范围
    2. @Bean
    3. public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
    4. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    5. shiroFilterFactoryBean.setSecurityManager(securityManager);
    6. // 拦截配置
    7. Map filterMap = new LinkedHashMap<>();
    8. //引入自定义拦截器,命名为jwt
    9. filterMap.put("jwt", new JwtFilter());
    10. shiroFilterFactoryBean.setFilters(filterMap);
    11. Map filterRuleMap=new LinkedHashMap<>();
    12. // 登录接口放行
    13. filterRuleMap.put("/api/UserController/userLogin/**", "anon");
    14. // 配置登出,请求该地址,shiro去清除session,实现退出登录。
    15. filterRuleMap.put("/api/UserController/logout", "logout");
    16. // 配置自定义jwt认证
    17. filterRuleMap.put("/api/UserController/jwtLan", "jwt");
    18. // 设置需要进行登录认证的拦截范围
    19. filterRuleMap.put("/**", "authc");
    20. // 添加存在用户的过滤器(rememberMe)
    21. filterRuleMap.put("/**", "user");
    22. shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
    23. return shiroFilterFactoryBean;
    24. }

    (5)测试,该访问路径会经过自定义jwt拦截器

    1. /*
    2. * 自定义jwt认证
    3. * */
    4. @GetMapping("/jwtLan")
    5. public String jwtLan() {
    6. System.out.println("自定义jwt认证");
    7. return "自定义jwt认证";
    8. }

  • 相关阅读:
    Influxdb数据库部署文档
    【英雄哥七月集训】第 26天:并查集
    UI设计原则背后的认知心理学 优漫动游
    Zookeeper
    现在啥软件都有开源,BI 呢?
    【深度学习】卷积神经网络(CNN)
    工作队列模式(任务队列)| RabbitMQ系列(二)
    【最佳实践】gorm 联表查询 joins
    Docker 部署网页版 vscode (code-server)
    ES 8.x 向量检索性能测试 & 把向量检索性能提升100倍!
  • 原文地址:https://blog.csdn.net/asddasddeedd/article/details/127819729