Shiro权限安全框架的使用

Shiro

一、概述

1.4 基本功能

1、基本功能点

image-20220929162947122

2、简介

(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:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用 登 录了

1.5 原理

1、Shiro架构(外部来看)

image-20220929163430547

(1)Subject:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心 就是 Subject。Subject 代表了当前“用户”, 这个用户不一定 是一个具体的人,与当 前应用交互的任何东西都是 Subject,如网络爬虫, 机器人等;与 Subject 的所有交互 都会委托给 SecurityManager; Subject 其实是一个门面,SecurityManager 才是实际的 执行者;

(2)SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与 Shiro 的其他 组件进行交互,它相当于 SpringMVC 中 DispatcherServlet 的角色

(3)Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户 进行比较以确 定用户身份是否合法;也需要从 Realm 得到用户相应的角色/ 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource

2、Shiro架构(内部来看)

image-20220929163510050

(1)Subject:任何可以与应用交互的“用户”

(2)SecurityManager :相当于 SpringMVC 中的 DispatcherServlet;是 Shiro 的心 脏; 所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负 责进 行认证、授权、会话及缓存的管理。

(3)Authenticator:负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认 证 策略(Authentication Strategy),即什么情况下算用户认证通过了;

(4)Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即 控 制着用户能访问应用中的哪些功能;

(5)Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需 要 实现自己的 Realm;

(6)SessionManager:管理 Session 生命周期的组件;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境

(7)CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据 基本上很少改变,放到缓存中后可以提高访问的性能

(8)Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解 密。

二、基本使用

2.1 环境准备

  • shiro不依赖容器,直接创建maven工程即可
  • 添加依赖
    <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.9.0</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>

2.2 INI文件

Shiro获取权限相关的信息可以通过数据库获取,这里为了方便通过ini配置文件获取

就是把账号密码写在这个文件里就可

image-20220929164751478

2.3 登录认证

1、概念

(1)身份验证:一般需要提供如身份ID等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明。

(2)在shiro中,用户需要提供principals(身份)和credentials(证明)给shiro,从 而应用能验证用户身份:

(3)principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一 即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/ 邮箱/手机号。

(4)credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

(5)最常见的principals和credentials组合就是用户名/密码

2、基本流程

(1)收集用户身份/凭证,即如用户名/密码

(2)调用 Subject.login 进行登录,如果失败将得到相应 的 AuthenticationException 异常,根据异常提示用户 错误信息;否则登录成功

(3)创建自定义的 Realm 类,继承 org.apache.shiro.realm.AuthenticatingRealm类, 实现 doGetAuthenticationInfo() 方法

image-20220929201039031

3、登录认证实例
public class ShiroRun {
    public static void main(String[] args) {
        //1、初始化获取SecurityManager
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        //将安全管理器放到工具类当中,之后可以通过工具类获取subject对象
        SecurityUtils.setSecurityManager(securityManager);

        //2、获取Subject对象
        Subject subject = SecurityUtils.getSubject();
        //3、创建token对象,web应用用户名和密码从页面传递
        AuthenticationToken token = new UsernamePasswordToken("zhangsan","z3");
        //4、完成登录
        try {
            subject.login(token);
            System.out.println("登录成功");
        }catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户不存在");
        }
        catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("密码错误");
        }
        catch (AuthenticationException e) {
            //unexpected condition? error?
            e.printStackTrace();
            System.out.println("登录失败");
        }
    }
}
4、身份认证流程

(1)首先调用 Subject.login(token) 进行登录,其会自动委托给 SecurityManager

(2)SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份 验证;

(3)Authenticator 才是真正的身份验证者,Shiro API 中核心的身份 认证入口点,此 处可以自定义插入自己的实现;

(4)Authenticator 可能会委托给相应的 AuthenticationStrategy 进 行多 Realm 身份 验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;

(5) Authenticator 会把相应的 token 传入 Realm,从 Realm 获取 身份验证信息,如 果没有返回/抛出异常表示身份验证失败了。此处 可以配置多个Realm,将按照相应的顺序 及策略进行访问。

2.4 角色、授权

1、授权概念

(1)授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面 操作 等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权 限 (Permission)、角色(Role)。

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

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

