使用mybatisplus快速开发springboot项目(三)--JWT拦截器

使用mybatisplus快速开发springboot项目(一)-CSDN博客

使用mybatisplus快速开发springboot项目(二)_如何用mybatis-plus写业务-CSDN博客

springboot中redis配置

pom.xml导入依赖

        <!--集成redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--开启redis缓存注解-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

application.yml配置文件

spring:
  redis:
    host: 127.0.0.1
    port: 6379

RedisConfig.java配置文件

package com.example.examservice.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration
@EnableCaching//允许我们使用缓存
public class RedisConfig {
    /**
     * 缓存过期时间(秒)
     */
    public static final long CACHE_EXPIRE_SECEND = 3600 * 2;

    @Bean//此时,将我们的redisTemplate加载到了我们的spring的上下文中,applicationContext
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
        //初始化一个redisTemplate
        RedisTemplate<String,Object> redisTemplate=new RedisTemplate<String,Object>();
        //序列话(一般用于key值)
        RedisSerializer<String> redisSerializer=new StringRedisSerializer();
        //引入json串的转化类(一般用于value的处理)
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper=new ObjectMapper();
        //设置objectMapper的访问权限
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //指定序列化输入类型,就是将数据库里的数据按照一定类型存储到redis缓存中。
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        //创建链接
        redisTemplate.setConnectionFactory(factory);
        //redis key值序列化
        redisTemplate.setKeySerializer(redisSerializer);
        //value序列化,因为我们的value大多是通过对象转化过来的,所以使用jackson2JsonRedisSerializer
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        //value序列化,hashmap的序列话
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        return redisTemplate;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory){
        //序列化(一般用于key值)
        RedisSerializer<String> redisSerializer=new StringRedisSerializer();
        //引入json串的转化类(一般用于value的处理)
        Jackson2JsonRedisSerializer  jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper=new ObjectMapper();
        //设置objectMapper的访问权限
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //指定序列化输入类型,就是将数据库里的数据按照一定类型存储到redis缓存中。
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        //序列话配置,乱码问题解决以及我们缓存的时效性
        RedisCacheConfiguration config=RedisCacheConfiguration.defaultCacheConfig().
                entryTtl(Duration.ofSeconds(CACHE_EXPIRE_SECEND)).//缓存时效性设置
                        serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).//key序列化
                        serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).//value序列化
                        disableCachingNullValues();//空值不存入缓存
        //创建cacheManager链接并设置属性
        RedisCacheManager cacheManager= RedisCacheManager.builder(factory).cacheDefaults(config).build();
        return cacheManager;
    }

}

BaseContext.java(本地线程静态方法,可忽略)

package com.example.examservice.common;

/**
 * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id。 每个线程都有一个单独的存储空间,线程之间隔离
 */
public class BaseContext {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    /**
     * 设置值
     * @param id
     */
    public static void setCurrentId(Long id){
        threadLocal.set(id);
    }

    /**
     * 获取值
     * @return
     */
    public static Long getCurrentId(){
        return threadLocal.get();
    }

}

springboot中jwt配置

pom.xml导入依赖

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

JWTUtils.java(jwt工具类)

package com.example.examservice.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import javax.servlet.http.HttpServletRequest;
import java.util.Calendar;
import java.util.Map;

/**
 * @Description: JWT工具
 */
public class JWTUtils {
    // 签名密钥
    private static final String SECRET = "!DAR$";

    /**
     * 生成token
     * @param payload token携带的信息
     * @return token字符串
     */
    public static String getToken(Map<String,String> payload){
        // 指定token过期时间为60分钟
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, 180);

        JWTCreator.Builder builder = JWT.create();
        // 构建payload
        payload.forEach((k,v) -> builder.withClaim(k,v));
        // 指定过期时间和签名算法
        String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SECRET));
        return token;
    }


    /**
     * 解析token
     * @param token token字符串
     * @return 解析后的token
     */
    public static DecodedJWT decode(String token){
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
        DecodedJWT decodedJWT = jwtVerifier.verify(token);
        return decodedJWT;
    }

    /**
     * 获取jwt中的token
     * @Param [request]
     * @return 返回redis中的token键
     */
    public static String getTokenByJWT(HttpServletRequest request){
        String token = request.getHeader("Authorization");//获取Authorization请求头中的jwt信息
        DecodedJWT decode = decode(token);//解析jwt信息
        String Token = decode.getClaim("token").asString();//获取jwt中的token信息并转为String
        return Token;
    }
}

JWTInterceptor.java(jwt拦截器类)

package com.example.examservice.interceptor;

import com.example.examservice.common.BaseContext;
import com.example.examservice.utils.JWTUtils;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class JWTInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String JWT = request.getHeader("Authorization");
        try {
            //对跨域中的预检请求进行放行
            if("OPTIONS".equalsIgnoreCase(request.getMethod())) {
                return true;
            }
            // 1.校验JWT字符串
            DecodedJWT decodedJWT = JWTUtils.decode(JWT);
            // 2.取出JWT字符串载荷中的用户信息
            String userId = decodedJWT.getClaim("id").asString();
            log.info("当前线程的id:"+userId);
            BaseContext.setCurrentId(Long.valueOf(userId));
            return true;
        }catch (SignatureVerificationException e){
            response.setStatus(508);
            System.out.println("无效签名");
            e.printStackTrace();
        }catch (TokenExpiredException e){
            response.setStatus(514);
            System.out.println("token已经过期");
            e.printStackTrace();
        }catch (AlgorithmMismatchException e){
            response.setStatus(508);
            System.out.println("算法不一致");
            e.printStackTrace();
        }catch (Exception e){
            response.setStatus(508);
            System.out.println("token无效");
            e.printStackTrace();
        }
        return false;
    }
}

