Springboot集成JWT token实现权限验证

1.后端 

1.1.导入依赖

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.3.0</version>
        </dependency>

1.2.编写jwt的拦截器

这个类实现一个HandlerInterceptor接口,这个类主要完成以下几个任务:

  • 从请求头里获取token,没有获取到就抛异常(注意:请求头里原本是没有token的,这个需要我们自己在前端添加一个token)
  • 解码token并从token里获取用户ID,没有获取到就抛异常,表明token里没有数据(注意:这个用户ID是自己在前端添加token时存储的)
  • 通过用户密码来生成一个验证器,解析token(JWT一般含有三个部分,头部,荷载,签名,解析过程中jwtVerifier会检验这三部分能不能正常分离,以及来用验证器来验证签名,以及检查token的过期时间)这一步也是最重要的一步!
package com.kuang.common;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.kuang.exception.ServiceException;
import com.kuang.mapper.UserMapper;
import com.kuang.pojo.User;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;

public class JwtInterceptor implements HandlerInterceptor {

    @Resource
    private UserMapper userMapper;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {


        //从请求头Header里接收传来的参数token
        String headerToken = request.getHeader("token");
        //如果传来的token为空,则从url参数中来接收传来的token
        if(StringUtils.isBlank(headerToken)){
            headerToken = request.getParameter("token");
            //如果url里的token为空,则抛异常
        }
        if (StringUtils.isBlank(headerToken)){
            throw new ServiceException("401","请登录");
        }

        //从token中获取userId
        //JWT.decode(headerToken) 解码JTW Token
        String userId;
        try {
            userId = JWT.decode(headerToken).getAudience().get(0);
        } catch (JWTDecodeException e) {
            throw new ServiceException("401","请登录");
        }
        //根据userId查询数据库
        //userId是String类型,这里要转换成int类型
        User user = userMapper.selectUserById(Integer.parseInt(userId));
        //user为空,则抛异常
        if (user == null){
            throw new ServiceException("401","请登录");
        }
        //通过用户密码加密之后生成一个验证器
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
        try {
            //验证token
            jwtVerifier.verify(headerToken);
        } catch (JWTVerificationException e) {
            throw new ServiceException("401","请登录");
        }
        return true;
    }
}

1.3.编写token的工具类

在这个类中有以下几点任务:

  • 生成token,并且将用户ID放在token的荷载(Payload)中当作受众声明(Audience),以及设置token的过期时间,把用户密码当作密钥,然后给token加一个签名,只有添加了签名,这个token才能被使用,而我们设置的这个密钥就是来验证签名的钥匙
package com.kuang.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.kuang.mapper.UserMapper;
import com.kuang.pojo.User;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.Date;

@Component
public class TokenUtils {

    private static UserMapper staticUserMapper;

    @Autowired
    UserMapper userMapper;

    @PostConstruct
    public void setUserService(){
        staticUserMapper = userMapper;
    }

    /**
     * 创建token
     * @param userId
     * @param sign
     * @return
     */
    public static String createToken(String userId,String sign){

        // 获取当前时间
        Date currentDate = new Date();

        // 创建Calendar实例
        Calendar calendar = Calendar.getInstance();

        // 设置Calendar的时间为currentDate
        calendar.setTime(currentDate);

        // 向前偏移两个小时
        calendar.add(Calendar.HOUR_OF_DAY, 2);

        // 获取偏移后的时间
        Date offsetDate = calendar.getTime();

        return JWT.create().withAudience(userId)//将userId保存到token里
                .withExpiresAt(offsetDate)      //2小时候token过期
                .sign(Algorithm.HMAC256(sign)); //将password作为token密钥

    }

    /**
     * 获取当前登录的用户信息
     * @return
     */
    public static User getCurrentUser(){

        //获取当前请求的HttpServletRequest对象,这样就能在下面访问请求头、参数等
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes())
                .getRequest();

        try {
            //从请求头里获取token
            String token = request.getHeader("token");
            //如果token不为空,则从Audience中获取第一个数据(是用户的id)
            if (StringUtils.isNotBlank(token)){
                String userId = JWT.decode(token).getAudience().get(0);
                return staticUserMapper.selectUserById(Integer.parseInt(userId));
            }
        } catch (Exception e) {
            return null;
        }
        return null;
    }



}

 1.4.扩展springmvc的拦截器

这个类继承WebMvcConfigurationSupport类,主要任务有以下几点:

  • 将在第二步编写的jwt的拦截器注入到spring容器中,并将其添加到spring的拦截器中
  • 设置拦截路径

当用户访问下面设置好的拦截路径时,就会触发我们自己编写的jwt的拦截器,然后进入校验过程(就是第二步中的那一套流程)

package com.kuang.common;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //配置jwt的拦截器规则,拦截所以请求,除了/user/login,/user/register,/file/upload
        registry.addInterceptor(jwtInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/user/login","/user/register","/file/upload");
        super.addInterceptors(registry);
    }


    @Bean
    public JwtInterceptor jwtInterceptor(){
        return new JwtInterceptor();
    }
}

 1.5.将后端生成的token返回给前端

一般会在用户登录时,将token返回给前端,下面这个类就是实现登录功能的service层的实现类

