主要记下授权和缓存还有验证码设计。还有一个是整合thymeleaf,篇幅要太长就不记了。😎

一、授权

首先,搬运认证和授权的业务流程图:

2.png

通过shiro标签实现用户授权控制:

3.PNG

4.PNG

如图,最后"user"角色用户"xiaojin"只能看到修改和查询的链接。

通过代码实现权限控制:

5.PNG

用"user"角色用户"xiaojin"登录后直接访问http://localhost:8888/shiro/order/save会直接回到index.jsp,控制台会输出"无权访问",将角色改为"admin"的话,输出"保存订单"。

通过注解实现权限控制:@RequiresRoles("admin")(可以多加角色,别忘了在自定义realm中也要把多角色加上),注意要是以其他角色访问这个方法时根本就进不去嗷。资源判断:

@RequiresPermissions("user:update:01") //用来判断权限字符串
simpleAuthorizationInfo.addStringPermission("user:update:*"); //访问OrderController  order/save可以访问到
simpleAuthorizationInfo.addStringPermission("user:update:02");  //这样的话访问以资源注解方式访问OrderController就500了

所以根据具体业务场景具体选择权限控制方式吧。

然后就是对授权时数据进行持久化了,继续使用mybatis,类似认证时,就是涉及多对多的连表查询了,数据库这块,我败了,反正最后达到的效果就是从表里查回的这个用户会带有不同的角色从而进入不同的页面。。不想贴代码了,说下这段mapper.xml:

6.PNG

就是连好几个表查询,会查回来这个用户的id、用户名、角色id、角色名。id和用户名就会放在User中,同时User实体中还要声明一个List的Role实体,为了存入角色id和角色名。

//定义角色集合
    private List<Role> roles;

然后在自定义realm中直接调用getRoles()的方法就可以获得有角色id和角色名的列表了,遍历后获得里面数据然后增加角色,从而进入不同的页面。

7.PNG

上面是基于角色的权限控制,接下来写写基于资源的权限控制。。。:

不写了,大致的意思就是表套表,循环套循环,基于资源的权限控制可以理解为在角色控制后,再根据遍历出的角色Id再去对角色用户进行更细化的控制手段。

8.PNG

二、缓存

引入shiro集成的缓存pom:

<!--引入shiro和ehcache-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.7.1</version>
        </dependency>

在自定义realm配置中开启缓存:

//开启缓存管理
        customerRealm.setCacheManager(new EhCacheManager());
        customerRealm.setCachingEnabled(true);                 //开启全局缓存
        customerRealm.setAuthenticationCachingEnabled(true);   //开启认证缓存
        customerRealm.setAuthenticationCacheName("authenticationCache");
        customerRealm.setAuthorizationCachingEnabled(true);    //开启授权缓存
        customerRealm.setAuthorizationCacheName("authorizationCache");

之后就不会频繁操作数据库了,不过还需要集成到redis中:

首先在本机装redis,然后在项目中引入redis(注意你要是在redis中设置密码了,项目配置中也要写,要不可连接不上)


spring.redis.host=127.0.0.1
spring.redis.database=0
spring.redis.port=6379
spring.redis.password=123
spring.redis.timeout=5000

需要把缓存管理器设置成自定义的Redis缓存管理器,自定义后会在容器中注入redisTemplate

//        customerRealm.setCacheManager(new EhCacheManager());
        customerRealm.setCacheManager(new RedisCacheManager());//设置自定义缓存管理器

RedisCacheManager是要继承shiro的CacheManager的,然后在RedisCache中会调用redisTemplate:

package com.jin.springboot_jsp_shiro.shiro.cache;

import com.jin.springboot_jsp_shiro.utils.ApplicationContextUtil;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.Collection;
import java.util.Set;

/**
 * @author jinyunlong
 * @date 2021/6/3 17:04
 * @profession ICBC锅炉房保安
 */
//自定义redis缓存的实现
public class RedisCache<k,v> implements Cache<k,v> {

