基于Redis的Token认证机制

Redis数据库设计

/**
* redis key 前缀
*/
public static final String REDIS_KEY_PREFIX = "easylive:";

/**
* 验证码key
*/
public static final String REDIS_KEY_CHECK_CODE = REDIS_KEY_PREFIX + "check_code:";

/**
* Redis key token web
*/
public static final String REDIS_KEY_TOKEN_WEB = REDIS_KEY_PREFIX + "token:web:";

public static final String TOKEN_WEB = "token";

public static final String TOKEN_ADMIN = "adminToken";

public static final String REDIS_KEY_TOKEN_ADMIN = REDIS_KEY_PREFIX + "token:admin:";

  • Redis 数据库存储如下:
    在这里插入图片描述

easylive:token:admin+token(UUID)

  • 这里在redis前缀组成的部分键后面加上token,一方面是为了防止存储内容存储。暂且把redis理解为一个Map,需要根据键获取值。需要保证键的唯一性。恰好在前后端身份校验时,也需要这样的机制保证用户的唯一性。
  • easylive:token:admin+token作为键,值 为 account账户

在这里插入图片描述

easylive:token:web+token(UUID)

  1. easylive:token:web+token作为键,值为 TokenUserInfoDto
{"@class":"com.easylive.entity.dto.TokenUserInfoDto",
 "userId":null,
 "nickName":null,
 "avatar":null,
 "expireAt":1730826856010,
 "token":"5b09d79f-b5c5-4a37-a4b4-3bba8fbad0ec",
 "fansCount":null,
 "currentCoinCount":null,
 "focusCount":null}
  1. TokenUserInfoDto实体
@JsonIgnoreProperties(ignoreUnknown = true)
public class TokenUserInfoDto implements Serializable {
    private static final long serialVersionUID = -6910208948981307451L;
    private String userId;
    private String nickName;
    private String avatar;
    private Long expireAt;
    private String token;
}

登录

  1. admin 端登录
@RequestMapping(value = "/login")
public ResponseVO login(HttpServletRequest request,
                        HttpServletResponse response,
                        @NotEmpty String account,
                        @NotEmpty String password, @NotEmpty String checkCode,
                        @NotEmpty String checkCodeKey) {
    try {
        if (!checkCode.equalsIgnoreCase((String) redisUtils.get(Constants.REDIS_KEY_CHECK_CODE + checkCodeKey))) {
            throw new BusinessException("图片验证码不正确");
        }
        if (!account.equals(appConfig.getAdminAccount()) || !password.equals(StringTools.encodeByMD5(appConfig.getAdminPassword()))) {
            throw new BusinessException("账号或者密码错误");
        }
        String token = redisComponent.saveTokenInfo4Admin(account);
        // 把token 放入cookie 中
        saveToken2Cookie(response, token);
        return getSuccessResponseVO(account);
    } finally {
        // 去掉老的admintoken
        redisUtils.delete(Constants.REDIS_KEY_CHECK_CODE + checkCodeKey);

        Cookie[] cookies = request.getCookies();
        String token = null;
        for (Cookie cookie : cookies) {
            if (cookie.getName().equals(Constants.TOKEN_ADMIN)) {
                token = cookie.getValue();
            }
        }
        if (!StringTools.isEmpty(token)) {
            redisComponent.cleanToken4Admin(token);
        }
    }
}

public String saveTokenInfo4Admin(String account) {
    String token = UUID.randomUUID().toString();
    redisUtils.setex(Constants.REDIS_KEY_TOKEN_ADMIN + token, account, Constants.REDIS_KEY_EXPIRES_DAY);
    return token;
}

public void saveToken2Cookie(HttpServletResponse response, String token) {
    Cookie cookie = new Cookie(Constants.TOKEN_ADMIN, token);
    cookie.setMaxAge(-1);
    cookie.setPath("/");
    response.addCookie(cookie);
}

总结

  1. 把用户信息存入 redis中,并返回对应的 token, 这个 token + 对应的前缀就是 redis 中对应的 键。后期可以通过这个键找到值。从而进行身份校验。
  2. 返回的 token 相当于是 用户信息的加密映射。存入 cookie