(4)权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中 用户 有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访 问用 户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权 限控 制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允 不允 许。

(5)Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权 限, 即实例级别的)

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

2、授权方式

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

image-20220929201919953

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

image-20220929201927851

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

image-20220929201936365

3、授权流程

(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-20220929202028402

4、授权实例

配置角色信息和权限

image-20220929210349092

在登录之后,就可以使用subject进行判断

subject.login(token);
System.out.println("登录成功");

//5判断角色
boolean hasRole1 = subject.hasRole("role1");
System.out.println("是否拥有此角色 role1 = " + hasRole1);

//6 判断权限
boolean isPermitted = subject.isPermitted("user:insert");
System.out.println("是否拥有此权限 user:insert = " + isPermitted);
//也可以使用checkPermission方法,这个方法没有返回值,没有权限的话直接抛出异常AuthenticationException

2.5 Shiro加密

对密码加密,Shiro中内嵌了很多常用的加密算法

1、加密
public class ShiroMD5 {
    public static void main(String[] args) {
        //密码明文
        String password = "z3";
        //使用MD5加密
        Md5Hash md5Hash = new Md5Hash(password);
        System.out.println("md5加密:"+md5Hash);
        //带盐的MD5加密,盐就是在密码后买拼接字符串,然后再进行加密
        Md5Hash md5Hash2 = new Md5Hash(password,"salt");
        System.out.println("md5带盐加密:"+md5Hash2);

        //为了保证安全,避免被破解还可以多次迭代加密,保证数据安全
        //这里加密三次
        Md5Hash md5Hash3 = new Md5Hash(password,"salt",3);
        System.out.println("md5带盐三次加密:"+md5Hash3.toHex());

        //使用父类进行加密,区别就是可以指定加密的算法
        SimpleHash simpleHash = new SimpleHash("MD5",password,"salt",3);
        System.out.println("通过父类md5带盐三次加密:"+simpleHash.toHex());

    }
}
2、在配置文件中添加信息
[main]
md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3

myrealm=com.zylai.shirotest.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm


[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f,role1,role2
lisi=l4

[roles]
role1=user:insert,user:select

2.6 Shiro自定义登录认证

  • 该方法只是获取进行对比的信息,认证逻辑还是按照Shiro底层认证逻辑完成
  • 需要通过配置使自定义的realm生效,目前在ini文件中配置,之后在springboot中配置
public class MyRealm extends AuthenticatingRealm {

    //自定义的登录认证方法,Shiro的login方法底层会调用该类的认证方法进行认证
    //还需要通过配置使自定义的realm生效,目前在ini文件中配置,之后在springboot中配置
    //该方法只是获取进行对比的信息,认证逻辑还是按照Shiro底层认证逻辑完成
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1、获取身份信息
        String principal = token.getPrincipal().toString();
        //2、获取凭证信息
        String password = new String((char[]) token.getCredentials());
        System.out.println("认证用户的信息:"+principal+"---"+password);
        //3、访问数据库获取数据库存储的用户信息,这里暂时不去访问数据库
        if(principal.equals("zhangsan")){
        //    3.1数据库中年存储的肯定不是明文,是加盐迭代三次的密码
            String pwdInfo = "7174f64b13022acd3c56e2781e098a5f";
            //4、封装到校验逻辑对象,把这个对象返回即可
            AuthenticationInfo info = new SimpleAuthenticationInfo(
                    token.getPrincipal(),
                    pwdInfo,
                    ByteSource.Util.bytes("salt"),
                    token.getPrincipal().toString()
            );
            return info;
        }

        return null;
    }

}

三、整合SpringBoot

3.1 框架整合

1、创建模块

image-20220930201048439

2、引入依赖
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
</parent>

<modelVersion>4.0.0</modelVersion>

<artifactId>shiro_springboot</artifactId>

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-web-starter</artifactId>
        <version>1.9.0</version>
    </dependency>

    <!--mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.0.5</version>
    </dependency>

    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

</dependencies>
3、配置文件
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*.xml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSL=false
    username: root
    password: root
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
shiro:
    loginUrl: /myController/login
4、启动类
@SpringBootApplication
@MapperScan("com.zylai.shiro.mapper")
public class ShiroApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShiroApplication.class,args);
    }
}

