SpringBoot使用JWT进行跨域身份验证

整合JWT令牌

一、不在拦截器使用

1、在模块中添加jwt工具依赖

<dependencies>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>

    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
    </dependency>
</dependencies>

2、创建JWT工具类

package com.stu.service.base.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/******************************
 * 用途说明:
 * 作者姓名: Administrator
 * 创建时间: 2022-06-30 21:47
 ******************************/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JwtInfo {
    private String id;
    private String nickName;
    private String avatar;
    //权限、角色等
    //不要存敏感信息
}

JwtUtils

package com.stu.service.base.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;
import org.springframework.util.StringUtils;

import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;

/******************************
 * 用途说明:jwt工具类
 * 作者姓名: Administrator
 * 创建时间: 2022-06-30 21:48
 ******************************/
public class JwtUtils {

    public static final String APP_SECRET = "sdfGRRD323FGSfdrtr4233";

    private static Key getKeyInstance() {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        byte[] bytes = DatatypeConverter.parseBase64Binary(APP_SECRET);
        return new SecretKeySpec(bytes, signatureAlgorithm.getJcaName());
    }

    /**
     * 获取 jwt token
     *
     * @param jwtInfo 实体
     * @param expire  过期时间 m
     * @return 生成的 token
     */
    public static String getJwtToken(JwtInfo jwtInfo, int expire) {

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                // 主题
                .setSubject("stu-user")
                // 颁发时间
                .setIssuedAt(new Date())
                // 过期时间
                .setExpiration(DateTime.now().plusSeconds(expire).toDate())
                // 用户id
                .claim("id", jwtInfo.getId())
                // 用户昵称
                .claim("nickName", jwtInfo.getNickName())
                // 用户头像
                .claim("avatar", jwtInfo.getAvatar())
                .signWith(SignatureAlgorithm.HS256, getKeyInstance())
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     *
     * @param jwtToken
     * @return
     */
    public static boolean checkJwtToken(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) {
            return false;
        }
        try {
            Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     *
     * @param request
     * @return
     */
    public static boolean checkJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        return checkJwtToken(jwtToken);
    }

    /**
     * 根据token获取会员信息
     *
     * @param request
     * @return
     */
    public static JwtInfo getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if (StringUtils.isEmpty(jwtToken)) {
            return null;
        }
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        JwtInfo jwtInfo = new JwtInfo(claims.get("id").toString(), claims.get("nickName").toString(), claims.get(
                "avatar").toString());
        return jwtInfo;
    }
}

3、使用

package com.stu.service.trade.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.stu.service.base.dto.CourseDto;
import com.stu.service.base.dto.MemberDto;
import com.stu.service.base.exception.CustomException;
import com.stu.service.base.result.R;
import com.stu.service.base.result.ResultCodeEnum;
import com.stu.service.base.utils.JwtInfo;
import com.stu.service.base.utils.JwtUtils;
import com.stu.service.trade.entity.Order;
import com.stu.service.trade.feign.EduCourseService;
import com.stu.service.trade.feign.UcenctMemberService;
import com.stu.service.trade.mapper.OrderMapper;
import com.stu.service.trade.service.OrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.stu.service.trade.utli.OrderUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;

/**
 * <p>
 * 订单 服务实现类
 * </p>
 *
 * @author stu
 * @since 2022-07-10
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    @Autowired
    private EduCourseService eduCourseService;

    @Autowired
    private UcenctMemberService ucenctMemberService;

    /***********************************
     * 用途说明:新增订单
     * @param courseId
     * @param request
     * 返回值说明:
     * @return java.lang.String
     ***********************************/
    @Override
    public String saveOrder(String courseId, HttpServletRequest request) {

        Order addOrder = null;
        JwtInfo jwtInfo = JwtUtils.getMemberIdByJwtToken(request);
        if (null == jwtInfo) {
            throw new CustomException(ResultCodeEnum.FETCH_ACCESSTOKEN_FAILD);
        }
        String memberId = jwtInfo.getId();
        if (StringUtils.isEmpty(memberId)) {
            throw new CustomException(ResultCodeEnum.FETCH_ACCESSTOKEN_FAILD);
        }
        //查询当前用户是否已有当前课程的订单
        QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("course_id", courseId);
        queryWrapper.eq("member_id", memberId);
        Order order = baseMapper.selectOne(queryWrapper);
        if (null != order) {
            return order.getId();
        }
        //查询用户信息
        MemberDto memberDto = ucenctMemberService.getMemberInfo(memberId);
        if (null == memberDto) {
            throw new CustomException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
        }

        //查询课程信息
        CourseDto courseDto = eduCourseService.getCourseById(courseId);

        if (null == courseDto) {
            throw new CustomException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
        }
        addOrder = new Order();
        addOrder.setOrderNo(OrderUtils.getOrderNo());
        addOrder.setCourseId(courseId);
        addOrder.setCourseId(courseDto.getId());
        addOrder.setCourseTitle(courseDto.getTitle());
        addOrder.setCourseCover(courseDto.getCover());
        addOrder.setTotalFee(courseDto.getPrice());
        addOrder.setTeacherName(courseDto.getTeacherName());

        addOrder.setMemberId(memberId);
        addOrder.setNickname(jwtInfo.getNickName());
        addOrder.setMobile(memberDto.getMobile());
        addOrder.setStatus(0);//"订单状态(0:未支付 1:已支付)"
        addOrder.setPayType(1);
        baseMapper.insert(addOrder);

        return addOrder.getId();
    }

    /***********************************
     * 用途说明:支付成功
     * @param orderId
     * 返回值说明:
     * @return boolean
     ***********************************/
    @Override
    public Order updateOrderStraus(String orderId) {
        Order order = baseMapper.selectById(orderId);
        order.setStatus(1);
        eduCourseService.updateBuyCountById(order.getCourseId());
        return order;
    }
}

