Bootstrap

Springboot+JWT验证 前后端

Springboot+JWT验证 前后端

前言

使用Springboot+JWT+vue 完成前后端分离验证


一、JWT 是什么?

JWT 主要用于用户登录鉴权,是一种认证机制,让后台知道该请求是来自于受信的客户端
验证流程:
1.用户使用账号、密码登录应用,登录的请求发送到 Authentication Server。
2.Authentication Server 进行用户验证,然后创建 JWT 字符串返回给客户端。
3.客户端请求接口时,在请求头带上 JWT。
4.Application Server 验证 JWT 合法性,如果合法则继续调用应用接口返回结果。

数据结构:xxxxx.yyyyy.zzzzz(是一段字符串,分为三个部分,以 “.” 隔开)

开发流程
1.编写JwtInterceptor生成规则
2.编写InterceptorConfig增加规则,进行拦截
3.编写TokenUtils 生成token返回给前端

二、使用步骤

1.引入库

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

2.JwtInterceptor

import cn.hutool.core.util.StrUtil;
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.example.qinggedemoone.entity.User;
import com.example.qinggedemoone.exception.ServiceException;
import com.example.qinggedemoone.mapper.UserMapper;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//以拦截器方式进行继承
public class JwtInterceptor implements HandlerInterceptor {

    @Resource
    private UserMapper userMapper;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token=request.getHeader("token"); //前端header中传过来的参数
        if(StrUtil.isBlank(token)){
            token=request.getParameter("token");   //url参数   ?token=xxxx
        }
        System.out.println("==token==:"+token);
        //执行认证
        if(StrUtil.isBlank(token)){
            throw new ServiceException("401","请登录");
        }
        //获取token中的user id
        String userId;
        try{
            //getAudience相当于一个仓库,存储了很多信息
            //get(0) 获取audience中的第一个数据
            userId= JWT.decode(token).getAudience().get(0);
        }catch (JWTDecodeException j){
            throw new ServiceException("401","请登录");
        }
        //更具token中的userId查询数据
        User user = userMapper.selectById(userId);
        System.out.println("userId:"+userId);
        System.out.println("++user++"+user);
        if(user==null){
            throw new ServiceException("401","请登录");
        }
        //通过用户密码  加密  生成一个验证器
        /*
        * 使用用户密码(user.getPassword())作为密钥来创建一个 HMAC256 算法实例,
        * 并基于这个算法实例生成一个 JWT 验证器(JWTVerifier)。JWT.require(Algorithm)
        * 方法返回一个 JWT 构建器,通过调用 .build() 方法来构建验证器。
        * Algorithm:算法
        * */
        JWTVerifier build = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
        try{
            build.verify(token);   //进行校验
        }catch (JWTVerificationException e){
            throw new ServiceException("401","请登录");
        }
        return true;
    }
}

3.InterceptorConfig

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;

/*
* InterceptorConfig拦截
* JwtInterceptor定义拦截规则
* */
/*@Configuration配置类*/
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
    /*
    * 实现addInterceptors方法  addInterceptor增加拦截器规则
    * addPathPatterns:拦截路径   /**拦截所有的请求路径
    * */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor())
                .addPathPatterns("/**").excludePathPatterns("/login","/register");
        super.addInterceptors(registry);
    }
    /* @Bean注入到容器中*/
    @Bean
    public JwtInterceptor jwtInterceptor(){
        return new JwtInterceptor();
    }
}

4.定义生成token的方法

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.example.qinggedemoone.entity.User;
import com.example.qinggedemoone.mapper.UserMapper;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;