WebMvcConfig.java(springbootMVC配置文件)

package com.example.examservice.config;

import com.example.examservice.common.JacksonObjectMapper;
import com.example.examservice.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.util.List;

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

	/**
	 * 扩展mvc框架的消息转换器
	 * @param converters
	 */
	@Override
	protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
		// 创建消息转换器对象
		MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
		// 设置对象转换器,底层使用Jackson将Java对象转为json
		messageConverter.setObjectMapper(new JacksonObjectMapper());
		// 将上面的消息转换器对象追加到mvc框架的转换器集合中
		converters.add(0, messageConverter);
	}

	/**
	 * 跨域配置
	 * @Param [registry]
	 */
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**") // 所有接口
				.allowCredentials(true) // 是否发送 Cookie
				.allowedOriginPatterns("*") // 支持域
				.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE","PATCH"}) // 支持方法
				.allowedHeaders("*")
				.exposedHeaders("*");
	}

	/**
	 * 注册jwt拦截器
	 * @Param [registry]
	 */
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(new JWTInterceptor()).
				excludePathPatterns("/user/login", "/user/info", "/question/**").//登录请求放行
				addPathPatterns("/**");
	}

}

springboot登录业务实现

       返回通用类R参考之前文章,这里直接给出三个具体实现业务,登录,获取用户登录用户信息,登出。

package com.example.examservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.examservice.common.CustomException;
import com.example.examservice.common.R;
import com.example.examservice.entity.ClassStudent;
import com.example.examservice.entity.User;
import com.example.examservice.mapper.UserMapper;
import com.example.examservice.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.examservice.utils.JWTUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 用户信息 服务实现类
 * </p>
 *
 * @author liujianchen
 * @since 2024-02-19
 */
@Transactional
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public R login(User user) {
        //将页面提交的密码password进行md5加密处理
        String password = user.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());

        //根据页面提交的用户名username查询数据库
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername,user.getUsername());
        User one = this.getOne(queryWrapper);

        //如果没有查询到则返回登录失败结果
        if(one == null){
            return R.error().message("账号不存在");
        }

        //密码比对,如果不一致则返回登录失败结果
        if(!one.getPassword().equals(password)){
            return R.error().message("密码错误");
        }

        //查看员工状态,如果为已禁用状态,则返回员工已禁用结果
        if(one.getStatus() == 0){
            return R.error().message("账号已禁用");
        }

        // 登录成功,UUID随机生成唯一Token存入redis中,实现登出
        String Token ="employee_" + UUID.randomUUID().toString().replaceAll("-", "");
        redisTemplate.opsForValue().set(Token,one,180, TimeUnit.MINUTES);

        //用jwt生成token
        Map<String, String> payload = new HashMap<>();
        payload.put("id", String.valueOf(one.getId()));
        payload.put("token", Token);//存入redis中的Token,作为关联
        String token = JWTUtils.getToken(payload);

        return R.ok().data("token",token);
    }

    @Override
    public R info(HttpServletRequest request) {
        // 获取jwt中redis的token键
        String Token = JWTUtils.getTokenByJWT(request);

        // redis中是否有该token键
        if (!redisTemplate.hasKey(Token)){
            throw new CustomException("用户信息已过期");
        }
        User user = (User) redisTemplate.opsForValue().get(Token);

        return R.ok().data("name",user.getName()).data("roles",user.getRoles()).data("id",user.getId())
                .data("avatar",user.getAvatar());
    }

    @Override
    public R logout(HttpServletRequest request) {
        // 删除redis缓存中的用户信息
        redisTemplate.delete(JWTUtils.getTokenByJWT(request));
        return R.ok();
    }
}

request.js(前端拦截器)

         为每个向后端发送的请求设置请求头给后端jwt解析,请求头是键值对的结构即("Authorization", token)的格式。后端设置传参HttpServletRequest request

通过request.getHeader("Authorization")拿到token

import axios from "axios";
import { MessageBox, Message, Notification } from "element-ui";
import store from "@/store";
import { getToken } from "@/utils/auth";
import defaultSettings from "@/settings";

const url = defaultSettings.url || "http://localhost";
const post = defaultSettings.post || "9000";


const service = axios.create({
  baseURL: url + ":" + post, 
  timeout: 5000, 
});

// request interceptor
service.interceptors.request.use(
  (config) => {
    if (store.getters.token) {
      //token保存的请求头
      config.headers["Authorization"] = getToken();
    }
    return config;
  },
  (error) => {
    console.log(error); // for debug
    return Promise.reject(error);
  }
);

// response interceptor
service.interceptors.response.use(
  (response) => {
    const res = response.data;
    console.log("状态码" + response.status);
    if (res.code !== 200) {
      Notification.error({
        title: "请求错误",
        message: res.message || "Error",
        duration: 5 * 1000,
      });

      return Promise.reject(new Error(res.message || "Error"));
    } else {
      return res;
    }
  },
  (error) => {
    console.log("err:" + error); // for debug
    console.log(error);
    let { message } = error;
    if (message == "Network Error") {
      message = "后端接口连接异常";
    } else if (message.includes("timeout")) {
      message = "系统接口请求超时";
    } else if (message.includes("Request failed with status code")) {
      message = "系统接口" + message.substr(message.length - 3) + "异常";
    }

    Notification.error({
      title: "获取失败",
      message: "请重新登录",
      duration: 5 * 1000,
    });

    const err = error.response.status;
    // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
    if (err === 508 || err === 512 || err === 514) {
      // to re-login
      store.dispatch("user/resetToken").then(() => {
        // location.reload(); // 为了重新实例化vue-router对象 避免bug
      });
    }

    return Promise.reject(error);
  }
);

export default service;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值