单体服务系统认证

上一节讲了如何使用JWT生成令牌,下面说说单体服务认证基本流程。

认证流程

流程图:
在这里插入图片描述
流程描述:

  1. 用户输入登录信息,客户端(Web/APP等)发起登录请求;
  2. 服务端校验该用户是否有效,用户有效则返回令牌信息,用户无效则返回用户不存在;
  3. 客户端收到令牌信息后,将其存储到客户端(如:cookie中);
  4. 客户端端请求服务器资源数据,在有头信息中携带令牌信息;
  5. 服务器拦截器校验该令牌是否有效,有效的话则返回请求的资源数据;
  6. 若令牌认证失败401,则携带刷新令牌,请求刷新令牌的接口;
  7. 服务端校验该刷新令牌是否有效,有效的话则返回新的令牌信息给客户端;
  8. 无效的话,则返回刷新令牌过期402给客户端,客户端跳转到用户登录页;
  9. 用户重新登录。

如何实现

  1. 集成上一节令牌SDK
<dependency>
     <groupId>com.angel.ocean</groupId>
     <artifactId>ocean-auth-core</artifactId>
     <version>1.0.0</version>
 </dependency>
  1. 定义拦截器 AuthFilter,去校验令牌是否有效,认证失败则返回401
package com.angel.ocean.filter;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.angel.ocean.common.ApiResult;
import com.angel.ocean.constant.NoAuthUrl;
import com.angel.ocean.constant.ResultCode;
import com.angel.ocean.constant.redis.RedisCacheKey;
import com.angel.ocean.token.TokenUtil;
import com.angel.ocean.util.SecurityUtil;
import com.angel.ocean.util.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.FilterConfig;
import java.io.IOException;

/**
 1. 认证Filter
 */
@Slf4j
public class AuthFilter implements Filter {

    @Resource
    private RedissonClient redissonClient;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        ApplicationContext context = WebApplicationContextUtils
                .getWebApplicationContext(filterConfig.getServletContext());
        this.redissonClient = context.getBean(RedissonClient.class);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        // 放开不需要认证的API
        String apiUrl = ServletUtil.getRequest().getRequestURI();
        if(NoAuthUrl.contains(apiUrl)) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }

        // Token有效性校验
        String token = SecurityUtil.getToken();
        if(StrUtil.isEmpty(token)) {
            // 未携带令牌,认证失败返回401
            responseWrite(servletResponse, ApiResult.error(ResultCode.UNAUTHORIZED));
            return;
        }

        if((TokenUtil.isExpired(token))) {
            // 携带令牌但是该令牌已过期,认证失败返回401
            responseWrite(servletResponse, ApiResult.error(ResultCode.UNAUTHORIZED));
            return;
        }

        Long userId = SecurityUtil.getUserId();
        RBucket<String> accessTokenCache = redissonClient.getBucket(RedisCacheKey.accessTokenKey(userId));
        if(!accessTokenCache.isExists()) {
            // 携带令牌,用户已退出登录导致令牌失效,认证失败返回401
            responseWrite(servletResponse, ApiResult.error(ResultCode.UNAUTHORIZED));
            return;
        }

        String oldToken = accessTokenCache.get();
        if(!oldToken.equals(token)) {
            // 该用户已经有了新令牌,老令牌过期了,使用老令牌认证,认证失败返回401
            responseWrite(servletResponse, ApiResult.error(ResultCode.UNAUTHORIZED));
            return;
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }

    public void responseWrite(ServletResponse response, Object data) {
        try {
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json; charset=utf-8");
            response.getWriter().write(JSON.toJSONString(data));
        } catch (IOException e) {
            log.error("responseWrite() error:", e);
        }
    }
}
  1. 定义与认证和用户有关的相关接口
  • 登录接口
  • 获取用户信息的接口
  • 刷新令牌的接口
  • 用户退出接口
package com.angel.ocean.controller;

import com.angel.ocean.common.ApiResult;
import com.angel.ocean.domain.dto.LoginDTO;
import com.angel.ocean.service.AuthService;
import com.angel.ocean.token.model.TokenInfo;
import com.angel.ocean.token.model.UserInfo;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;

/**
 * 鉴权认证相关
 */
