Shiro安全框架

安全框架Shiro

1,什么是RBAC

Role-Based Access Control 基于角色的访问控制

Resource-Based Access Control 基于资源的访问控制

2,什么是安全框架?

给项目添加认证与授权功能的框架, 就叫安全框架

认证: 验证用户身份的合法性(验证用户名和密码是否正确)

授权: 授权用户访问资源的权限

3, 都有哪些安全框架

Spring Security

Shiro

sa-token

4,Shiro的4大核心

认证(Authentication), 就相当于验证账号和密码是否正确. 就是判断你的身份是否合法.

授权(Authorization), 就是授予你访问资源的权利

session会话管理,

加密(Cryptography), 一般就是给密码加密

5,springboot2里添加shiro

maven的中央仓库: https://mvnrepository.com/


<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-spring-boot-web-starterartifactId>
    <version>1.9.1version>
dependency>

6, 加密

shiro自带6种加密方式分别是

MD2, MD5, SHA-1, SHA-256, SHA-384, SHA-512

我们选择SHA-256进行一次加密,试试

RandomNumberGenerator random = new SecureRandomNumberGenerator();
String salt = random.nextBytes().toHex();
System.out.println("salt = " + salt);
//SimpleHash构造函数的参数说明(加密方式, 原密码, 盐, 哈希迭代数)
String pass = new SimpleHash("SHA-256", "zhaogang", salt, 1).toHex();
System.out.println("pass = " + pass);

打印结果

salt = 17228b8287f254609b0e00471ddaee37
pass = 81bffda6895fa3eb78523412fb6ad659b62cd0a4bc4f990b6edcb7e386704842

我们把数据库里的密码和盐进行修改

Shiro安全框架_第1张图片

7,创建自定义Realm

Realm(域): 用来编写认证与授权代码的一个地方

Shiro安全框架_第2张图片

