上文说到 Spring Boot 上 MongoDB 的安装配置与使用,本来打算和 Redis 合并成一篇文章的,奈何一写就写多了,只好为 Redis 单独写一篇文章。

Redis

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。

Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

Redis 的性能非常高,速度也非常快。本文使用 Redis 存储用户的 Token。利用 Redis 的高速访问,快速鉴权。

首先,当然是导入Maven依赖,引用pom包:

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.1.5.RELEASE</version>
</dependency>

application.yml 中配置redis:

spring:
  redis:
    host: 127.0.0.1
    password: redispassword
    port: 6379

如果对连接池有特殊需求可以继续细化配置 pool: xxx

在这里,我们手动写一个工具类 RedisUtil.java,方便使用

@Component
public class RedisUtil {

    private final StringRedisTemplate redisTemplate;

    /**
     * 一周有多少秒
     */
    private static final long WEEK_SECONDS = 7 * 24 * 60 * 60;

    @Autowired
    public RedisUtil(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 将 key,value 存放到redis数据库中,默认设置过期时间为一周
     *
     * @param key
     * @param value
     */
    public void set(String key, String prefix, Object value) {
        redisTemplate.opsForValue().set(mergeKey(key, prefix), JSONObject.toJSONString(value), WEEK_SECONDS, TimeUnit.SECONDS);
    }

    /**
     * 将 key,value 存放到redis数据库中,设置过期时间单位是秒
     *
     * @param key
     * @param value
     * @param expireTime
     */
    public void set(String key, String prefix, Object value, long expireTime) {
        redisTemplate.opsForValue().set(mergeKey(key, prefix), JSONObject.toJSONString(value), expireTime, TimeUnit.SECONDS);
    }

    /**
     * 将 key,value 存放到redis数据库中,设置过期时间单位是秒
     *
     * @param key
     * @param value
     * @param expireTime
     */
    public void set(String key, String prefix, String value, long expireTime) {
        redisTemplate.opsForValue().set(mergeKey(key, prefix), value, expireTime, TimeUnit.SECONDS);
    }

    /**
     * 判断 key 是否在 redis 数据库中
     *
     * @param key
     * @return
     */
    public boolean exists(final String key, String prefix) {
        return redisTemplate.hasKey(mergeKey(key, prefix));
    }

    /**
     * 获取与 key 对应的对象
     *
     * @param key
     * @param clazz 目标对象类型
     * @param <T>
     * @return
     */
    public <T> T get(String key, String prefix, Class<T> clazz) {
        String s = get(key, prefix);
        if (s == null) {
            return null;
        }
        return JSONObject.parseObject(s, clazz);
    }

    /**
     * 获取 key 对应的字符串
     *
     * @param key
     * @return
     */
    public String get(String key, String prefix) {
        return redisTemplate.opsForValue().get(mergeKey(key, prefix));
    }

    /**
     * 删除 key 对应的 value
     *
     * @param key
     */
    public void delete(String key, String prefix) {
        redisTemplate.delete(mergeKey(key, prefix));
    }

    /**
     * merge Key
     */
    private String mergeKey(String key, String prefix) {
        if (Strings.isNullOrEmpty(prefix)) return key;
        return prefix + ":" + key;
    }
}

登陆时,用户账号和密码校验成功后,存储用户的Token:

String token = UUID.randomUUID().toString().replaceAll("-", "");
// Redis : DB.Sys -> Id:No:RoleId
redisUtil.set(token, RedisPrefixKeyEnum.Sys.toString(), emp.getId() + ":" + emp.getNo() + ":" + emp.getRoleId(), expireTime);

利用Java随机的 UUID 做为用户的 Token,并用作 redis 的 key 存进 redis 中,value 是(用户ID:用户编号:权限ID)格式。打开 redis-desktop-manager 看到信息已经保存成功:


当用户再次操作时,就可以从 HttpServletRequest 请求的头部 获取 Token ,并 Redis 查询实现鉴权了,鉴权部分如下:

@Around(value = "doAuthorize(authorize)", argNames = "pjp,authorize")
public Object deBefore(ProceedingJoinPoint pjp, Authorize authorize) throws Throwable {
  ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  HttpServletRequest request = attributes.getRequest();
  // 获取token
  String token = request.getHeader("token");
  // 获取注解
  String[] codes = authorize.value();

  // 未登录
  if (Strings.isNullOrEmpty(token) || !redisUtil.exists(token, RedisPrefixKeyEnum.Sys.toString()))
    return ApiRes.unAuthorized();

  if (codes.length == 0) {
    // 注解为空,登陆即可
    return pjp.proceed();
  } else {
    String[] arr = redisUtil.get(token, RedisPrefixKeyEnum.Sys.toString()).split(":");
    int roleId = Integer.valueOf(arr[2]);
    // roleId 的所有权限
    List<RolePermission> powers = rolePermissionMapper.selectList(
        new QueryWrapper<RolePermission>().lambda()
            .eq(RolePermission::getRoleId, roleId)
    );

    for (String code : codes) {
      for (RolePermission power : powers) {
        if (code.equals(power.getPermissionCode())) {
          // 身份匹配成功
          return pjp.proceed();
        }
      }
    }

    return ApiRes.unAuthorized("权限不足:" + Arrays.toString(codes));
  }
}

(这里使用了 Spring Aop(aspect) 自定义注解权限控制,AOP相关内容会在将来的文章中会讲到)