3.2 登录认证实现

这一步就是业务真正实现

1、后端服务接口

(1)数据库表

CREATE TABLE `user` (
 `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
 `name` VARCHAR(30) DEFAULT NULL COMMENT '用户名',
 `pwd` VARCHAR(50) DEFAULT NULL COMMENT '密码',
 `rid` BIGINT(20) DEFAULT NULL COMMENT '角色编号',
 PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户表';

(2)实体类

@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
    private Integer id;
    private String name;
    private String pwd;
    private Integer rid;
}

(3)mapper

public interface UserMapper extends BaseMapper<User> {

(4)service

public interface UserService {
    //用户登录
    User getUserInfoByName(String name);
}
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User getUserInfoByName(String name) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name",name);
        return userMapper.selectOne(wrapper);
    }
}
2、配置类
@Configuration
public class ShiroConfig {
    //注入自定理的realm
    @Autowired
    private MyRealm myRealm;

    //配置SecurityManager
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(){
        //1、创建defaultWebSecurityManager 对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        //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);

        //5、返回
        return defaultWebSecurityManager;
    }

    //配置 Shiro 内置过滤器拦截范围
    @Bean
    public DefaultShiroFilterChainDefinition
    shiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition definition = new
                DefaultShiroFilterChainDefinition();
        //设置不认证可以访问的资源
        definition.addPathDefinition("/myController/userLogin","anon");
        definition.addPathDefinition("/login","anon");

        //设置需要进行登录认证的拦截范围
        definition.addPathDefinition("/**","authc");
        return definition;
    }

}
3、自定义Realm类

这里就是对应着原生操作中自定义登录认证那一步

@Component
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    //自定义授权方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    //自定义登录方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1、获取用户信息
        String username = token.getPrincipal().toString();
        //2、调用业务层获取用户信息(数据库中)
        User user = userService.getUserInfoByName(username);
        //3、非空值判断,将数据封装返回
        if(user != null){
            /**
             * 1、用户身份信息
             * 2、用户在数据中存储的密码(加密之后的)
             * 3、盐值
             * 4、用户名
             */
            AuthenticationInfo info = new SimpleAuthenticationInfo(
                    token.getPrincipal(),
                    user.getPwd(),
                    ByteSource.Util.bytes("salt"),
                    username
            );
            return info;
        }
        return null;
    }
}
4、实现登录逻辑

这里和原生的不同之处在于少了一步 初始化获取SecurityManager,并把SecurityManager发入工具类中,因为我们已经通过配置类配置了SecurityManager

如此,登录的逻辑就很容易了,就下面这三步

@Controller
@RequestMapping("/myController")
public class MyController {


    @GetMapping("/userLogin")
    @ResponseBody
    public String userLogin(String name,String pwd){
        //1、获取Subject对象
        Subject subject = SecurityUtils.getSubject();
        //2、封装请求数据token
        AuthenticationToken token = new UsernamePasswordToken(name,pwd);
        //3、调用login方法进行登录认证
        try {
            subject.login(token);
            return "登录成功";
        } catch (AuthenticationException e) {
            System.out.println("登录失败");
            return "登录失败";
        }
    }
}
5、测试

发送请求:http://localhost:8080/myController/userLogin?name=张三&pwd=z3

image-20220930203115642

自定义Realm中也真正去数据库中查询了

image-20220930203141678

6、整合前端

(1)引入thymeleaf依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

(2)创建两个页面

login:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Shiro 登录认证</h1>
<br>
<form action="/myController/userLogin">
    <div>用户名:<input type="text" name="name" value=""></div>
    <div>密码:<input type="password" name="pwd" value=""></div>
    <div><input type="submit" value="登录"></div>
</form>
</body>

</html>

登录成功之后的:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Shiro 登录认证后主页面</h1>
<br>
登录用户为:<span th:text="${session.user}"></span>
</body>
</html>

(3)添加controller中的login方法,改造认证的方法

//跳转登录页面
@GetMapping("/login")
public String login(){
    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中
        session.setAttribute("user",token.getPrincipal().toString());
        return "main";
    } catch (AuthenticationException e) {
        //System.out.println(e.getMessage());
        System.out.println("登录失败");
        session.setAttribute("user","登录失败");
        return "main";
    }
}

(4)一定要在配置类中放行登录请求

//配置 Shiro 内置过滤器拦截范围
@Bean
public DefaultShiroFilterChainDefinition
shiroFilterChainDefinition(){
    DefaultShiroFilterChainDefinition definition = new
            DefaultShiroFilterChainDefinition();
    //设置不认证可以访问的资源
    definition.addPathDefinition("/myController/userLogin","anon");
    definition.addPathDefinition("/login","anon");

    //设置需要进行登录认证的拦截范围
    definition.addPathDefinition("/**","authc");
    return definition;
}

(5)在配置文件中声明登录地址

image-20220930205040733

7、请求认证原理

在controller中写一个处理器,我们视图在未登录的情况下访问这个接口

@ResponseBody
@GetMapping("/test1")
public String test1(){
    return "test1";
}

在为进行登录认证的情况下,访问之后会重定向到login页面,这是因为在配置文件中的配置

之后通过登录页面登录之后,看一下请求的响应为:

image-20220930210515501

同时存储了session的JSESSIONID

认证成功之后,我们再访问/test1接口,发现可以正常访问,看一下发送的请求如下

image-20220930210823895

所以说shiro是通过session来存储认证的

3.3 多个realm的认证策略设置

SecurityManager来管理多个Realm

1、实现原理

​ 当应用程序配置多个 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 方式。可以通过配置修改策略

2、代码实现

在配置类中配置SecurityManager的过程中加入认证对象

//配置 SecurityManager
@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<Realm> list = new ArrayList<>();
    list.add(myRealm);
    list.add(myRealm2);

    //4 将 myRealm 存入 defaultWebSecurityManager 对象
    defaultWebSecurityManager.setRealms(list);
    //5 返回
    return defaultWebSecurityManager;
}

这里不再举具体的例子

3.4 remember me功能

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

1、基本流程

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

(2) 关闭浏览器再重新打开;会发现浏览器还是记住你的;
(3) 访问一般的网页服务器端,仍然知道你是谁,且能正常访问;

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

2、代码实现

(1)在配置类中进行修改

添加两个方法,分别为设置cookie的属性和 创建Shiro 的 cookie 管理对象

//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 = new
            CookieRememberMeManager();
    cookieRememberMeManager.setCookie(rememberMeCookie());

    //rememberMe 对应cookie的加密密匙
    cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());
    return cookieRememberMeManager;
}

然后在配置SecurityManager的过程中加上第4.5步

//配置SecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
    //1、创建defaultWebSecurityManager 对象
    DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

    //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 设置rememberMe
    defaultWebSecurityManager.setRememberMeManager(rememberMeManager());

    //5、返回
    return defaultWebSecurityManager;
}

第三步添加存在用户的过滤器

在创建Shiro过滤器方法中修改

//配置 Shiro 内置过滤器拦截范围
@Bean
public DefaultShiroFilterChainDefinition
shiroFilterChainDefinition(){
    DefaultShiroFilterChainDefinition definition = new
            DefaultShiroFilterChainDefinition();
    //设置不认证可以访问的资源
    definition.addPathDefinition("/myController/userLogin","anon");
    definition.addPathDefinition("/login","anon");

    //设置需要进行登录认证的拦截范围
    definition.addPathDefinition("/**","authc");

    //添加存在用户的过滤器(rememberMe)
    definition.addPathDefinition("/**","user");
    return definition;
}

(2)修改controller

在认证的方法中,获取remember这一个参数

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

//登录认证验证 rememberMe
@GetMapping("/userLoginRm")
public String userLogin(HttpSession session) {
    session.setAttribute("user","rememberMe");
    return "main";
}

(3)在登陆页面添加一个记住我的选项

<body>
<h1>Shiro 登录认证</h1>
<br>
<form action="/myController/userLogin">
    <div>用户名:<input type="text" name="name" value=""></div>
    <div>密码:<input type="password" name="pwd" value=""></div>
    <div>记住用户:<input type="checkbox" name="rememberMe" value="true"></div>

    <div><input type="submit" value="登录"></div>
</form>
</body>
3、测试

(1)

直接通过地址访问userLoginRm

http://localhost:8080/myController/userLoginRm

过滤器拦截跳回登录页面

(2)登录勾选记住用户

image-20220930214430259

(3)关闭浏览器之后,再直接访问访问userLoginRm

http://localhost:8080/myController/userLoginRm

访问成功

image-20220930214514729

3.5 用户登录认证之后登出

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

<body>
<h1>Shiro 登录认证后主页面</h1>
<br>
登录用户为:<span th:text="${session.user}"></span>
<br>
<a href="/logout">登出</a>
</body>

直接在过滤器中配置即可

//配置 Shiro 内置过滤器拦截范围
@Bean
public DefaultShiroFilterChainDefinition
shiroFilterChainDefinition(){
    DefaultShiroFilterChainDefinition definition = new
            DefaultShiroFilterChainDefinition();
    //设置不认证可以访问的资源
    definition.addPathDefinition("/myController/userLogin","anon");
    definition.addPathDefinition("/login","anon");

    //配置登出过滤器
    definition.addPathDefinition("/logout","logout");
    //设置需要进行登录认证的拦截范围
    definition.addPathDefinition("/**","authc");

    //添加存在用户的过滤器(rememberMe)
    definition.addPathDefinition("/**","user");
    return definition;
}

3.6 授权、角色认证

这里只是演示shiro的授权、权限控制功能,并不是严格的RBAC权限控制模型

1、授权

用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进行判 断。 这个工具就是Realm的doGetAuthorizationInfo方法进行判断。触发权限判断的有两种 方式

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

2、后端接口注解

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

(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()

3、验证角色

(1)建表

角色表

CREATE TABLE `role` (
  `id` bigint 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=3 DEFAULT CHARSET=utf8mb3 COMMENT='角色表';

image-20221001212524638

角色-用户映射表

CREATE TABLE `role_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `uid` bigint DEFAULT NULL COMMENT '用户 id',
  `rid` bigint DEFAULT NULL COMMENT '角色 id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COMMENT='角色用户映射\r\n表';

image-20221001212535061

(2)业务准备

mapper

//根据用户名查询用户角色信息
@Select("SELECT NAME FROM role WHERE id IN (SELECT rid FROM role_user WHERE uid=(SELECT id FROM USER WHERE NAME=#{principal}))")
List<String> getUserRoleInfoMapper(@Param("principal") String  principal);

service

@Override
public List<String> getUserRoleInfo(String name) {
    return userMapper.getUserRoleInfoMapper(name);
}

(3)改造MyRealm授权方法

//自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    System.out.println("进入 自定义授权方法");
    //1、获取用户信息
    String principal = principals.getPrimaryPrincipal().toString();
    //2、调用业务层获取用户的角色信息(数据库中)
    List<String> roles = userService.getUserRoleInfo(principal);
    System.out.println("当前用户角色信息="+roles);

    //3、创建对象,封装当前登录用户的角色、权限信息
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.addRoles(roles);

    //3、返回信息
    return info;
}