public class MyRealm extends AuthorizingRealm {
    /**
     * 授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    /**
     * 认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        return null;
    }
}
Authentication: 认证  [ɔːˌθentɪˈkeɪʃn]
Authorization: 授权	[ˌɔːθərəˈzeɪʃn] 

8,创建shiro配置类, 将自义定Realm放入ioc容器

@Configuration
public class ShiroConfig {
    @Bean("authorizer")
    public MyRealm myRealm(){
        MyRealm realm = new MyRealm();
        //添加一个凭证匹配器,设置了加密方式为SHA-256, 哈希迭代数为1, 存储格式为Hex16进制
        realm.setCredentialsMatcher(new HashedCredentialsMatcher("SHA-256"));
        return realm;
    }
}

9, 启动项目后,请求改变为/login.jsp

Shiro安全框架_第3张图片

​ 启动项目后,shiro要进行认证, 结果发现未认证, shiro会重定向到未认证的请求/login.jsp

为什么未认证

因为自定义Realm里负责认证的代码还是空的呢, 啥也没写, 如下

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    return null;
}

10, 认证

下面是shiro认证过程的流程思想图

Shiro安全框架_第4张图片

修改登录代码为如下写法

@RequestMapping("/login")
@ResponseBody
public Result login(String username, String password, HttpServletRequest request){
    //获取当前用户
    Subject subject = SecurityUtils.getSubject();
    //创建账号和密码的令牌
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    //登录
    try {
        subject.login(token);
        //查询用户信息
        SysUser user = sysUserService.getUserByUsername(username);
        //把用户信息存储到session里
        request.getSession().setAttribute("user", user);
        //修改登录次数和最后登录时间
        sysUserService.updateLoginCount(user.getUid());
        
    } catch (UnknownAccountException e) {
        return Result.error("账号错误");
    } catch (IncorrectCredentialsException e) {
        return Result.error("密码错误");
    } catch (AuthenticationException e) {
        e.printStackTrace();
        return Result.error("认证错误");
    }
    return Result.success();
}

自定义Realm里认证的代码编写如下

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    UsernamePasswordToken token1 = (UsernamePasswordToken)token;
    //账号
    String username = token1.getUsername();
    //判断账号是否正确
    SysUser user = sysUserMapper.getUserByUsername(username);
    if (user == null) {
        throw new UnknownAccountException();
    }
    //返回认证对象, 将判断密码是否正确的工作交给shiro里的凭证匹配器来完成
    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
        username,
        user.getPwd(),
        ByteSource.Util.bytes(user.getSalt()),
        getName()
    );
    return info;
}

shiro给我们提供了一些异常, 供我们所使用

Shiro安全框架_第5张图片

11, 退出

@RequestMapping("/logout")
@ResponseBody
public Result logout(HttpServletRequest request){
    //shiro里的退出
    Subject subject = SecurityUtils.getSubject();
    if (subject.isAuthenticated()) {
        subject.logout();
    }
    return Result.success();
}

12, 拦截器

给shiro设置拦截器

@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(){
    DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
    //anon 不拦截  
    definition.addPathDefinition("/", "anon");
    definition.addPathDefinition("/login", "anon");
    definition.addPathDefinition("/logout", "anon");
    //登录成功后跳转的页面
    definition.addPathDefinition("/login-success", "anon");
    definition.addPathDefinition("/static/**", "anon");
    //网页上面的图标
    definition.addPathDefinition("/favicon.ico", "anon");
    definition.addPathDefinition("/error", "anon");
	//其他请求都必须认证才能通过 authc 表示必须认证才行
    definition.addPathDefinition("/**", "authc");
    return definition;
}

13, 未认证

shiro的认证不能通过后, shiro会进行一个重定向的请求 /login.jsp

我们有两种方法来处理这个请求

在application.properties里设置shiro的未认证请求路径

# 默认, 可以修改
shiro.loginUrl=/login.jsp

在WebMvcConfig类中设置页面的跳转

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    //未认证的请求
    registry.addViewController("/login.jsp").setViewName("noauthe");
}

也可以不要设计一个错误的提示页面, 未认证就调转到登录页即可

# 也可以直接设置为跳转到登录页
shiro.loginUrl=/

14, 认证的完整代码

(1), shiro配置类

@Configuration
public class ShiroConfig {
    /**
     * 自定义Realm, 用来编写认证与授权代码的地方.
     */
    @Bean
    public Realm realm(){
        MyRealm realm = new MyRealm();
        //添加一个凭证匹配器,设置了加密方式为SHA-256, 哈希迭代数为1, 存储格式为Hex16进制
        realm.setCredentialsMatcher(new HashedCredentialsMatcher("SHA-256"));
        return realm;
    }
    /**
     * 拦截器设置,设置哪些该拦截, 哪些不该拦截
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        definition.addPathDefinition("/", "anon");
        definition.addPathDefinition("/login", "anon");
        definition.addPathDefinition("/logout", "anon");
        //登录成功后跳转的页面
        definition.addPathDefinition("/login-success", "anon");
        definition.addPathDefinition("/static/**", "anon");
        //网页上面的图标
        definition.addPathDefinition("/favicon.ico", "anon");
        definition.addPathDefinition("/error", "anon");
        //其他请求都必须认证才能通过
        definition.addPathDefinition("/**", "authc");
        return definition;
    }
}

(2), 自定义Realm里的代码

public class MyRealm extends AuthorizingRealm {
    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授权");
        return null;
    }
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken token1 = (UsernamePasswordToken)token;
        String username = token1.getUsername();
        SysUser user = sysUserMapper.getUserByUsername(username);
        if (user == null) {
            throw new UnknownAccountException();
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
                username,
                user.getPwd(),
                ByteSource.Util.bytes(user.getSalt()),
                getName()
        );
        return info;
    }
}

(3),登录代码

@RequestMapping("/login")
@ResponseBody
public Result login(String username, String password, HttpServletRequest request){
    //获取当前的用户
    Subject subject = SecurityUtils.getSubject();
    //创建一个令牌
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    //登录
    try {
        subject.login(token);
        SysUser user = sysUserService.getUserByUsername(username);
        //账号和密码都正确, 则把用户信息存储到会话作用域中
        request.getSession().setAttribute("user", user);
        //修改登录次数和最后登录时间
        sysUserService.updateLoginCount(user.getUid());
    } catch (UnknownAccountException e) {
        return Result.error("账号或者密码错误!");
    } catch (IncorrectCredentialsException e) {
        return Result.error("账号或者密码错误!!");
    } catch (AuthenticationException e) {
        e.printStackTrace();
        return Result.error("认证失败");
    }
    return Result.success();
}

15, 授权

1, 例子1: 固定角色的写法

先学一个注解@RequiresRoles(“”) 基于角色的授权

在自定义Realm里编写授权代码, 给用户一个固定的角色名称

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    System.out.println("授权");
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.addRole("p1");
    return info;
}

在控制层的方法上, 加入注解@RequiresRoles(“p1”)来指定必须是p1角色才能访问此方法

@RequestMapping("/user/query")
@RequiresRoles("p1")
public String query(Integer pageNum, ModelMap modelMap, String account,String name){}

在转添加页面的方法上, 加入注解@RequiresRoles(“p2”),来指定必须是p2角色才能访问此方法

@RequiresRoles("p2")
@GetMapping("/user/addpage")
public String addpage(ModelMap modelMap){}

结果测试

访问query方法时, 没有问题, 但是访问addpage方法时,页面报了一个异常

Shiro安全框架_第6张图片

特别注意: 如果加入注解@RequiresRoles后,页面不是500而是404错误, 如下页面

Shiro安全框架_第7张图片

则在shiro配置文件里加入下面的代码即可.

@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAdapterRegistry(){
	DefaultAdvisorAutoProxyCreator creator = new
	DefaultAdvisorAutoProxyCreator();
	creator.setProxyTargetClass(true);
	return creator;
}

2 例子2, 固定权限字符的写法

在授权里, 给用户设置两个权限

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    System.out.println("授权");
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    Set<String> set = new HashSet<>();
    set.add("user:query");
    set.add("user:addsave");
    info.setStringPermissions(set);
    return info;
}

控制层里,给方法添加对应的访问权限

@RequiresPermissions("user:query")
@RequestMapping("/user/query")
public String query(Integer pageNum, ModelMap modelMap, String account,String name){}
@RequiresPermissions("user:addpage")
@GetMapping("/user/addpage")
public String addpage(ModelMap modelMap){}

因为在授权里,没有给用户设置user:addpage的权限, 故此,访问addpage方法时也会报异常

Shiro安全框架_第8张图片

3,查询用户的真实角色和权限字符

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String userName = (String) principals.getPrimaryPrincipal();
    //查询角色
    Set<String> roles = sysUserMapper.getRoleByUserName(userName);
    Set<String> permissions = sysUserMapper.getPermissionByUserName(userName);
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.setRoles(roles);
    info.setStringPermissions(permissions);
    return info;
}

根据用户查询角色的SQL

select role_name
from sys_role r
inner join sys_user_role ur on r.role_id=ur.role_id
inner join sys_user u on ur.user_id=u.uid
where u.account='zhangfei'

控制层里根据角色访问的写法为

@RequiresRoles("admin")
@RequestMapping("/user/query")
public String query(Integer pageNum, ModelMap modelMap, String account,String name){}
@RequiresRoles(value = {"admin", "accounting"}, logical = Logical.OR)
@RequestMapping("/user/query")
public String query(Integer pageNum, ModelMap modelMap, String account,String name){}

Logical.OR表示前面的两个角色是或者关系, 默认是并且的关系

16,自定义异常解析器

1, 针对普通请求设置的异常解析

@ControllerAdvice
public class MyExceptionResolve {
    /**
     * 抓取未授权异常,跳转到页面
     */
    @ExceptionHandler(UnauthorizedException.class)
    public String unauthorized(){
        return "error/noautho";
    }
}