@Component   //加入到spring容器中
public class TokenUtils {
    //静态的  可以直接通过类去访问  下面有方法是静态的 所以要在此声明静态的方法
    //静态方法只能访问静态变量
    private static UserMapper staticUserMapper;
    @Resource   //要想拿到容器中的对象  必须类上添加@Component注解
    UserMapper userMapper;
    @PostConstruct
    public void setUserService(){
        staticUserMapper=userMapper;
    }
    /*生成token*/
    public static String createToken(String userId,String sign){
        return JWT.create().withAudience(userId)   //将 user id保存到token中 ,作为载荷
                .withExpiresAt(DateUtil.offsetHour(new Date(),2))   //2小时后token过期
                .sign(Algorithm.HMAC256(sign));   // 以password 作为token的密钥
    }
    /*
    * 获取当前登录的用户信息
    * return user对象
    * 只要请求有token 我就可以通过token来判断当前登录的用户的所有信息
    * */
    public static User getCurrentUser(){
        try{
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String token=request.getHeader("token");
            if(StrUtil.isNotBlank(token)){
                String userId=JWT.decode(token).getAudience().get(0);
                return staticUserMapper.selectByUsername(userId);
            }
        }catch (Exception e){
            return null;
        }
        return null;
    }
}

接口请求

@Autowired
    UserService userService;
@PostMapping("/login")
    public Result login(@RequestBody User user){
        if(StrUtil.isBlank(user.getUsername())||StrUtil.isBlank(user.getPassword())){
            return Result.error("输入数据不合法");
        }
        User login = userService.login(user);
        return Result.success(login);
    }

UserService login方法中生成token

@Override
    public User login(User user) {
        //根据用户名查询数据库的用户信息
        User user1 = userMapper.selectByUsername(user.getUsername());
        if(user1==null){
            //抛出一个自定义异常
            throw new ServiceException("账号不存在");
        }

        if(!user.getPassword().equals(user1.getPassword())){
            throw new ServiceException("用户名或密码错误");
        }
        //当通过之后生成token
        String token = TokenUtils.createToken(user1.getId().toString(), user1.getPassword());
        user1.setToken(token);
        return user1;
    }

插播一段注意事项:后端一定要配置跨域

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
    private static final long MAX_AGE=24*60*60;
    @Bean
    public CorsFilter corsFilter(){
        UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration=new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");   //设置访问原地址
        corsConfiguration.addAllowedHeader("*");   //设置访问源请求头
        corsConfiguration.addAllowedMethod("*");   //设置访问原请求方法
        corsConfiguration.setMaxAge(MAX_AGE);
        source.registerCorsConfiguration("/**",corsConfiguration);   //对接口配置跨域设置
        return new CorsFilter(source);
    }
}

前端中的内容

request.js

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

// 创建可一个新的axios对象
const request = axios.create({
    baseURL: 'http://localhost:8080',   // 后端的接口地址  ip:port
    timeout: 30000
})

// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
//在请求发送前,可以通过拦截器对请求进行一些处理,比如添加请求头。
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    //每次请求都会带上请求头作为验证信息
    let user = JSON.parse(localStorage.getItem("honey-user") || '{}')
    config.headers['token'] = user.token  // 设置请求头
    return config
}, error => {
    console.error('request error: ' + error) // for debug
    return Promise.reject(error)
});

// 在响应返回后,通过拦截器统一处理结果。
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
    response => {
        let res = response.data;

        // 兼容服务端返回的字符串数据
        if (typeof res === 'string') {
            res = res ? JSON.parse(res) : res
        }
        //如果接口返回401  直接跳转到登录页面
        if(res.code==='401'){
            router.push('/login')
        }


        return res;
    },
    error => {
        console.error('response error: ' + error) // for debug
        return Promise.reject(error)
    }
)
export default request

前端登录函数

login(){
            console.log(this.code)
            console.log(this.SRcode)
            if(this.code.toLowerCase()==this.SRcode.toLowerCase()){
                console.log('验证码输入正确')
            }else{
                this.$message.error('验证码错误');
            }

            request.post('/login',this.user).then(res=>{
                console.log(res)
                if(res.code=='200'){
                    this.$router.push('/') 
                    this.$message.success('登录成功');
                    localStorage.setItem("honey-user",JSON.stringify(res.data))   //存储用户数据,key-value方式存储
                }if(res.code=='500'){
                    this.$message.error('用户名或密码错误');
                }
            })
        }
;