(4)改造前端页面

<body>
 <h1>Shiro 登录认证后主页面</h1>
 <br>
 登录用户为:<span th:text="${session.user}"></span>
 <br>
 <a href="/logout">登出</a>
<br>
 <a href="/myController/userLoginRoles">测试授权</a>
</body>

(5)测试

image-20221001212848141

4、权限验证

(1)建表

权限表:

CREATE TABLE `permissions` (
  `id` bigint 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=4 DEFAULT CHARSET=utf8mb3 COMMENT='权限表';

image-20221001213013213

权限角色映射表:

CREATE TABLE `role_ps` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `rid` bigint DEFAULT NULL COMMENT '角色 id',
  `pid` bigint DEFAULT NULL COMMENT '权限 id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COMMENT='角色权限映射\r\n表';

image-20221001213059821

(2)业务准备

这里只是用作演示,没有那么严谨

mapper:

@Select({
            "<script>",
            "select info FROM permissions WHERE id IN ",
            "(SELECT pid FROM role_ps WHERE rid IN (",
            "SELECT id FROM role WHERE NAME IN ",
            "<foreach collection='roles' item='name' open='(' separator=',' close=')'>",
            "#{name}",
            "</foreach>",
            "))",
            "</script>"
            })
    List<String> getUserPermissionInfoMapper(@Param("roles")List<String> roles);

service

@Override
public List<String> getUserPermissionInfo(List<String> roles) {
    return userMapper.getUserPermissionInfoMapper(roles);
}

(3)改造MyRealm授权方法

//自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    System.out.println("进入 自定义授权方法");
    //1、获取用户信息
    String principal = principals.getPrimaryPrincipal().toString();
    //2、调用业务层获取用户的角色信息(数据库中)
    List<String> roles = userService.getUserRoleInfo(principal);
    System.out.println("当前用户角色信息="+roles);

    List<String> permissions = userService.getUserPermissionInfo(roles);
    System.out.println("当前用户的权限信息="+permissions);

    //3、创建对象,封装当前登录用户的角色、权限信息
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.addRoles(roles);
    info.addStringPermissions(permissions);

    //3、返回信息
    return info;
}