@RestController
@RequestMapping("user")
public class AuthController {

    @Resource
    private AuthService authService;

    /**
     * 用户登录
     * @param dto
     * @return
     */
    @PostMapping("login")
    public ApiResult<TokenInfo> login(@RequestBody LoginDTO dto) {
        return authService.login(dto);
    }

    /**
     * 获取用户信息
     * @return
     */
    @GetMapping("info")
    public ApiResult<UserInfo> userInfo() {
        return authService.userInfo();
    }

    /**
     * 刷新token
     * @param refreshToken
     * @return
     */
    @PostMapping("token/refresh")
    public ApiResult<TokenInfo> refreshToken(@RequestBody String refreshToken) {
        return authService.refreshToken(refreshToken);
    }

    /**
     * 用户退出
     * @return
     */
    @PostMapping("logout")
    public ApiResult logout() {
        return authService.logout();
    }
}

业务实现

package com.angel.ocean.service.impl;

import cn.hutool.core.util.StrUtil;
import com.angel.ocean.common.ApiResult;
import com.angel.ocean.constant.ResultCode;
import com.angel.ocean.constant.redis.RedisCacheKey;
import com.angel.ocean.domain.dto.LoginDTO;
import com.angel.ocean.domain.entity.SysUser;
import com.angel.ocean.mapper.SysUserMapper;
import com.angel.ocean.token.TokenUtil;
import com.angel.ocean.token.model.TokenInfo;
import com.angel.ocean.token.model.UserInfo;
import com.angel.ocean.util.SecurityUtil;
import com.angel.ocean.service.AuthService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;

/**
 1. 鉴权认证相关
 */
@Service
public class AuthServiceImpl implements AuthService {

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private SysUserMapper sysUserMapper;

    @Override
    public ApiResult<TokenInfo> login(LoginDTO dto) {

        // 参数非空校验
        if(StrUtil.isEmpty(dto.getUsername()) || StrUtil.isEmpty(dto.getPassword())) {
            return ApiResult.error(ResultCode.PARAM_ERROR);
        }

        QueryWrapper<SysUser> query = new QueryWrapper<>();
        query.eq("username", dto.getUsername());
        query.eq("password", dto.getPassword());
        SysUser sysUser = sysUserMapper.selectOne(query);

        if(null == sysUser) {
            return ApiResult.error(ResultCode.USER_NOT_EXIST);
        }

        UserInfo userInfo = new UserInfo();
        userInfo.setName(sysUser.getUsername());
        userInfo.setUid(sysUser.getId());

        TokenInfo tokenInfo = TokenUtil.generateToken(userInfo);

        setTokenCache(sysUser.getId(), tokenInfo);

        return ApiResult.success(tokenInfo);
    }

    @Override
    public ApiResult<UserInfo> userInfo() {
        UserInfo userInfo = SecurityUtil.getLoginUser();
        return ApiResult.success(userInfo);
    }

    @Override
    public ApiResult<TokenInfo> refreshToken(String refreshToken) {

        // 刷新令牌有效性校验
        if(TokenUtil.isExpired(refreshToken)) {
            return ApiResult.error(ResultCode.AUTHORIZED_EXPIRED);
        }

        TokenInfo tokenInfo = TokenUtil.refreshToken(refreshToken);
        Long userId = TokenUtil.getUserInfoByToken(refreshToken).getUid();

        setTokenCache(userId, tokenInfo);

        return ApiResult.success(tokenInfo);
    }

    @Override
    public ApiResult logout() {
        Long userId = SecurityUtil.getUserId();
        if(null != userId) {
            removeTokenCache(userId);
        }
        return ApiResult.success();
    }

    /**
     * 设置令牌缓存
     * @param userId
     * @param tokenInfo
     */
    private void setTokenCache(Long userId, TokenInfo tokenInfo) {

        RBucket<String> accessTokenCache = redissonClient.getBucket(RedisCacheKey.accessTokenKey(userId));
        accessTokenCache.set(tokenInfo.getAccessToken());
        accessTokenCache.expireAt(new Date(tokenInfo.getAccessTokenExpireIn() * 1000));

        RBucket<String> refreshTokenCache = redissonClient.getBucket(RedisCacheKey.refreshTokenKey(userId));
        refreshTokenCache.set(tokenInfo.getRefreshToken());
        refreshTokenCache.expireAt(new Date(tokenInfo.getRefreshTokenExpireIn() * 1000));
    }

