【Axios的使用学习笔记(在项目中采用jwt进行校验及拦截器的权限控制)】

Axios的使用学习笔记(在项目中采用jwt进行校验及自定义注解+拦截器的权限控制)

    • 学习说明
    • 后端说明
        • jwt的身份认证
        • 自定义注解实现权限控制
    • 前端axios封装
        • 前端测试页面
        • 关于preflight请求导致的问题

学习说明

采用Vue3编写,采用Axiozidingzs进行Ajax通信,后端采用jwt进行校验,将token放在请求头部
利用自定义注解控制权限,这一部分只讲解jwt的验证及头部存放token进行axios通信

后端说明

jwt的身份认证

讲解基本的依赖引入及jwt的生成和验证

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
</parent>

<dependencies>
    <!-- 基本的spring boot、jwt 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

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

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

jwt工具类

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;

import java.util.Calendar;

/**
 * @author bbyh
 * @date 2022/10/7 0007 19:43
 * @description
 */
public class JwtUtils {

    private static final String SING = "Hello";

    public static String getToken() {
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.MINUTE, 30);	// 可以在这里设置过期时间,也可以结合redis使用
        JWTCreator.Builder builder = JWT.create();
        return builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(SING));
    }

    public static void verify(String token) {
        JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
    }
}

拦截器部分(考虑到只是验证头部请求是否能够正确接收到,所以拦截器设置的很简单)

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * @author bbyh
 * @date 2022/10/14 0014 15:11
 * @description
 */
@Component
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) {
        String token = request.getHeader("token");
        System.out.println("JwtInterceptor: " + token);

		// 这里的验证部分,也可以采用多一些异常捕获,细致一些
        try {
            JwtUtils.verify(token);
            System.out.println("token正确");
            return true;
        } catch (Exception e) {
            System.out.println("token错误");
        }

        return false;
    }

}

添加配置类及添加拦截器(这里的拦截路径也只是为了方便进行后续操作)
另外附带说明:如果在前面的拦截器内使用了StringRedisTemplate或RedisTemplate
则需要将下面的初始化 new MyInterceptor() 改为@Bean引入

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

/**
 * @author bbyh
 * @date 2022/10/14 0014 15:12
 * @description
 */
@Configuration
public class MyConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/getToken");
    }

	@Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
        		// 这里可以配置允许接收请求的IP,例如:http://localhost:8080
        		// .allowedOrigins("http://localhost:8080")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                .maxAge(3600);
    }

}

使用了 StringRedisTemplate 的写法

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

/**
 * @author bbyh
 * @date 2022/10/14 0014 15:12
 * @description
 */
@Configuration
public class MyConfig implements WebMvcConfigurer {

	@Bean
    public MyInterceptor myInterceptor(){
        return new MyInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/getToken");
    }

	@Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                .maxAge(3600);
    }

}

后端控制器部分(只是为了验证头部携带token能否被成功获取,所以请求非常简单)

import com.boot.config.JwtUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

/**
 * @author bbyh
 * @date 2022/10/14 0014 14:58
 * @description
 */
@RestController
// @CrossOrigin(maxAge = 3600, origins = "*") 
// 这里没有采用跨域,在配置类里面配好了,在这里配比较麻烦,需要每个控制器都加
public class Test {

    @GetMapping("/get")
    public String test1(HttpServletRequest request) {
        System.out.println(request.getHeader("token"));
        return "get";
    }

    @PostMapping("/post")
    public String test2(HttpServletRequest request) {
        System.out.println(request.getHeader("token"));
        return "post";
    }

    @DeleteMapping("/delete")
    public String test3(HttpServletRequest request) {
        System.out.println(request.getHeader("token"));
        return "delete";
    }

    @PutMapping("/put")
    public String test4(HttpServletRequest request) {
        System.out.println(request.getHeader("token"));
        return "put";
    }

    @GetMapping("/getToken")
    public String getToken() {
        return JwtUtils.getToken();
    }

}

自定义注解实现权限控制

自定义注解

RoleEnum.java

package com.boot.annotation;

/**
 * @author bbyh
 * @date 2022/10/13 0013 12:46
 * @description
 */
public enum RoleEnum {
    /**
     * Admin表示管理员,Guest表示未登录游客,User表示登陆的一般用户
     */
    Admin,
    Guest,
    User
}

Roles.java

package com.boot.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author bbyh
 * @date 2022/10/13 0013 12:42
 * @description
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
    RoleEnum value();
}

MyConfig.java

package com.boot.config;

import com.boot.annotation.RoleEnum;
import com.boot.annotation.Roles;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * @author bbyh
 * @date 2022/10/13 0013 12:54
 * @description
 */
public class MyConfig implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        boolean assignableFrom = handler.getClass().isAssignableFrom(HandlerMethod.class);
        System.out.println("此方法是否存在注解:" + assignableFrom);

        String token = request.getHeader("token");

        if (assignableFrom) {
            Roles methodAnnotation = ((HandlerMethod) handler).getMethodAnnotation(Roles.class);
            System.out.println("当前方法的注解是:" + methodAnnotation);
            if (methodAnnotation == null) {
                System.out.println("当前没有添加注解");
            } else {
                System.out.println(methodAnnotation.value());
                System.out.println("当前添加了注解");

                if (methodAnnotation.value() == RoleEnum.Guest) {
                    return true;
                } else if (methodAnnotation.value() == RoleEnum.User) {

                }
            }
        }

        return true;
    }

}