(4)改造前端页面

<body>
 <h1>Shiro 登录认证后主页面</h1>
 <br>
 登录用户为:<span th:text="${session.user}"></span>
 <br>
 <a href="/logout">登出</a>
 <br>
 <a href="/myController/userLoginRoles">测试授权-角色验证</a>
 <br>
 <a href="/myController/userPermissions">测试授权-权限验证</a>
</body>

(5)启动测试

image-20221001213520491

3.7 异常处理

当身份认证不通过和权限认证不通过的时候,shiro都会直接抛出异常,因此我们需要对这些异常进行过处理

直接添加一个异常处理类来捕获两个异常即可

@ControllerAdvice
public class PermissionsExceptionHandler {
    @ResponseBody
    @ExceptionHandler(UnauthorizedException.class)
    public String noPermissions(){
        return "无权限";
    }

    @ResponseBody
    @ExceptionHandler(AuthenticationException.class)
    public String authorizationException(){
        return "身份认证失败";
    }
}

image-20221001213745421

image-20221001213753128

3.8 前端页面的权限控制

就是拥有不同角色或权限的用户看到的页面是不一样的,这里提供了一种思想,实际中也都是这样解决的,即判断用户的权限来显示内容。

这里前端用thymeleaf来演示

(1)添加依赖