4、vue页面

import axios from 'axios'
import { Message, MessageBox } from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'

// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api的base_url
  timeout: 5000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(
  config => {
    if (store.getters.token) {
      config.headers['token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
    }
    return config
  },
  error => {
    // Do something with request error
    console.log(error) // for debug
    Promise.reject(error)
  }
)

// respone拦截器
service.interceptors.response.use(
  response => {
    /**
     * code为非20000是抛错 可结合自己业务进行修改
     */
    const res = response.data
    if (res.code !== 20000) {
       
      Message({
        message: res.message,
        type: 'error',
        duration: 5 * 1000
      })

      // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        MessageBox.confirm(
          '你已被登出,可以取消继续留在该页面,或者重新登录',
          '确定登出',
          {
            confirmButtonText: '重新登录',
            cancelButtonText: '取消',
            type: 'warning'
          }
        ).then(() => {
          store.dispatch('FedLogOut').then(() => {
            location.reload() // 为了重新实例化vue-router对象 避免bug
          })
        })
      }
      return Promise.reject('error')
    } else {
      return response.data
    }
  },
  error => {
 
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

二、通过拦截器方式验证

创建拦截器

package com.qfedu.fmmall.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.qfedu.fmmall.vo.ResStatus;
import com.qfedu.fmmall.vo.ResultVO;
import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Component
public class CheckTokenInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String method = request.getMethod();
        if("OPTIONS".equalsIgnoreCase(method)){
            return true;
        }
        String token = request.getHeader("token");
        if(token == null){
            ResultVO resultVO = new ResultVO(ResStatus.LOGIN_FAIL_NOT, "请先登录!", null);
            doResponse(response,resultVO);
        }else{
            try {
                JwtParser parser = Jwts.parser();
                parser.setSigningKey("QIANfeng6666"); //解析token的SigningKey必须和生成token时设置密码一致
                //如果token正确(密码正确,有效期内)则正常执行,否则抛出异常
                Jws<Claims> claimsJws = parser.parseClaimsJws(token);
                return true;
            }catch (ExpiredJwtException e){
                ResultVO resultVO = new ResultVO(ResStatus.LOGIN_FAIL_OVERDUE, "登录过期,请重新登录!", null);
                doResponse(response,resultVO);
            }catch (UnsupportedJwtException e){
                ResultVO resultVO = new ResultVO(ResStatus.LOGIN_FAIL_NOT, "Token不合法,请自重!", null);
                doResponse(response,resultVO);
            }catch (Exception e){
                ResultVO resultVO = new ResultVO(ResStatus.LOGIN_FAIL_NOT, "请先登录!", null);
                doResponse(response,resultVO);
            }
        }
        return false;
    }

    private void doResponse(HttpServletResponse response,ResultVO resultVO) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        PrintWriter out = response.getWriter();
        String s = new ObjectMapper().writeValueAsString(resultVO);
        out.print(s);
        out.flush();
        out.close();
    }

}

配置拦截器

package com.qfedu.fmmall.config;

import com.qfedu.fmmall.interceptor.CheckTokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private CheckTokenInterceptor checkTokenInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(checkTokenInterceptor)
                .addPathPatterns("/shopcart/**")
                .addPathPatterns("/orders/**")
                .addPathPatterns("/useraddr/**")
                .addPathPatterns("/user/check");
    }
}

axios通过请求头传值

axios({
    method: "get",
    url: baseUrl + "shopcart/list",
    headers: {
        token: this.token
    }
}).then(function(res) {
    console.log(res);
});

各种异常

JwtException 总异常
ClaimJwtException 获取Claim异常
ExpiredJwtException token过期异常
IncorrectClaimException token无效
MalformedJwtException 密钥验证不一致
MissingClaimException JWT无效
RequiredTypeException 必要类型异常
SignatureException 签名异常
UnsupportedJwtException 不支持JWT异常

 

作者:
出处:https://www.cnblogs.com/konglxblog//
版权:本文版权归作者和博客园共有
转载:欢迎转载,文章中请给出原文连接,此文章仅为个人知识学习分享,否则必究法律责任 

 

 

posted @ 2022-10-16 22:19  程序员小明1024  阅读(77)  评论(0编辑  收藏  举报