service层: 

 @Override
    public User selectUserByUsername(User user) {

        User user1 = userMapper.selectUserByUsername(user);
        //生成token,userId用来放在token里,password用来生成token的验证器,来验证token
        String token = TokenUtils.createToken(String.valueOf(user1.getId()), user1.getPassword());
        user1.setToken(token);
        return user1;
    }

controller层: 

    //登陆功能
    @PostMapping("/login")
    public Result login(@RequestBody User user, HttpServletRequest request){
        User user1 = userService.selectUserByUsername(user);

        if (!user.getPassword().equals(user1.getPassword())){
            return Result.error("用户名或密码不正确");
        }
        //将id存入session
        //request.getSession().setAttribute("userId",user1.getId());

        //将含有token的user对象返回给前端
        return Result.success(user1);
    }

2.前端

2.1.导入request.js文件 

这里添加一个通用的request.js文件,这个文件用于以下几点:

  • 可以在请求发送前对请求做一些处理
  • 可以在接口响应后统一处理结果
  • 对请求路径前面的http:localhost做了封装,以便不用每次都写上
  • 对返回的数据做了封装,原本要访问后端返回的数据要这样写res.data.data,封装了之后可以简化为res.data

import axios from 'axios'
import router from "@/router";

const request = axios.create({
    baseURL: 'http://localhost:8082',  // 注意!! 这里是全局统一加上了 '/api' 前缀,也就是说所有接口都会加上'/api'前缀在,页面里面写接口的时候就不要加 '/api'了,否则会出现2个'/api',类似 '/api/api/user'这样的报错,切记!!!
    timeout: 5000
})

// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    //在请求头里添加一个token
    let user = JSON.parse(localStorage.getItem("user") || '{}')
    config.headers['token'] = user.token;  // 设置请求头
    return config
}, error => {
    return Promise.reject(error)
});

// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
    response => {
        let res = response.data;
        // 如果是返回的文件
        if (response.config.responseType === 'blob') {
            return res
        }
        // 兼容服务端返回的字符串数据
        if (typeof res === 'string') {
            res = res ? JSON.parse(res) : res
        }
        if (res.code === '401'){
            router.push('/login')
        }
        return res;
    },
    error => {
        console.log('err' + error) // for debug
        return Promise.reject(error)
    }
)


export default request

在上面的文件里,我们着重看这几行代码,在前端发送请求时,在请求头里添加一个token,并且从localStorage中获取在登录时存储的用户信息,用来存储在token中,具体程序如下:

在接口响应后,统一处理结果中的如下程序,如果用户没有token,后端就会返回401的错误,在这里就会处理401错误,进行页面跳转

导入这个文件之后,我们可以在main.js文件中注册这个文件的全局对象

2.2.采用request.js文件里提供的请求方式

 这里只提供一个例子,其他的地方都是一样的

3.流程图分析

下面的流程图涉及了后端以下几个类:

  • JwtInterceptor:1.2中的jwt的拦截器
  • TokenUtils:1.3中的token的工具类
  • InterceptorConfig:1.4中的springmvc的扩展类
  • LoginController:处理用户登录的controller方法,这个方法用来返回给前端token
  • selectUserController:在登录之后,处理前端发过来的查询用户的请求

解释上面的过程,用户在登陆之前还没有token,登录之后,通过调用TokenUtils来生成token,并且返回给前端,至此,该用户就有了token,在之后的请求中首先会被Interceptor-Config类拦截下来,然后进入JwtInterceptor类进行token的校验,成功后才会进入controller层,否则就会抛出401的异常(这里的异常是自己手动设置的),然后就会返回给前端,前端就会发生页面跳转,跳转到login页面

JWT(JSON Web Token)是一种基于 JSON 的开放标准(RFC 7519),用于在各方之间安全地将声明作为 JSON 对象传输。JWT通常用于身份验证和授权。JWT由三部分组成:头部、载荷和签名。 在使用 JWT 进行身份验证时,服务器会在用户登录成功后生成一个 JWT,并将其发送给客户端。客户端将 JWT 存储在本地,每次向服务器发送请求时,都需要在请求头中携带该 JWT。服务器在接收到请求后,会验证JWT 的有效性,并根据 JWT 中的信息进行相应的处理。 JWT 的优点是可以减轻服务器的压力,因为服务器不需要在每次请求时都去查询数据库进行身份验证。同时,由于 JWT 中已经包含了足够的信息,服务器可以快速地进行权限验证和授权。 如果要实现 JWT 的登录验证,可以按照以下步骤进行: 1. 用户在登录成功后,服务器生成一个 JWT,并将其发送给客户端。 2. 客户端将 JWT 存储在本地,例如在 LocalStorage 或者 Cookie 中。 3. 客户端在向服务器发送请求时,需要在请求头中携带该 JWT。 4. 服务器在接收到请求后,需要验证JWT 的有效性。服务器可以根据 JWT 的头部信息,结合自己的密钥对 JWT 进行解密,验证 JWT 的签名是否正确。 5. 如果 JWT 验证通过,则说明当前用户已经登录。服务器可以根据 JWT 中的信息,进行相应的权限验证和授权。 需要注意的是,JWT 中的信息是可以被解密的,因此在 JWT 中不应该存储敏感信息,例如用户的密码等。如果需要存储敏感信息,可以考虑使用加密算法对其进行加密。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值