<!--配置 Thymeleaf 与 Shrio 的整合依赖-->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

(2)给配置类添加配置

//用于解析 thymeleaf 中的 shiro:相关属性
@Bean
public ShiroDialect shiroDialect(){
    return new ShiroDialect();
}

(3)Thymeleaf中常用的shiro:属性

标签说明
guest用户没有身份验证时显示相应信息,即游客访问信息
user用户已经身份验证/记住我登录后显示相应的信息
authenticated用户已经身份验证通过,即 Subject.login 登录成功,不是记住我登录的
notAuthenticated用户已经身份验证通过,即没有调用 Subject.login 进行登录,包括记住我自动登录的 也属于未进行身份验证
principal相当于((User)Subject.getPrincipals()).getUsername()
lacksPermission如果当前 Subject 没有权限将显示 body 体内容
hasRole如果当前 Subject 有角色将显示 body 体内容
hasAnyRoles如果当前 Subject 有任意一个角色(或的关系)将显示 body 体内容
lacksRole如果当前 Subject 没有角色将显示 body 体内容
hasPermission如果当前 Subject 有权限将显示 body 体内容

其中用的较多的是:hasRolehasPermission,比如:<shiro:hasRole name="admin"></shiro:hasRole><shiro:hasPermission name="user:create"></shiro:hasPermission>

(4)改造前端页面

注意引入相应的命名空间

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
                xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Shiro 登录认证后主页面</h1>
<br>
登录用户为:<span th:text="${session.user}"></span>
<br>
<a href="/logout">登出</a>
<br/>
<a shiro:hasRole="admin" href="/myController/userLoginRoles">测试授权</a>
<br>
<a shiro:hasPermission="user:delete" href="/myController/userPermissions">测试授权-权限验证</a>
</body>
</html>

(5)结果

image-20221001214836337

四、整合Redis缓存

4.1 shiro缓存说明

从上面可以看到,客户端发送一次请求我们都需要进行一次判断,判断该请求携带的用户信息是否有权限,需要从数据库中查询,这样会给数据库带来很大的压力,所以考虑到使用缓存