    private String cacheName;

    public RedisCache(){

    }

    public RedisCache(String cacheName) {
        this.cacheName = cacheName;
    }

    @Override
    public v get(k k) throws CacheException {
        System.out.println("get key:"+k);
        return (v) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
    }

    @Override
    public v put(k k, v v) throws CacheException {
        System.out.println("put key:"+k);
        System.out.println("put value:"+v);
        getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
        return null;
    }

    @Override
    public v remove(k k) throws CacheException {
        return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
    }

    @Override
    public void clear() throws CacheException {
        getRedisTemplate().delete(this.cacheName);
    }

    @Override
    public int size() {
        return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
    }

    @Override
    public Set<k> keys() {
        return getRedisTemplate().opsForHash().keys(this.cacheName);
    }

    @Override
    public Collection<v> values() {
        return getRedisTemplate().opsForHash().values(this.cacheName);
    }

    public RedisTemplate getRedisTemplate(){
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

对Redis缓存操作都在该类方法中实现。然后启动项目,登录后序列化报错:

org.springframework.data.redis.serializer.SerializationException: 
Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: 
Failed to serialize object using DefaultSerializer; nested exception is java.io.NotSerializableException: 
org.apache.shiro.util.SimpleByteSource

是shiro的bug,需要把盐也自定义序列化一下:

package com.jin.springboot_jsp_shiro.shiro.salt;

import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.codec.Hex;
import org.apache.shiro.util.ByteSource;

import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Arrays;

/**
 * @author jinyunlong
 * @date 2021/6/4 10:21
 * @profession ICBC锅炉房保安
 */
//自定义salt实现 实现序列化接口
/**
 * 解决:
 *  shiro 使用缓存时出现:java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource
 *  序列化后,无法反序列化的问题
 */
public class MySimpleByteSource implements ByteSource, Serializable {
    private static final long serialVersionUID = 1L;

    private  byte[] bytes;
    private String cachedHex;
    private String cachedBase64;

    public MySimpleByteSource(){
    }

    public MySimpleByteSource(byte[] bytes) {
        this.bytes = bytes;
    }

    public MySimpleByteSource(char[] chars) {
        this.bytes = CodecSupport.toBytes(chars);
    }

    public MySimpleByteSource(String string) {
        this.bytes = CodecSupport.toBytes(string);
    }

    public MySimpleByteSource(ByteSource source) {
        this.bytes = source.getBytes();
    }

    public MySimpleByteSource(File file) {
        this.bytes = (new MySimpleByteSource.BytesHelper()).getBytes(file);
    }

    public MySimpleByteSource(InputStream stream) {
        this.bytes = (new MySimpleByteSource.BytesHelper()).getBytes(stream);
    }

    public static boolean isCompatible(Object o) {
        return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
    }

    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }

    @Override
    public byte[] getBytes() {
        return this.bytes;
    }


    @Override
    public String toHex() {
        if(this.cachedHex == null) {
            this.cachedHex = Hex.encodeToString(this.getBytes());
        }
        return this.cachedHex;
    }

    @Override
    public String toBase64() {
        if(this.cachedBase64 == null) {
            this.cachedBase64 = Base64.encodeToString(this.getBytes());
        }

        return this.cachedBase64;
    }

    @Override
    public boolean isEmpty() {
        return this.bytes == null || this.bytes.length == 0;
    }
    @Override
    public String toString() {
        return this.toBase64();
    }

    @Override
    public int hashCode() {
        return this.bytes != null && this.bytes.length != 0? Arrays.hashCode(this.bytes):0;
    }

    @Override
    public boolean equals(Object o) {
        if(o == this) {
            return true;
        } else if(o instanceof ByteSource) {
            ByteSource bs = (ByteSource)o;
            return Arrays.equals(this.getBytes(), bs.getBytes());
        } else {
            return false;
        }
    }

    private static final class BytesHelper extends CodecSupport {
        private BytesHelper() {
        }

        public byte[] getBytes(File file) {
            return this.toBytes(file);
        }

        public byte[] getBytes(InputStream stream) {
            return this.toBytes(stream);
        }
    }

}

然后在自定义realm中调用String型入参的那个方法就行。再启动后会报:

Caused by: java.io.InvalidClassException: com.jin.springboot_jsp_shiro.shiro.salt.MyByteSource; no valid constructor

这个错我还不知道为啥,不过我按视频里在自定义缓存中对RedisCache()也构造函数(有参构造要,无参构造也要,都要),将cacheName也声明后,而且以opsForValue()改成了opsForHash()就好了,不知道是这些原因中的哪个。

评论区解释说需要用到那个cacheName,序列化到redis的时候就不会出错了,应该是这个意思吧。(是吗)

好像还是不写无参构造会抛出no valid constructor。。。。(废话。。查有道去。。)

发现Redis已经计入数据了:

9.PNG

三、认证时加验证码

没什么东西,加了验证码后认证流程就以先验证验证码开始,然后是用户密码,web拦截时放行调用获取验证码接口别忘了;学到了一个经验:写继承类一定要写无参构造!

shiro还有好多标签,可以看看。之后整合thymeleaf就要写个新springboot项目了,这个项目现在的项目结构:

├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─jin
│  │  │          └─springboot_jsp_shiro
│  │  │              │  SpringbootJspShiroApplication.java
│  │  │              │  
│  │  │              ├─config
│  │  │              │      ShiroConfig.java
│  │  │              │  
│  │  │              ├─controller
│  │  │              │      OrderController.java
│  │  │              │      UserController.java
│  │  │              │  
│  │  │              ├─dao
│  │  │              │      UserDAO.java
│  │  │              │  
│  │  │              ├─entity
│  │  │              │      Perms.java
│  │  │              │      Role.java
│  │  │              │      User.java
│  │  │              │  
│  │  │              ├─service
│  │  │              │      UserService.java
│  │  │              │      UserServiceImpl.java
│  │  │              │  
│  │  │              ├─shiro
│  │  │              │  ├─cache
│  │  │              │  │      RedisCache.java
│  │  │              │  │      RedisCacheManager.java
│  │  │              │  │  
│  │  │              │  ├─realms
│  │  │              │  │      CustomerRealm.java
│  │  │              │  │  
│  │  │              │  └─salt
│  │  │              │          MySimpleByteSource.java
│  │  │              │    
│  │  │              └─utils
│  │  │                      ApplicationContextUtil.java
│  │  │                      SaltUtil.java
│  │  │                      VerifyCodeUtils.java
│  │  │                
│  │  ├─resources
│  │  │  │  application.properties
│  │  │  │  springboot_jsp_shiro.sql
│  │  │  │  
│  │  │  ├─mapper
│  │  │  │      UserDAOMapper.xml
│  │  │  │  
│  │  │  ├─static
│  │  │  └─templates
│  │  └─webapp
│  │          index.jsp
│  │          login.jsp
│  │          register.jsp

四、shiro整合thymeleaf模板

工作比较多,不整合了。

总结下使用shiro框架最关键的要点:认证、授权、缓存。都和自定义的realm相关!(入口是去程序看ShiroConfig中的getRealm()方法。)具体认证和授权的realm类和缓存的cache类都要通过继承实现。

2021.6.4 晚 网抑云评论😄,有点意思:

蓝翔学长曾对我说,就算挖穿了地球,也挖不到他迷失的心;广场舞大妈曾对我说,如果跳的足够快,她的孤独就追不上她;拾荒大叔曾对我说,如果翻垃圾翻的足够仔细,便能找回丢失的自己;碰瓷的大爷曾对我说,只要演的够逼真,就能骗过匆匆流逝的时光。想到现在,连踢足球都需要递简历面试了,我不禁潸然泪下。


标题:Shiro要点(3)
作者:jyl
地址:http://www.jinyunlong.xyz/articles/2021/06/03/1622690372279.html