token可以是后端api的访问依据,一般绝大多数时候,前端要访问后端的api,后端都要求前端请求需要携带一个有效的token,这个token用于用户的身份校验,通过了校验,后端才会向前端返回数据,进行相应的操作,如果没有通过校验,后端则做其他处理。
之前后端代码并没有对前端请求进行token校验,之前的后端src/main/resources/application.properties的文件
spring.datasource.url=jdbc:mysql://localhost:3306/server?serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=1234
spring.sql.init.mode=always
spring.sql.init.encoding=UTF-8
interceptor.key=abcdefghijklmnopqrstuvwxyz0123456789
#NONE:不拦截
interceptor.mode=NONE
#拦截白名单,因为在登录的时候是没有token的,所以没有进行拦截校验
interceptor.whiteList=/api/loginJwt,/api/loginSession
logging.level.com.lala.mapper=debug
spring.jackson.default-property-inclusion=non_null
mybatis.configuration.map-underscore-to-camel-case=true
src/main/java/com/lala/controller/LoginInterceptor.java文件
package com.lala.controller;
import com.lala.service.SecretKeyService;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.SignatureException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.stream.Stream;
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private SecretKeyService secretKeyService;
//@Value将外部的值动态注入到Bean中
/*这段代码是Spring框架中的一部分,用于从属性文件中注入值。@Value 注解用于将属性文件中的值注入到字段中。
在这个例子中,它试图从属性文件中获取一个名为 interceptor.mode 的值,
并把它赋给 InterceptorMode 类型的 interceptorMode 字段。如果没有找到这个属性,
那么 InterceptorMode.NONE 将被赋给 interceptorMode。*/
@Value("${interceptor.mode}")
private final InterceptorMode interceptorMode = InterceptorMode.NONE;
@Value("${interceptor.whiteList}")
private String[] interceptorWhiteList;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//浏览器在跨域的时候,可能发送一个OPTIONS请求,对这个请求不做拦截,或者是白名单的请求也不做拦截
if ("OPTIONS".equals(request.getMethod()) ||
Stream.of(interceptorWhiteList).anyMatch(p -> p.equals(request.getRequestURI()))) {
return true;
}
switch (interceptorMode) {
case JWT -> {
String token = request.getHeader("Authorization");
System.out.println("token=" + token);
if (token == null) {
//未携带 token
throw new Exception401("未携带 token");
}
try {
SecretKey secretKey = secretKeyService.getSecretKey();
//根据secretKey生成token
Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token);
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException |
IllegalArgumentException e) {
//校验 token 失败
throw new Exception401("校验 token 失败");
}
}
case SESSION -> {
Object user = request.getSession().getAttribute("user");
if (user == null) {
//校验 session 失败
throw new Exception401("校验 session 失败");
}
}
case NONE -> {
return true;
}
}
return true;
}
enum InterceptorMode {
NONE, JWT, SESSION;
}
}
修改src/main/resources/application.properties文件
#NONE:不拦截,JWT:拦截JWT,SESSION:拦截SESSION
interceptor.mode=JWT
src/main/java/com/lala/AppForServer4.java文件内容如下
package com.lala;
import com.lala.controller.LoginInterceptor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
public class AppForServer4 implements WebMvcConfigurer {
@Bean
public LoginInterceptor loginInterceptor() {
return new LoginInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截以api打头的所有请求
registry.addInterceptor(loginInterceptor()).addPathPatterns("/api/**");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:7070")
.allowedMethods("OPTIONS", "GET", "POST", "PUT", "DELETE")
.allowCredentials(true)//是否发送cookie
.allowedHeaders("*")
.allowedOriginPatterns("*")//意味着任何来源都可以访问该应用
.allowCredentials(true);
}
public static void main(String[] args) {
SpringApplication.run(AppForServer4.class, args);
}
}
这个时候登录就会做token校验
校验token失败是因为在登录的时候发送了下面的请求
const { runAsync: menu } = useRequest<AxiosRespMenuAndRoute, string[]>((username) => _axios.get(`/api/menu/${username}`), { manual: true })
这个请求没有携带合法的token
登录的时候还发送了另一个请求
const { runAsync: login } = useRequest<AxiosRespToken, LoginDto[]>((dto) => _axios.post('/api/loginJwt', dto), { manual: true })
这个请求在白名单中,被放行了
要在登录的时候携带合法的token,可以在src\api\request.ts文件中添加请求拦截器,在拦截器中可以对请求进行统一的处理,代码如下
import { message } from "ant-design-vue";
import axios from 'axios'
import {serverToken} from '../router/a6router'
const _axios = axios.create({
baseURL: import.meta.env.VITE_BACKEND_BASE_URL,
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
});
_axios.interceptors.request.use(
(config)=>{
// 如果登录成功
if(serverToken.value){
config.headers = {
// 因为在登录的时候把token保存在本地存储里面了,可以从这里面获取token的值
Authorization: serverToken.value
}
}
return config
},
(error)=>{
// 这里是把异常抛给调用者
return Promise.reject(error)
}
)
_axios.interceptors.response.use((res) => {
if(res.data.message){
message.success(res.data.message)
}
return res;
});
export default _axios
再登陆
登录成功,可以看到admin请求携带了一个Authorization头,里面是token值