    /**
     * 删除令牌缓存
     * @param userId
     */
    private void removeTokenCache(Long userId) {
        RBucket<String> accessTokenCache = redissonClient.getBucket(RedisCacheKey.accessTokenKey(userId));
        accessTokenCache.delete();

        RBucket<String> refreshTokenCache = redissonClient.getBucket(RedisCacheKey.refreshTokenKey(userId));
        refreshTokenCache.delete();
    }
}
  1. 用户信息的工具类SecurityUtil,给其他业务使用
package com.angel.ocean.util;

import com.angel.ocean.token.TokenUtil;
import com.angel.ocean.token.model.UserInfo;

public class SecurityUtil {

    private SecurityUtil() {
    }

    public static String getUsername() {
        UserInfo userInfo = getLoginUser();
        String username = userInfo.getName();
        return ServletUtil.urlDecode(username);
    }

    public static Long getUserId() {
        UserInfo userInfo = getLoginUser();
        return userInfo.getUid();
    }

    public static UserInfo getLoginUser() {
        UserInfo userInfo = TokenUtil.getUserInfoByToken(getToken());
        return userInfo;
    }

    public static String getToken() {
        String token = ServletUtil.getRequest().getHeader("Authorization");
        return token;
    }
}
package com.angel.ocean.util;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;

public class ServletUtil {

    private ServletUtil() {
    }

    public static String getParameter(String name) {
        return getRequest().getParameter(name);
    }

    public static String getParameter(String name, String defaultValue) {
        return Convert.toStr(getRequest().getParameter(name), defaultValue);
    }

    public static Integer getParameterToInt(String name) {
        return Convert.toInt(getRequest().getParameter(name));
    }

    public static Integer getParameterToInt(String name, Integer defaultValue) {
        return Convert.toInt(getRequest().getParameter(name), defaultValue);
    }

    public static HttpServletRequest getRequest() {
        try {
            return getRequestAttributes().getRequest();
        } catch (Exception var1) {
            return null;
        }
    }

    public static HttpServletResponse getResponse() {
        try {
            return getRequestAttributes().getResponse();
        } catch (Exception var1) {
            return null;
        }
    }

    public static HttpSession getSession() {
        return getRequest().getSession();
    }

    public static ServletRequestAttributes getRequestAttributes() {
        try {
            RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
            return (ServletRequestAttributes)attributes;
        } catch (Exception var1) {
            return null;
        }
    }

    public static Map<String, String> getHeaders(HttpServletRequest request) {
        Map<String, String> map = new LinkedHashMap();
        Enumeration<String> enumeration = request.getHeaderNames();
        if (enumeration != null) {
            while(enumeration.hasMoreElements()) {
                String key = (String)enumeration.nextElement();
                String value = request.getHeader(key);
                map.put(key, value);
            }
        }

        return map;
    }

    public static String renderString(HttpServletResponse response, String string) {
        try {
            response.setStatus(200);
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(string);
        } catch (IOException var3) {
            var3.printStackTrace();
        }

        return null;
    }

    public static boolean isAjaxRequest(HttpServletRequest request) {
        String accept = request.getHeader("accept");
        if (accept != null && accept.indexOf("application/json") != -1) {
            return true;
        } else {
            String xRequestedWith = request.getHeader("X-Requested-With");
            if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1) {
                return true;
            } else {
                String uri = request.getRequestURI();
                if (StrUtil.containsAnyIgnoreCase(uri, new String[]{".json", ".xml"})) {
                    return true;
                } else {
                    String ajax = request.getParameter("__ajax");
                    return StrUtil.containsAnyIgnoreCase(ajax, new String[]{"json", "xml"});
                }
            }
        }
    }

    public static String urlDecode(String str) {
        try {
            return URLDecoder.decode(str, "UTF-8");
        } catch (UnsupportedEncodingException var2) {
            return "";
        }
    }
}

运行演示

  1. 登录接口