Cookie cookie = new Cookie(Constants.TOKEN_ADMIN, token); 草草画个图🤡

在这里插入图片描述

前端发起请求时进行校验。每次发请求时。后端通过全局请求拦截器。取出 token ,这里有两种取法:

  1. 前端在每次的请求头中,带上 token,后端通过 request.getHeader(对应的前缀组合成的键)
  2. 遍历 Cookie数组,获取其中的对应 键的值。

补充

  • 补充一些redis的工具类
  1. RedisComponent 封装了对RedisUtils 的操作
package com.easylive.component;

import com.easylive.entity.constants.Constants;
import com.easylive.entity.dto.TokenUserInfoDto;
import com.easylive.redis.RedisUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.UUID;

/**
 * 功能:
 * 日期:2024/11/3 下午4:48
 */
@Component
public class RedisComponent {
    @Resource
    private RedisUtils redisUtils;

    /**
     * 保存验证码
     * @param code
     * @return
     */
    public String saveCheckCode(String code) {
        String checkCode = UUID.randomUUID().toString();
        redisUtils.setex(Constants.REDIS_KEY_CHECK_CODE + checkCode, code, Constants.REDIS_KEY_EXPRIES_ONE_MINUTE * 10);
        return checkCode;
    }

    /**
     * 获取验证码
     * @param checkCodeKey
     * @return
     */
    public String getCheckCode(String checkCodeKey) {
        return (String) redisUtils.get(Constants.REDIS_KEY_CHECK_CODE + checkCodeKey);
    }

    /**
     * 清除验证码
     * @param checkCodeKey
     */
    public void clearCheckCode(String checkCodeKey) {
        redisUtils.delete(Constants.REDIS_KEY_CHECK_CODE + checkCodeKey);
    }

    /**
     * 保存token信息
     *
     * @param tokenUserInfoDto
     */
    public void saveTokenInfo(TokenUserInfoDto tokenUserInfoDto) {
        String token = UUID.randomUUID().toString();
        tokenUserInfoDto.setExpireAt(System.currentTimeMillis() + Constants.REDIS_KEY_EXPRIES_ONE_DAY * 7);
        tokenUserInfoDto.setToken(token);
        redisUtils.setex(Constants.REDIS_KEY_TOKEN_WEB + token, tokenUserInfoDto, Constants.REDIS_KEY_EXPRIES_ONE_DAY * 7);
    }

    /**
     * 清除token
     *
     * @param token
     */
    public void cleanToken(String token) {
        redisUtils.delete(Constants.REDIS_KEY_TOKEN_WEB + token);

    }

    /**
     * 获取admintoken信息
     * @param token
     */
    public void cleanToken4Admin(String token) {
        redisUtils.delete(Constants.REDIS_KEY_TOKEN_ADMIN + token);

    }

    /**
     * 获取token信息
     * @param token
     * @return
     */
    public TokenUserInfoDto getTokenUserInfo(String token) {
        return (TokenUserInfoDto) redisUtils.get(Constants.REDIS_KEY_TOKEN_WEB + token);
    }

    /**
     * 更新token信息
     * @param account
     * @return
     */
    public String saveTokenInfo4Admin(String account) {
        String token = UUID.randomUUID().toString();
        redisUtils.setex(Constants.REDIS_KEY_TOKEN_ADMIN + token, account, Constants.REDIS_KEY_EXPRIES_ONE_DAY);
        return token;
    }

    /**
     * 清除admintoken
     * @param token
     * @return
     */
    public String getTokenInfo4Admin(String token) {
        return (String) redisUtils.get(Constants.REDIS_KEY_TOKEN_ADMIN + token);
    }
}
  1. RedisUtils.java
package com.easylive.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component("redisUtils")
public class RedisUtils<V> {

    @Resource
    private RedisTemplate<String, V> redisTemplate;

    private static final Logger logger = LoggerFactory.getLogger(RedisUtils.class);

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    public void delete(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }

    public V get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, V value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            logger.error("设置redisKey:{},value:{}失败", key, value);
            return false;
        }
    }

    public boolean keyExists(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean setex(String key, V value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.MILLISECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            logger.error("设置redisKey:{},value:{}失败", key, value);
            return false;
        }
    }

    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.MILLISECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public List<V> getQueueList(String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    public boolean lpush(String key, V value, Long time) {
        try {
            redisTemplate.opsForList().leftPush(key, value);
            if (time != null && time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public long remove(String key, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, 1, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    public boolean lpushAll(String key, List<V> values, long time) {
        try {
            redisTemplate.opsForList().leftPushAll(key, values);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public V rpop(String key) {
        try {
            return redisTemplate.opsForList().rightPop(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public Long increment(String key) {
        Long count = redisTemplate.opsForValue().increment(key, 1);
        return count;
    }

    public Long incrementex(String key, long milliseconds) {
        Long count = redisTemplate.opsForValue().increment(key, 1);
        if (count == 1) {
            //设置过期时间1天
            expire(key, milliseconds);
        }
        return count;
    }

    public Long decrement(String key) {
        Long count = redisTemplate.opsForValue().increment(key, -1);
        if (count <= 0) {
            redisTemplate.delete(key);
        }
        logger.info("key:{},减少数量{}", key, count);
        return count;
    }

    public Set<String> getByKeyPrefix(String keyPrifix) {
        Set<String> keyList = redisTemplate.keys(keyPrifix + "*");
        return keyList;
    }

    public Map<String, V> getBatch(String keyPrifix) {
        Set<String> keySet = redisTemplate.keys(keyPrifix + "*");
        List<String> keyList = new ArrayList<>(keySet);
        List<V> keyValueList = redisTemplate.opsForValue().multiGet(keyList);
        Map<String, V> resultMap = keyList.stream().collect(Collectors.toMap(key -> key, value -> keyValueList.get(keyList.indexOf(value))));
        return resultMap;
    }

    public void zaddCount(String key, V v) {
        redisTemplate.opsForZSet().incrementScore(key, v, 1);
    }

    public List<V> getZSetList(String key, Integer count) {
        Set<V> topElements = redisTemplate.opsForZSet().reverseRange(key, 0, count);
        List<V> list = new ArrayList<>(topElements);
        return list;
    }

}

基于Spring Hibernate和RedisToken鉴权是一种常见的身份验证和授权机制。该机制使用Token作为用户认证凭据,并通过Redis存储和管理Token的有效期和访问权限。 实现该机制的步骤如下: 1. 用户登录时,后台验证用户名和密码的正确性,并生成一个唯一的Token。 2. 将Token存储在Redis中,并设置Token的有效期。可以使用Key-Value格式将Token和用户相关信息存储在Redis中。 3. 将Token作为响应结果返回给客户端。 4. 客户端在后续的请求中,将Token作为请求参数或在请求头中携带。 5. 后台每次接收到请求时,首先从Redis中获取Token,并验证Token的有效性。 6. 如果Token有效,后台可以根据Token中的用户信息进行进一步的授权检查,以确定用户是否有权访问请求的资源。 7. 如果Token无效或过期,后台将返回相应的错误码给客户端,要求重新登录或刷新Token。 通过使用Spring Hibernate框架,可以方便地实现用户登录认证Token的生成、存储和验证。同时,利用Redis作为数据存储和缓存,可以提高系统的性能和扩展性。 基于Spring Hibernate和RedisToken鉴权机制具有以下优点: 1. 无状态:Token鉴权不需要在后台存储用户的认证信息,有效减少了服务器的负担,实现了无状态的API设计。 2. 可扩展:由于Token保存在Redis中,可以方便地增加、修改和删除Token的有效期和权限,使系统具备更好的扩展性。 3. 安全性:Token可以使用较长的随机字符串,并通过SSL等方式进行加密传输,提高系统的安全性。 总之,基于Spring Hibernate和RedisToken鉴权是一种高效、安全和可扩展的身份验证和授权机制,适用于各种Web应用和API接口的开发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值