2, 针对ajax请求设置的异常解析

@ControllerAdvice
public class MyExceptionResolve {
    private final static String XHR = "XMLHttpRequest";
    /**
     * 抓取未授权异常,跳转到页面
     */
    @ExceptionHandler(UnauthorizedException.class)
    public String unauthorized(HttpServletRequest request, HttpServletResponse response){
        String xrw = request.getHeader("X-Requested-With");
        //判断是否是ajax请求.
        if (XHR.equals(xrw)) {
            Result r = Result.error("权限不足");
            response.setContentType("html/text; charset=utf-8");
            try {
                response.getWriter().println(JSON.toJSONString(r));
                return null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "error/noautho";
    }
}

17, 授权4个方式

1 注解式授权

//基于角色的授权
@RequiresRoles(value = {"admin", "system"},logical = Logical.OR)
@RequiresRoles("admin")
//基于权限字符的授权
@RequiresPermissions("user:query")

这些注解可以放在方法上, 也可以放在类上

放在方法上表示, 运行这个方法必须满足指定的角色或者权限

放在类(控制层)上, 表示这个类中的所有方法都必须满足指定的角色或者权限

注解式授权如果不满足角色或者权限,则抛异常UnauthorizedException

2 编程式授权

Subject subject = SecurityUtils.getSubject();
//1,判断用户是否拥有admin角色
if (!subject.hasRole("admin")) {
    //内部转到权限不足提示页面
    return "error/noautho";
}
//2,判断用户是否拥有user:add的权限
if (!subject.isPermitted("user:add")) {
    return Result.error("权限不足");
}
//3,判断用户是否拥有user:add的权限, 如果没有这个权限,则抛一个异常UnauthorizedException
subject.checkPermission("user:add");

3 拦截器授权

shiro里有一个默认过滤器DefaultFilter的枚举,里面有13个过滤器.

过滤器(拦截)名称 解释
anon 不拦截,不登录就可以访问
authc 认证拦截
roles 基于角色的拦截
perms 基于权限的拦截
port 基于的端口的拦截
ssl 带加密的请求,SSL拦截器
user 认证或者用了记住我功能的拦截
rest 基于RestFul风格的拦截
authcBasic Basic HTTP 身份验证拦截器
logout 退出拦截器
noSessionCreation 不创建会话拦截器

基于拦截器的授权写法

definition.addPathDefinition("/user/query", "roles[admin]");

如果没有权限, 我们可以设置一个未授权的请求路径,跳转到一个友好的提示页面

shiro.unauthorizedUrl=/noautho

缺点: 下面的写法表示必须同时具有两个角色, 如果只觉有期中一个,则被拦截.

definition.addPathDefinition("/user/query", "roles[admin,system]");

基于权限字符的拦截

definition.addPathDefinition("/user/query", "perms[user:query]");

4 标签式授权

能隐藏一些标签, 不让没有权限的人看到

(1), 添加一个shiro标签的方言依赖包()


<dependency>
    <groupId>com.github.theborakompanionigroupId>
    <artifactId>thymeleaf-extras-shiroartifactId>
    <version>2.0.0version>
dependency>

(2), 在shiro配置文件里, 创建一个方言组件

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

(3),在HTML页面里, 首先给html标签添加一个shiro标签的名字空间(ns=name space)

<html lang="zh" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

(4),可以使用的shiro标签

标签名 解释
shiro:hasRole=“admin” 只有角色名称是admin的用户才可以看到对应的标签
shiro:lacksRole=“admin” 与上面正好相反, 拥有admin角色的用户,看不到这个标签
shiro:hasPermission=“‘user:add’” 拥有user:add权限的人, 能看到这个标签
shiro:lacksPermission=“‘user:add’” 与上面相反, 拥有user:add 看不到这个标签
shiro:hasAllRoles=“developer, admin” 验证用户是否满足前面所有角色
shiro:hasAnyRoles=“admin, vip, developer” 验证用户是否属于前面任意一个角色
shiro:hasAllPermissions=“‘user:query, user:add’” 验证用户是有满足前面所有权限
shiro:hasAnyPermissions=“‘user:query, user:add’” 验证用户是否满足前面任意一个权限<
shiro:authenticated=“” 已认证通过的用户。不包含已记住的用户
shiro:user=“” 认证通过或已记住的用户
shiro:guest=“” 验证当前用户是否为“访客”,即未认证(包含未记住)的用户
shiro:notAuthenticated=“” 未认证通过用户,与 authenticated 标签相对应。
与 guest 标签的区别是,该标签包含已记住用户
欢迎你: <shiro:principal/>			表示输出用户的身份(常用的身份就是账号)

18, 会话管理

shiro也提供了一个session

Session session = subject.getSession();

在shiro-spring-boot-web-starter里, shiro的session和HttpSession做了整合,

用httpSession存储的数据, 可以用shiro的session取值.

//存, 取, 删
Session session = subject.getSession();
session.setAttribute("user", user);
session.getAttribute("user");
session.removeAttribute("user");

19, 缓存

shiro中缓存的重要性: 提高授权的性能

没有缓存之前的现象是, 授权的代码会执行无数次,而添加了缓存后, 授权的代码只执行了一次

shiro里提供了一个基于内存的缓存

注意导包

import org.apache.shiro.cache.CacheManager;
@Bean
public CacheManager cacheManager(){
    return new MemoryConstrainedCacheManager();
}

这个缓存实际上是一个Map

有缓存的编程模型如下:

Shiro安全框架_第9张图片

20, shiro配置文件完整代码如下

@Configuration
public class ShiroConfig {
    
    @Bean
	@ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAdapterRegistry(){
        DefaultAdvisorAutoProxyCreator creator = new
        DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    @Bean("authorizer")
    public MyRealm myRealm(){
        MyRealm realm = new MyRealm();
        //添加一个凭证匹配器,设置了加密方式为SHA-256, 哈希迭代数为1, 存储格式为Hex16进制
        realm.setCredentialsMatcher(new HashedCredentialsMatcher("SHA-256"));
        return realm;
    }

    /**
     * 过滤拦截设置
     * @return
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        definition.addPathDefinition("/", "anon");
        definition.addPathDefinition("/login", "anon");
        definition.addPathDefinition("/logout", "anon");
        //登录成功后跳转的页面
        definition.addPathDefinition("/login-success", "anon");
        definition.addPathDefinition("/static/**", "anon");
        //网页上面的图标
        definition.addPathDefinition("/favicon.ico", "anon");
        definition.addPathDefinition("/error", "anon");
        definition.addPathDefinition("/unautho", "anon");

        //其他请求都必须认证才能通过
        definition.addPathDefinition("/**", "authc");
        return definition;
    }

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

pom文件里需要的依赖包


<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-spring-boot-web-starterartifactId>
    <version>1.9.1version>
dependency>

<dependency>
    <groupId>com.github.theborakompanionigroupId>
    <artifactId>thymeleaf-extras-shiroartifactId>
    <version>2.0.0version>
dependency>

自定义Realm里的代码

public class MyRealm extends AuthorizingRealm {
    @Resource
    private SysUserMapper sysUserMapper;
    /**
     * 授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //查询用户真正的角色
        Set<String> roles = sysUserMapper.getRoleNameByUserName(username);
        info.setRoles(roles);
        //查询用户的权限字符
        Set<String > permission=sysUserMapper.getPermissionByUserName(username);
        info.setStringPermissions(permission);
        return info;
    }

    /**
     * 认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken token1 = (UsernamePasswordToken)token;
        String username = token1.getUsername();
        //判断账号是否正确
        SysUser user = sysUserMapper.getUserByUsername(username);
        if (user == null) {
            throw new UnknownAccountException();
        }
        //返回认证对象, 将判断密码是否正确的工作交给shiro里的凭证匹配器来完成
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
                username,
                user.getPwd(),
                ByteSource.Util.bytes(user.getSalt()),
                getName()
        );
        return info;
    }
}

SQL语句


<select id="getRoleNameByUserName" resultType="java.lang.String">
    select role_name
    from sys_role r
             inner join sys_user_role ur on r.role_id=ur.role_id
             inner join sys_user u on ur.user_id=u.uid
    where u.account=#{username}
select>

<select id="getPermissionByUserName" resultType="java.lang.String">
    select m.pstr
	from sys_menu m
	inner join sys_role_menu rm on m.mid=rm.mid
	inner join sys_role r on rm.role_id=r.role_id
	inner join sys_user_role ur on r.role_id=ur.role_id
	inner join sys_user u on ur.user_id=u.uid
	where u.account = #{username}
	and m.pstr is not null
select>

21, 给springboot添加一个ehcache缓存(目的是为了给shiro换缓存)

换一个Ehcache缓存

我们需要把ehcache整合到spring里, 还需要整合到shiro里,所以需要两个依赖包

(1) 添加依赖包


<dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-ehcacheartifactId>
    <version>1.9.1version>
dependency>

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-cacheartifactId>
dependency>

(2), 添加一个ehcache.xml配置文件

timeToIdleSeconds="120" //空闲时间: 120秒内,只要有查询,则时间重置为120秒.
例子: 米饭需要2分钟就会凉了, 但是2分钟内只要加热一次, 则米饭又需要2分钟才能凉.
timeToLiveSeconds="120" //生存时间, 只有2分钟,时间一到就删除掉.

<ehcache>
    
    <diskStore path="java.io.tmpdir/shiro-ehcache"/>
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="false"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120"
    />
    <cache name="userCache"
            maxElementsInMemory="2000"
            eternal="false"
            timeToIdleSeconds="60"
            timeToLiveSeconds="0"
            overflowToDisk="false"
    >cache>

ehcache>

(3),在springboot的application.properties里加载ehcache的配置文件

spring.cache.ehcache.config=classpath:/ehcache.xml

22, 给shiro换缓存

import net.sf.ehcache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
@Bean
public EhCacheManager ehCacheManager(CacheManager cacheManager){
    EhCacheManager ehCacheManager = new EhCacheManager();
    ehCacheManager.setCacheManager(cacheManager);
    return ehCacheManager;
}

23, springboot项目里怎么使用缓存

(1),开启缓存

@EnableCaching

(2),在springboot里怎么使用这个缓存呢?

第一步,注入缓存管理器, 注意导包 import org.springframework.cache.CacheManager;

@Autowired
private CacheManager cacheManager;

第二步, 按照缓存使用原则的编程模型,开始编写代码

queryWrapper.orderByDesc("uid");
//=====================开始=========================
Cache cache = cacheManager.getCache("userCache");
String key = "user-"+pageNum;
PageInfo<SysUser> pageInfo = cache.get(key, PageInfo.class);
if (pageInfo == null) {
    PageHelper.startPage(pageNum, 10);
    List<SysUser> list = sysUserService.list(queryWrapper);
    pageInfo = new PageInfo<>(list);
    cache.put(key, pageInfo);
}
//=====================结束============================
modelMap.put("pageInfo", pageInfo);
modelMap.put("account", account);
modelMap.put("name", name);

第三步, 缓存和数据库同步问题,

我们再进行添加, 修改,删除时, 应该清空缓存

@PostMapping("/user/addsave")
@ResponseBody
public Result addsave(SysUser user, Integer[] roleIds){
    boolean bj = sysUserService.addsave(user, roleIds);
    //======================开始========================
    if (bj) {
        Cache cache = cacheManager.getCache("userCache");
        cache.clear();
    }
    //======================结束========================
    return Result.out(bj, "添加失败", user);
}

你可能感兴趣的:(Web开源框架,java,安全,java,shiro)