注意:由于是演示使用,用户名字是明文(不安全),在项目开发过程中,一定要先在客户端加密,然后服务区解密。
在这里插入图片描述

  1. 刷新令牌接口

在这里插入图片描述

  1. 获取用户信息接口,携带有效令牌

在这里插入图片描述

  1. 获取用户信息, 携带无效令牌

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/735972.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

批量重命名神器揭秘:一键实现文件夹随机命名,自定义长度轻松搞定!

在数字化时代&#xff0c;我们经常需要管理大量的文件夹&#xff0c;尤其是对于那些需要频繁更改或整理的文件来说&#xff0c;给它们进行批量重命名可以大大提高工作效率。然而&#xff0c;传统的重命名方法既繁琐又耗时&#xff0c;无法满足高效工作的需求。今天&#xff0c;…

【05】数据模型和工作量证明-简单的区块链模型

1. 简单的区块链模型 每一个区块都保存了签一个区块的hash值,这样多个区块就可以形成一个有序的后向连接的列表。 如果,区块链中的某1区块的数据被篡改,那么该区块的hash值会被改变,那么致使由该“错误区块”引导的子链失效(不被认可),从而从结构上保证了数据的可靠性、…

视频AI分析定时任务思路解析

序言&#xff1a; 最近项目中用到视频ai分析&#xff0c;由于sdk涉及保密&#xff0c;不便透露&#xff0c;仅对定时任务分析的思路作出分享&#xff0c;仅供参考。 1、定时任务 由于ai服务器的性能上限&#xff0c;只能同时对64个rtsp流分析一种算法&#xff0c;或者对8个rts…

累了就坐下来喝杯茶 然后继续前行

前言&#xff1a; 今天是情人节就不发技术文章了 先祝愿各位有情人总成眷属。也包括我&#xff0c;哈哈哈 今天要分享的是一个diy制作的教程 因为我女朋友不在这边&#xff0c;心中那份思念难耐 所以有感而发 写了这篇diy 教程的文章 效果图&#xff1a; 需要的材料 502胶水…

动手学自然语言处理:解读大模型背后的核心技术

自从 ChatGPT 横空出世以来&#xff0c;自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09; 研究领域就出现了一种消极的声音&#xff0c;认为大模型技术导致 NLP “死了”。在某乎上就有一条热门问答&#xff0c;大家热烈地讨论了这个问题。 有…

▶《强化学习的数学原理》(2024春)_西湖大学赵世钰 Ch5 蒙特卡洛方法【model-based ——> model-free】

PPT 截取必要信息。 课程网站做习题。总体 MOOC 过一遍 1、视频 学堂在线 习题 2、 过 电子书 是否遗漏 【下载&#xff1a;本章 PDF GitHub 页面链接 】 【第二轮 才整理的&#xff0c;忘光了。。。又看了一遍视频】 3、 过 MOOC 习题 看 PDF 迷迷糊糊&#xff0c; 恍恍惚惚。…

STM32 温湿度采集与OLED显示

目录 一、I2C总线通信协议 1、I2C介绍 2、软件I2C和硬件I2C &#xff08;1&#xff09;硬件I2C &#xff08;2&#xff09;软件I2C 差异 二、AHT20温湿度传感器 接口原理介绍 1. 温度测量原理 2. 湿度测量原理 实物引脚 传感器性能 电气特性 三、任务实现 具…

工程师 - Total Phase公司介绍

Total Phase 是一家领先的嵌入式系统工具供应商&#xff0c;可简化各种通信协议的开发和调试。公司提供一系列产品&#xff0c;旨在帮助工程师和开发人员更高效地使用 I2C、SPI、USB 和 CAN 等协议。 关于Total Phase的关键信息&#xff1a; 产品&#xff1a; 协议分析仪&…

day1-web安全基础(米斯特web渗透测试)

day1-web安全基础&#xff08;米斯特web渗透测试&#xff09; 一、搭建网站二、专业术语1.域名2.木马&#xff08;1&#xff09;软件木马&#xff1a;&#xff08;2&#xff09;脚本木马&#xff1a; 3.社工4.IP5.后门6.Poc&#xff08;验证&#xff09;&#xff0c;exp&#x…

2024.06.23 刷题日记