shiro官方提供了整合EhCache缓存,非常方便,但是这个缓存是单机的,对于我们的分布式架构不友好,所以这里使用redis

4.2 整合

1、引入依赖

行吧,我是用springboot整合完成了spring版本的,才发现他们还提供了springboot的starter,救命,所以为了图方便,你可以直接看4.3

由于shiro官方并没有提供整合redis的依赖,我们引入的是github上开源的第三方依赖shiro-redis

该依赖的使用文档为:http://alexxiyang.github.io/shiro-redis/

下面主要是参考的这个依赖的文档

<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>3.3.1</version>
</dependency>

对应的版本关系为:

image-20221002203448702

2、用户主体标识唯一

使用redis的key-value形式存储,所以需要保证用户的身份标识唯一,即principal唯一,就是在SimpleAuthenticationInfo方法中token.getPrincipal()这个值唯一

//自定义登录方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    //1、获取用户信息
    String username = token.getPrincipal().toString();
    //2、调用业务层获取用户信息(数据库中)
    User user = userService.getUserInfoByName(username);
    //3、非空格判断,将数据封装返回
    if(user != null){
        /**
         * 1、用户身份信息
         * 2、用户在数据中存储的密码(加密之后的)
         * 3、盐值
         * 4、用户名
         */
        AuthenticationInfo info = new SimpleAuthenticationInfo(
                token.getPrincipal(),
                user.getPwd(),
                ByteSource.Util.bytes("salt"),
                username
        );
        return info;
    }
    return null;
}

如果这里是一个实体类,比如:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
    UserInfo userInfo = new UserInfo();
    userInfo.setUsername(usernamePasswordToken.getUsername());
    return new SimpleAuthenticationInfo(userInfo, "123456", getName());
}

那么需要保证类UserInfo中的一个属性唯一,比如设置userId唯一,就可以在配置中设置cacheManager的属性

<property name="principalIdFieldName" value="userId" />
3、使用配置类配置

shiro-redis插件官方在文档中提供了spring原生配置文件中的配置,这里就使用配置类代替配置文件了,具体的参数参考文档即可:http://alexxiyang.github.io/shiro-redis/

其中详细配置参考:http://alexxiyang.github.io/shiro-redis/#configurable-options

这里完全是跟着官方文档做就行了

@Configuration
public class ShiroConfig {
    //注入自定理的realm
    @Autowired
    private MyRealm myRealm;

    //配置SecurityManager
    //传入sessionManager,cacheManager,会自动从容器中找
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(SessionManager sessionManager, CacheManager cacheManager){
        //1、创建defaultWebSecurityManager 对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        //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 设置rememberMe
        defaultWebSecurityManager.setRememberMeManager(rememberMeManager());

        //4.6、设置缓存
        defaultWebSecurityManager.setSessionManager(sessionManager);
        defaultWebSecurityManager.setCacheManager(cacheManager);

        //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 = new
                CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());

        //rememberMe 对应cookie的加密密匙
        cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());
        return cookieRememberMeManager;
    }

    //配置 Shiro 内置过滤器拦截范围
    @Bean
    public DefaultShiroFilterChainDefinition
    shiroFilterChainDefinition(){
       //略
    }

    //用于解析 thymeleaf 中的 shiro:相关属性
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

    //shiro 的RedisManager
    @Bean
    public RedisManager redisManager(){
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("82.157.140.177:6379");
        redisManager.setPassword("123321");
        return redisManager;
    }

     //RedisSessionDAO shiro sessionDao层的实现
     //原理就是重写 AbstractSessionDAO
    @Bean
    public RedisSessionDAO redisSessionDAO(RedisManager redisManager){
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager);
        return redisSessionDAO;
    }

    //shiro的sessionManager
    @Bean
    public DefaultWebSessionManager sessionManager(RedisSessionDAO redisSessionDAO){
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        defaultWebSessionManager.setSessionDAO(redisSessionDAO);
        return defaultWebSessionManager;
    }

    //cacheManager 缓存 redis实现
    @Bean
    public RedisCacheManager cacheManager(RedisManager redisManager){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager);
        return redisCacheManager;
    }
}
4、将属性改到配置文件中

注意到我们是在配置类中将redis的地址写死了,这样肯定不好,所以把这些配置都写配置文件中最好

