使用AI一步一步实现若依(28)

功能28:JWT

功能27:实现用户登录
功能26:新增一个新员工培训页面
功能25:角色管理
功能24:菜单管理
功能23:从后端获取路由/菜单数据
功能22:用户管理
功能21:使用axios发送请求
功能20:使用分页插件
功能19:集成MyBatis-Plus
功能18:创建后端工程
功能17:菜单管理
功能16:角色管理
功能15:用户管理
功能14:使用本地SVG图标库
功能13:侧边栏加入Logo
功能12:折叠/展开侧边栏
功能11:实现面包屑功能
功能10:添加首页菜单项
功能9:退出登录功能
功能8:页面权限控制
功能7:路由全局前置守卫
功能6:动态添加路由记录
功能5:侧边栏菜单动态显示
功能4:首页使用Layout布局
功能3:点击登录按钮实现页面跳转
功能2:静态登录界面
功能1:创建前端项目

前言

JWT(JSON Web Token)是一种基于 Token 的轻量级认证协议,由 RFC 7519 规范定义。其核心思想是:
无状态:服务端无需存储 Token,认证信息通过加密签名直接嵌入 Token 中。
自包含性:Token 本身包含用户身份、权限等关键数据,服务端只需验证签名即可信任内容。
跨域友好:通过 HTTP Header(如 Authorization)传递,天然支持跨域场景。

Token 结构
JWT 由三部分组成,以 . 分隔:

Header.Payload.Signature

Header:声明 Token 类型(typ: "JWT")和签名算法(如 alg: HS256)。
Payload:存放用户身份、权限、过期时间等数据(称为 Claims)。
Signature:对前两部分的签名,防止数据篡改。

实现JWT的包很多,选一个用的人多的Java JWT。如何选三方包也是开发中的难点。
在这里插入图片描述

一.操作步骤

1.引入依赖

        <!-- Auth0 JWT -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>

2.Controller

src/main/java/com/ruoyi/web/controller/system/SysLoginController.java

  • 用户登录成功后,根据用户ID,生成token,返回给前端。
  • getRouters接口,解析请求头里的token,获取用户ID
@RestController
public class SysLoginController {
    @Autowired
    private TokenService tokenService;

    @Autowired
    private ISysMenuService menuService;

    @Autowired
    private ISysUserService sysUserService;

    @PostMapping("/login")
    public AjaxResult login(@RequestBody SysUserDTO userDTO) {
        LambdaQueryWrapper<SysUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        if (userDTO.getUserName() != null) {
            lambdaQueryWrapper.eq(SysUser::getUserName, userDTO.getUserName());
        }

        SysUser sysUser = sysUserService.getOne(lambdaQueryWrapper);

        if (sysUser != null && sysUser.getPassword().equals(userDTO.getPassword())) {
            AjaxResult ajax = AjaxResult.success();

            // 生成令牌
            String token = tokenService.createToken(String.valueOf(sysUser.getUserId()));
            ajax.put(Constants.TOKEN, token);
            return ajax;
        } else {
            throw new RuntimeException();
        }
    }

    /**
     * 获取路由信息
     *
     * @return 路由信息
     */
    @GetMapping("getRouters")
    public AjaxResult getRouters(HttpServletRequest request) {
        String userId = tokenService.getToken(request);

        return AjaxResult.success(menuService.buildMenus(Long.valueOf(userId)));
    }
}

3.配置文件

增加token相关的配置
src/main/resources/application.yml

# token配置
token:
  # 令牌自定义标识
  header: Authorization
  # 令牌密钥
  secret: abcdefghijklmnopqrstuvwxyzaaaaaa
  # 令牌有效期(默认30分钟)
  expireTime: 30

4.配置类

src/main/java/com/ruoyi/framework/web/service/TokenService.java

@Component
public class TokenService {
    // 令牌自定义标识
    @Value("${token.header}")
    private String header;

    // 令牌秘钥
    @Value("${token.secret}")
    private String secret;

    // 令牌有效期(默认30分钟)
    @Value("${token.expireTime}")
    private int expireTime;

    protected static final long MILLIS_SECOND = 1000;

    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

    /**
     * 从数据声明生成令牌
     *
     * @return 令牌
     */
    public String createToken(String userId) {
        Algorithm algorithm = Algorithm.HMAC256(secret);

        // 生成Access Token
        return JWT.create()
                .withIssuer("com-ruoyi")
                .withSubject(userId)
                .withExpiresAt(new Date(System.currentTimeMillis() + expireTime * MILLIS_MINUTE))
                .withClaim("role", "test")
                .sign(algorithm);
    }

    /**
     * 从令牌中获取数据声明
     */
    public String parseToken(String token) {
        DecodedJWT jwt = JWT.require(Algorithm.HMAC256(secret))
                .build()
                .verify(token);
        return jwt.getSubject();
    }

    public String getToken(HttpServletRequest request) {
        String token = request.getHeader(header);
        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {
            token = token.replace(Constants.TOKEN_PREFIX, "");
        }
        return parseToken(token);
    }
}

5.新增常量

src/main/java/com/ruoyi/common/constant/Constants.java

    /**
     * 令牌前缀
     */
    public static final String TOKEN_PREFIX = "Bearer ";

6.前端axios新增请求拦截器

src\utils\request.js

import axios from 'axios'
import { getToken } from '@/utils/auth'

// 创建axios实例
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: import.meta.env.VITE_APP_BASE_API,
  // 超时
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  }
})

// request拦截器
service.interceptors.request.use(config => {
  // 是否需要设置 token
  const isToken = (config.headers || {}).isToken === false

  if (getToken() && !isToken) {
    config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }

  return config
}, error => {
  console.log(error)
  Promise.reject(error)
})

// 响应拦截器
service.interceptors.response.use(res => {
  return Promise.resolve(res.data)
},
  error => {
    return Promise.reject(error)
  }
)

export default service

7.修改全局守卫Bug

在获取路由接口失败后,注销后跳转到根目录。
src\permission.js

import router from './router'
import { getToken } from '@/utils/auth'
import usePermissionStore from '@/stores/permission'
import useUserStore from '@/stores/user'
import { ElMessage } from 'element-plus'


const whiteList = ['/login']
const whiteListDict = whiteList.reduce((acc, cur) => {
    acc[cur] = true;
    return acc;
}, {});

router.beforeEach(async (to, from, next) => {

    if (getToken()) {
        if (whiteListDict[to.path]) {
            next({ path: '/' })
        } else {
            if (router.getRoutes().length <= 3) {
                try {
                    const newRouteRecord = await usePermissionStore().generateRoutes()

                    newRouteRecord.forEach(route => {
                        router.addRoute(route) // 动态添加可访问路由表
                    })

                    next({ ...to, replace: true })
                } catch (error) {
                    useUserStore().logout().then(() => {
                        ElMessage.error(error)
                        next({ path: '/' })
                    })
                }
            } else {
                next()
            }
        }
    } else {
        // 没有token
        if (whiteListDict[to.path]) {
            // 在免登录白名单,直接进入
            next()
        } else {
            next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
        }
    }
})


二.功能验证

运行项目,浏览器访问http://localhost:5173/index
输入用户名和密码登录
浏览器使用开发者工具,查看getRouters请求是否带上token
在这里插入图片描述
debug后端程序,检查在getRouters里获取的userId是否是登录用户的userId
在这里插入图片描述

三.思考

目前的用户管理,角色管理,菜单管理的新增,都只做了单表的新增,如何才能把用户,角色,菜单三张表关联起来?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值