〇、前言 今天重点刷了回溯&#xff0c;以及常见的题目。 46. 全排列 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3…

提示缺少Microsoft Visual C++ 2019 Redistributable Package (x64)(下载)

下载地址&#xff1a;这个是官网下载地址&#xff1a;Microsoft Visual C 2019 Redistributable Package (x64) 步骤&#xff1a; 第一步&#xff1a;点开链接&#xff0c;找到下图所示的东西 第二步&#xff1a;点击保存下载 第三步&#xff1a;双击运行安装 第四步&#xf…

【漏洞复现】AJ-Report开源数据大屏 verification;swagger-ui RCE漏洞

0x01 产品简介 AJ-Report是一个完全开源的B平台&#xff0c;酷炫大屏展示&#xff0c;能随时随地掌控业务动态&#xff0c;让每个决策都有数据支撑。多数据源支持&#xff0c;内置mysql、elasticsearch、kudu等多种驱动&#xff0c;支持自定义数据集省去数据接口开发&#xff…

three.js 基础02 ---光源

1.光源【基础材质不受光照影响】 注&#xff1a; 除MeshBasicMaterial&#xff08;网格基础材质&#xff09;外&#xff0c;设置材质时必须同时设置光源&#xff0c;否则图形出不来 1.1 材质 漫反射 MeshBasicMaterial 高光 MeshLambertMaterial 物理 MeshStandardMa…

【数据结构】链表的大概认识及单链表的实现

目录 一、链表的概念及结构 二、链表的分类 三、单链表的实现 建立链表的节点&#xff1a; 尾插——尾删&#xff1a; 头插——头删&#xff1a; 查找&#xff1a; 指定位置之后删除——插入&#xff1a; 指定位置之前插入——删除指定位置&#xff1a; 销毁链表&am…

浏览器插件利器-allWebPluginV2.0.0.14-bata版发布

allWebPlugin简介 allWebPlugin中间件是一款为用户提供安全、可靠、便捷的浏览器插件服务的中间件产品&#xff0c;致力于将浏览器插件重新应用到所有浏览器。它将现有ActiveX插件直接嵌入浏览器&#xff0c;实现插件加载、界面显示、接口调用、事件回调等。支持谷歌、火狐等浏…

C语言中的字符输入/输出和验证输入

在C语言中&#xff0c;字符输入/输出功能允许程序与用户进行交互&#xff0c;读取用户的输入信息并展示输出结果。同时&#xff0c;验证输入的作用在于确保用户输入的数据符合预期&#xff0c;以提高程序的稳定性和可靠性&#xff0c;防止无效输入引发的错误或异常行为&#xf…

外排序(C语言实现)

前言 本篇博客讲解一下外排序&#xff0c;看这篇排序你的先去看一下&#xff1a;八大经典排序算法-CSDN博客 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;排序_普通young man的博客-CSDN博客 若有问题 评论区见&#x1f4dd; &#x1f3…

二叉树的基础讲解

二叉树在遍历&#xff0c;查找&#xff0c;增删的效率上面都很高&#xff0c;是数据结构中很重要的&#xff0c;下面我们来基础的认识一下。(高级的本人还没学&#xff0c;下面的代码用伪代码或C语言写的)我会从树&#xff0c;树的一些专有名词&#xff0c;树的遍历&#xff0c…

【博客719】时序数据库基石:LSM Tree的增删查改

时序数据库基石&#xff1a;LSM Tree的增删查改 LSM结构 LSM树将任何的对数据操作都转化为对内存中的Memtable的一次插入。Memtable可以使用任意内存数据结构&#xff0c;如HashTable&#xff0c;BTree&#xff0c;SkipList等。对于有事务控制需要的存储系统&#xff0c;需要在…

web安全渗透测试十大常规项(一):web渗透测试之JAVA反序列化

渗透测试之PHP反序列化 1. Java反序列化1.1 Java安全-反序列化-原生序列化类函数1.1.1 原生序列化类函数:1.2 Java安全-SpringBoot框架-泄漏&CVE1. Java反序列化 1、序列化与反序列化 序列化:将内存中的对象压缩成字节流 反序列化:将字节流转化成内存中的对象2、为什么…