其实很容易实现,就在配置文件中写好,然后在配置类中使用@Value注解去读取就行了

5、测试

可以发现,只有登录的时候,才查询了数据库,其他的操作都没有查询数据库

image-20221002214813831

4.3 starter整合

直接引入

<!--直接引入第三方整合shiro和redis依赖的starter-->
<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis-spring-boot-starter</artifactId>
    <version>3.3.1</version>
</dependency>

配置类中:

直接注入RedisSessionDAO和RedisCacheManager,其中sessionManager要使用RedisSessionDAO,SecurityManager使用sessionManager和RedisCacheManager

    //按照名字找到redisSessionDAO
    @Resource
    private RedisSessionDAO redisSessionDAO;

    //按照名字找到redisCacheManager
    @Resource
    private RedisCacheManager redisCacheManager;
//配置SecurityManager
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(SessionManager sessionManager){
        //1、创建defaultWebSecurityManager 对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        //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 设置rememberMe
        defaultWebSecurityManager.setRememberMeManager(rememberMeManager());

        //4.6、设置缓存
        defaultWebSecurityManager.setSessionManager(sessionManager);
        defaultWebSecurityManager.setCacheManager(redisCacheManager);

        //5、返回
        return defaultWebSecurityManager;
    }

    //shiro的sessionManager
    @Bean
    public DefaultWebSessionManager sessionManager(){
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        defaultWebSessionManager.setSessionDAO(redisSessionDAO);
        return defaultWebSessionManager;
    }

配置文件中指定redis的连接属性即可

shiro-redis:
  enabled: true
  redis-manager:
    host: 127.0.0.1:6379
    password: 123321

详细配置信息:

TitleDefaultDescription
shiro-redis.enabledtrueEnables shiro-redis’s Spring module
shiro-redis.redis-manager.deploy-modestandaloneRedis deploy mode. Options: standalone, sentinel, ‘cluster’
shiro-redis.redis-manager.host127.0.0.1:6379Redis host. If you don’t specify host the default value is 127.0.0.1:6379. If you run redis in sentinel mode or cluster mode, separate host names with comma, like 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381
shiro-redis.redis-manager.master-namemymasterOnly used for sentinel mode The master node of Redis sentinel mode
shiro-redis.redis-manager.timeout2000Redis connect timeout. Timeout for jedis try to connect to redis server(In milliseconds)
shiro-redis.redis-manager.so-timeout2000Only used for sentinel mode or cluster mode The timeout for jedis try to read data from redis server
shiro-redis.redis-manager.max-attempts3Only used for cluster mode Max attempts to connect to server
shiro-redis.redis-manager.passwordRedis password
shiro-redis.redis-manager.database0Redis database. Default value is 0
shiro-redis.redis-manager.count100Scan count. Shiro-redis use Scan to get keys, so you can define the number of elements returned at every iteration.
shiro-redis.session-dao.expire-2Redis cache key/value expire time. The expire time is in second. Special values: -1: no expire -2: the same timeout with session Default value: -2 Note: Make sure expire time is longer than session timeout.
shiro-redis.session-dao.key-prefixshiro:session:Custom your redis key prefix for session management Note: Remember to add colon at the end of prefix.
shiro-redis.session-dao.session-in-memory-timeout1000When we do signin, doReadSession(sessionId) will be called by shiro about 10 times. So shiro-redis save Session in ThreadLocal to remit this problem. sessionInMemoryTimeout is expiration of Session in ThreadLocal. Most of time, you don’t need to change it.
shiro-redis.session-dao.session-in-memory-enabledtrueWhether or not enable temporary save session in ThreadLocal
shiro-redis.cache-manager.principal-id-field-nameidPrincipal id field name. The field which you can get unique id to identify this principal. For example, if you use UserInfo as Principal class, the id field maybe id, userId, email, etc. Remember to add getter to this id field. For example, getId(), getUserId(), getEmail(), etc. Default value is id, that means your principal object must has a method called getId()
shiro-redis.cache-manager.expire1800Redis cache key/value expire time. The expire time is in second.
shiro-redis.cache-manager.key-prefixshiro:cache:Custom your redis key prefix for cache management Note: Remember to add colon at the end of prefix.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值