使用JWT 实现完整登录鉴权

关于什么是JWT,本文就不多叙述,如果不太熟悉的话,可以查阅其他文献进行学习。

本文主要实现了:登录、登出、横向鉴权、Token的有效期管理,并添加了Token黑名单。这些应该够满足一些简单项目针对Token作用的需求。如需满足大型项目,可能还需设计其他模型。

说明:本文仅供学习参考,如有异议,欢迎指出。

关键字:JWT,鉴权。

1. 登录

1.1 颁发Token

登录时,根据自己的业务进行必要的账号验证。验证通过后,颁发Token。这里从Redis中取,可以避免多处登录时出现互斥的情况,后续开发可以实现单点登录等其他功能。如果要实现多端只能同时保持一个客户端登录,则可以直接生成新的Token,让缓存中Redis失效,强迫其他端的Token失效。

这里,我将用户的userId和userName存入了Token中。方便后面的横向鉴权。

String token = JwtUtil.generateToken(String.valueOf(selectConsumerDo.getId()), consumerDo.getUsername());

在生成Token时,首先想到Token的有效期。一般设置Token有效期为30天。

/**
 * token过期时间
 * 单位:天
 */
public static final Integer EXPIRY_DATE = 30;

1.2 有效期

但如何实现Token在特定时间后过期呢?

这里就借助于Redis实现了。可以将颁发的Token存入Redis缓存,并将过期时间设置为我们所需要的时间。
在颁发Token时,先检查Redis中是否存在有效的Token,有则直接颁发,并延长Token有效期。

Object redisToken = redisUtil.get(JwtTokenConstant.TOKEN_REDIS_KEY + req.getUsername());
if (!StringUtils.isEmpty(redisToken) && !"null".equals(redisToken.toString())) {
  tokenResp.setToken(String.valueOf(redisToken));
  resultResp.setData(tokenResp);

  // 将Token存入Redis,延长Token有效期
  redisUtil.set(JwtTokenConstant.TOKEN_REDIS_KEY + req.getUsername(), String.valueOf(redisToken), 12L, TimeUnit.HOURS);
  LOGGER.debug("CommentServiceImpl.login end, resultResp = [{}]", resultResp);
  return resultResp;
}

在鉴权时,当Redis里的Token失效时,我们即可判断用户当前Token 过期了。

// 取出Redis中的Token,并进行比较
Object redisToken = redisUtil.get(JwtTokenConstant.TOKEN_REDIS_KEY + userName);
if (!token.equals(redisToken)) {
  errorToken(servletResponse);
  return;
}

我这里用户的userName是唯一的,可根据自己的业务更换其他的key。

1.3 延长有效期

我将Token保存入Redis并设置了有效期为12h,但用户可能在连续使用12h后,被判断Token过期。
这里就需要在每次判断Token有效后,延长Redis中Tokne的有效期。这样可实现当在12h内无操作,再让Token过期。

redisUtil.set(JwtTokenConstant.TOKEN_REDIS_KEY + userName, token, 12L, TimeUnit.HOURS);

1.4 黑名单

Redis中过期的Token,其实这个Token还并未失效(Token的真实有效期为30天)。为了防止Redis同步错误,异常等情况,这里,我们可以添加一个黑名单,来管理这些过期的Token,加一个双重保证。

redisUtil.set(JwtTokenConstant.TOKEN_BLACKLIST_CACHE_PREFIX + token, token, 31L, TimeUnit.DAYS);

2. 鉴权

2.1 鉴定Token是否有效

我实现了一个继承FilterApiPermissionFilter的实现类,用于过滤所有需要进行鉴权的接口。

基本思路和步骤是:

  1. 跳过不需要鉴权的接口
  2. 判断Token是否有效
  3. 判断Token是否在有效期(借助Redis)
  4. 判断Token是否在黑名单中

2.1.1 跳过不需要鉴权的接口

//跳过不需要验证的路径
if (urlMatches(request)) {
    filterChain.doFilter(servletRequest, servletResponse);
    return;
}

/**
 * url匹配方法
 *
 * @param request http 请求
 * @return 是否成功匹配
 */