WebConfig.java

package com.boot.config;

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

/**
 * @author bbyh
 * @date 2022/10/13 0013 13:01
 * @description
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyConfig()).addPathPatterns("/**");
    }

}

TestController.java

package com.boot.controlller;

import com.boot.annotation.RoleEnum;
import com.boot.annotation.Roles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author bbyh
 * @date 2022/10/13 0013 12:48
 * @description
 */
@RestController
public class TestController {

    @GetMapping("/test")
    @Roles(RoleEnum.Guest)
    public String test() {

        return "test";
    }

}

前端axios封装

前端测试页面

采用vue3,引入了axios,采用 npm i axios 安装

请求封装的api.js(为了方便获取token,我就放在了sessionStorage里面,也可以放在Vuex里面,
但是Vuex我还没有进行试验,我猜也是可以获取到数据的)
附注:让我感到为难的,是安全性的问题,这个token一旦被获取就没有办法保证接口不被坏人使用了
尽管可以在后端角色内加上限制,但对于单个用户,它的安全性,只存在于密码和token,感觉安全性很空洞

import axios from "axios";

const apiURL = 'http://127.0.0.1:9001';

export const getRequest = (url, params) => {
    return axios({
        headers: {
            token: sessionStorage.getItem('token')
        },
        method: 'get',
        url: apiURL + url,
        params: params
    })
}

export const postRequest = (url, params) => {
    return axios({
        headers: {
            token: sessionStorage.getItem('token')
        },
        method: 'post',
        url: apiURL + url,
        data: params,
    })
}

export const putRequest = (url, params) => {
    return axios({
        headers: {
            token: sessionStorage.getItem('token')
        },
        method: 'put',
        url: apiURL + url,
        data: params,
    })
}

export const deleteRequest = (url, params) => {
    return axios({
        headers: {
            token: sessionStorage.getItem('token')
        },
        method: 'delete',
        url: apiURL + url,
        data: params,
    })
}

App.vue文件内容(这里直接进行axios请求,尝试向后端发送附带token的请求)

<template>
    <div id="app">
        <button @click="testGet">testGet</button>
        <br><br>
        <button @click="testPost">testPost</button>
        <br><br>
        <button @click="testPut">testPut</button>
        <br><br>
        <button @click="testDelete">testDelete</button>
        <br><br>
        <button @click="getToken">getToken</button>
    </div>
</template>

<script>
import { deleteRequest, getRequest, postRequest, putRequest } from './utils/api';
export default {
    name: "App",
    setup(){
        function testGet(){
            getRequest('/get').then((res)=>{
                console.log(res);
            })
        }
        function testPost(){
            postRequest('/post').then((res)=>{
                console.log(res);
            })
        }
        function testPut(){
            putRequest('/put').then((res)=>{
                console.log(res);
            })
        }
        function testDelete(){
            deleteRequest('/delete').then((res)=>{
                console.log(res);
            })
        }
        function getToken(){
            getRequest('/getToken').then((res)=>{
                console.log(res.data);
                sessionStorage.setItem('token', res.data)
                console.log(res);
            })
        }

        return {
            testGet,
            testPost,
            testPut,
            testDelete,
            getToken
        }
    }
};
</script>

结果说明:验证发现,可以通过上述封装将headers内的内容发送到后端,请求成功

运行截图
【Axios的使用学习笔记(在项目中采用jwt进行校验及拦截器的权限控制)】_第1张图片

关于preflight请求导致的问题

附注:有时候请求会出现被跨域拦截,原因是一个请求分裂为两个请求,然后那个preflight被拦截了,后面那个请求就一直拿不到token,但当只有一个请求的时候是不会被跨域拦截的,因为我们后端设置好了跨域

【Axios的使用学习笔记(在项目中采用jwt进行校验及拦截器的权限控制)】_第2张图片

关于preflight请求的详细内容,可以参考这两篇博客:博客一 博客二
下面也给出简单的处理方式,即过滤该请求,在拦截器中稍微修改一下判断流程即可

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * @author bbyh
 * @date 2022/10/14 0014 15:11
 * @description
 */
@Component
public class MyInterceptor implements HandlerInterceptor {

    private static final String PREFLIGHT = "OPTIONS";

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) {
        String token = request.getHeader("token");

		// 这里对请求方式进行判断,preflight采用OPTIONS方式请求,就可以直接过滤,选择不拦截
		// 这样在preflight的下个请求中就可以成功在头部获得token
        if (PREFLIGHT.equals(request.getMethod())) {
            System.out.println("触发了一次" + PREFLIGHT + "请求");
            return true;
        }

        try {
            JwtUtils.verify(token);
            System.out.println("token正确");
            return true;
        } catch (Exception e) {
            System.out.println("token错误");
        }

        return false;
    }

}

目前难缠的跨域问题将告一段落,最后也推荐这篇跨域文章(跨域资源共享 CORS 详解)
经过试验得出,非简单请求确实会触发preflight请求,但当头部的token被后端获取过后,再次刷新了token,之后便不会再进行preflight请求检验,这里面还需要再研究研究。

你可能感兴趣的:(学习,java,servlet)