private boolean urlMatches(HttpServletRequest request) {
    if (CollectionUtils.isEmpty(whiteUrlList)) {
        return true;
    }

    return whiteUrlList.stream().anyMatch(url -> {
        AntPathRequestMatcher matcher = new AntPathRequestMatcher(url);
        return matcher.matches(request);
    });
}

2.1.2 判断Token是否有效

try {
    parseToken = JwtUtil.parseToken(token);
}
catch (JWTVerificationException e) {
  errorToken(servletResponse);
  return;
}

2.1.3 判断Token是否在有效期(借助Redis)

// 取出Redis中的Token,并进行比较
Object redisToken = redisUtil.get(JwtTokenConstant.TOKEN_REDIS_KEY + userName);
if (!token.equals(redisToken)) {
  errorToken(servletResponse);
  return;
}

2.1.4 判断Token是否在黑名单中

Object blackToken = redisUtil.get(JwtTokenConstant.TOKEN_BLACKLIST_CACHE_PREFIX + token);
if (token.equals(blackToken)) {
    redisUtil.expire(JwtTokenConstant.TOKEN_REDIS_KEY + userName, 0L, TimeUnit.SECONDS);
    errorToken(servletResponse);
    return;
}

经过以上,就可以实现针对接口进行鉴权。

2.2 横向鉴权

在实现业务过程中,有些接口,是需要进行横向鉴权的。例如某些信息,根据userId获取。这就需要用户只能获取自己userID的

信息。那如何判断用户上传的userId就是自己的呢?即如何进行横向鉴权呢?

就可以借助Token实现。

在上文中,我在Token中放入了用户的userId。这里就可以借助这个userId来实现。

以下为我实现横向鉴权的方法:

@Override
public void checkPermission(Long userId, HttpServletRequest request) {
    try {
        LOGGER.debug("PermissionUtilImpl.checkPermission, userId = [{}]", userId);
        String token = request.getHeader(JwtTokenConstant.AUTHORIZATION);
        // 解密Token
        Map<String, String> tokenMap = JwtUtil.parseToken(token);
        long id = Long.parseLong(tokenMap.get(JwtTokenConstant.USER_GUID));
        if (id == userId) {
            LOGGER.debug("PermissionUtilImpl.checkPermission success.");
            return;
        }
        LOGGER.error("PermissionUtilImpl.checkPermission failure.");
        throw new MusicException(MusicErrorCode.NO_PERMISSION, "NO permission!");
    } catch (JWTVerificationException | NumberFormatException e) {
        LOGGER.error("PermissionUtilImpl.checkPermission failure.");
        throw new MusicException(MusicErrorCode.NO_PERMISSION, "NO permission!");
    }
}

3. 登出

在用户登出后,要立马让Token失效。

首先就是让保存在Redis中的Token过期。其次就是在登出时,将Token加入黑名单。

String token = request.getHeader(JwtTokenConstant.AUTHORIZATION);
Map<String, String> parseToken = JwtUtil.parseToken(token);
// 让Redis中的Tokne立马失效
redisUtil.expire(JwtTokenConstant.TOKEN_REDIS_KEY + parseToken.get(JwtTokenConstant.USER_NAME), 0L, TimeUnit.SECONDS);

// 将Token加入黑名单。过期时间为31,大于Token的有效期30天。
redisUtil.set(JwtTokenConstant.TOKEN_BLACKLIST_CACHE_PREFIX + token, token, 31L, TimeUnit.DAYS);

以上就是我实现的全部过程和思路。

这里我没有贴上RedisUtil和JwtUtil的代码,可在我项目中获取。

项目还实现了其他功能,比如定时任务;利用对称加密算法对用户的密码等信息进行加密。如果有兴趣的话,后续再做讲解吧。

完整项目可从以下仓库获取:music-client-backend

参考

1.[SpringBoot实现JWT认证]: https://gitee.com/rayfoo/SpringBoot-JWT
2.[开发SpringBoot+Jwt+Vue的前后端分离后台管理系统VueAdmin-后端笔记]: https://www.markerhub.com/post/77

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,561评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,218评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,162评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,470评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,550评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,806评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,951评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,712评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,166评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,510评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,643评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,306评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,930评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,745评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,983评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,351评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,509评论 2 348

推荐阅读更多精彩内容