| 知乎专栏 |
WebMvcConfigurerAdapter
package mis.config;
import mis.interceptor.SpringMVCInterceptor;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebAppConfig extends WebMvcConfigurerAdapter {
/**
* 配置拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SpringMVCInterceptor()).addPathPatterns("/**");
}
}
HandlerInterceptor
package mis.interceptor;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SpringMVCInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
if(request.getServletPath().startsWith("/index") || request.getServletPath().startsWith("/login")) {
return true;
}
String username = (String)request.getSession().getAttribute("userName");
if (StringUtils.isEmpty(username)){
response.sendRedirect(request.getContextPath() + "/index");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
package cn.netkiller.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenPass {
boolean required() default true;
}
package cn.netkiller.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenVerification {
boolean required() default true;
}
package cn.netkiller.component;
import cn.netkiller.annotation.TokenPass;
import cn.netkiller.annotation.TokenVerification;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import java.lang.reflect.Method;
@Slf4j
@Component
public class TokenHandlerInterceptor implements HandlerInterceptor {
// 返回 true 表示继续向下执行,返回 false 表示中断后续操作
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");// 从 http 请求头中取出 token
// 如果不是映射到方法直接通过
if (!(handler instanceof HandlerMethod handlerMethod)) {
return true;
}
Method method = handlerMethod.getMethod();
//检查方法是否有TokenPass注解,有则跳过认证,直接通过
if (method.isAnnotationPresent(TokenPass.class)) {
TokenPass tokenPass = method.getAnnotation(TokenPass.class);
if (tokenPass.required()) {
return true;
}
}
//检查 TokenVerification 需要用户权限的注解
if (method.isAnnotationPresent(TokenVerification.class)) {
TokenVerification tokenVerification = method.getAnnotation(TokenVerification.class);
if (tokenVerification.required()) {
// 执行认证
if (token == null) {
throw new RuntimeException("无 token,请重新登录");
}
// token 校验逻辑写在这里
}
}
throw new RuntimeException("没有权限");
}
// 目标方法执行后, 该方法在控制器处理请求方法调用之后、解析视图之前执行, 可以通过此方法对请求域中的模型和视图做进一步修改
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.debug("postHandle");
}
// 页面渲染后, 该方法在视图渲染结束后执行, 可以通过此方法实现资源清理、记录日志信息等工作
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.debug("afterCompletion");
}
}
package cn.netkiller.config;
import cn.netkiller.component.TokenHandlerInterceptor;
import jakarta.annotation.Resource;
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 TokenWebMvcConfigurer implements WebMvcConfigurer {
@Resource
private TokenHandlerInterceptor tokenHandlerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册拦截器,并设置拦截的请求路径
//addPathPatterns为拦截此请求路径的请求
//excludePathPatterns为不拦截此路径的请求
registry.addInterceptor(tokenHandlerInterceptor)
.addPathPatterns("/mock/*")
.excludePathPatterns("/device/*")
.excludePathPatterns("/callback/*");
}
}
package cn.netkiller.controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Map;
@ControllerAdvice
public class GloablExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public Object handleException(Exception e) {
String msg = e.getMessage();
if (msg == null || msg.equals("")) {
msg = "服务器出错";
}
return Map.of("message", msg, "status", 500);
}
}
package cn.netkiller.controller;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/mock")
@Slf4j
public class HomeController {
//无需 token 可以访问
@TokenPass
@GetMapping("/neo")
public String neo() {
return "https://www.netkiller.cn";
}
// 需要 Token 才能访问
@TokenVerification
@GetMapping("/netkiller")
public String netkiller() {
return "https://www.netkiller.cn";
}
}
neo@MacBook-Pro-M2 ~> curl -X 'GET' 'http://localhost:8080/mock/neo'
https://www.netkiller.cn⏎
neo@MacBook-Pro-M2 ~> curl -X 'GET' 'http://localhost:8080/mock/netkiller'
{"message":"无 token,请重新登录","status":500}⏎
neo@MacBook-Pro-M2 ~> curl -X 'GET' 'http://localhost:8080/mock/flux' -H 'token: 8A8691CF-DC81-4477-84D8-DC5CDDF98568'
https://www.netkiller.cn⏎
package cn.netkiller.config;
import cn.netkiller.annotation.TokenPass;
import cn.netkiller.component.JwtTokenComponent;
import com.auth0.jwt.interfaces.DecodedJWT;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import java.lang.reflect.Method;
import java.util.Base64;
@Slf4j
@Component
public class TokenHandlerInterceptor implements HandlerInterceptor {
@Autowired
private JwtTokenComponent jwtTokenComponent;
// @Autowired
// private DeviceService deviceService;
// 返回 true 表示继续向下执行,返回 false 表示中断后续操作
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Class clazz = handler.getClass();
// log.info(clazz.getSimpleName());
// 如果不是映射到方法直接通过
if (!(handler instanceof HandlerMethod handlerMethod)) {
return true;
}
Method method = handlerMethod.getMethod();
if (method.getDeclaringClass().isAnnotationPresent(TokenPass.class)) {
TokenPass tokenPass = method.getDeclaringClass().getAnnotation(TokenPass.class);
if (tokenPass.required()) {
return true;
}
// h.getMethod().getDeclaringClass().getAnnotation(MyOperation.class);
}
//检查方法是否有TokenPass注解,有则跳过认证,直接通过
if (method.isAnnotationPresent(TokenPass.class)) {
TokenPass tokenPass = method.getAnnotation(TokenPass.class);
if (tokenPass.required()) {
return true;
}
}
//检查 TokenVerification 需要用户权限的注解
// if (method.isAnnotationPresent(TokenVerification.class)) {
// TokenVerification tokenVerification = method.getAnnotation(TokenVerification.class);
// if (tokenVerification.required()) {
// //
// }
// }
String authorization = request.getHeader("Authorization");// 从 http 请求头中取出 token
log.info("Authorization: " + authorization);
if (authorization != null && authorization.startsWith("Bearer ")) {
String jwtToken = authorization.substring(7);
// token 校验逻辑写在这里
try {
// Validate or process the JWT token here
DecodedJWT jwt = jwtTokenComponent.verifier(jwtToken);
// log.debug("JWT Token: " + jwtToken);
log.debug("Token decode Header: " + new String(Base64.getDecoder().decode(jwt.getHeader())) + " Payload: " + new String(Base64.getDecoder().decode(jwt.getPayload())));
} catch (Exception e) {
e.printStackTrace();
// throw new AigcException.AigcTokenException(e.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid JWT");
}
} else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Missing or invalid Authorization header");
// throw new AigcException.AigcTokenException("token error");
// log.info("Token: " + token);
}
// Map<String, String> pathVariable = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
// if (!pathVariable.isEmpty() && pathVariable.containsKey("device")) {
//// log.debug("pathVariable: " + pathVariable);
// Optional<Device> device = deviceService.findByName(pathVariable.get("device"));
// device.orElseThrow(() -> new AigcException.AigcDeviceException(String.format("设备 %s 不存在", pathVariable.get("device"))));
// }
// Set<GrantedAuthority> authorities = new HashSet<>();
// authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
// authorities.add(new SimpleGrantedAuthority("ROLE_WATCH"));
// authorities.add(new SimpleGrantedAuthority("ROLE_PICTURE"));
//
// UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("netkiller", null, authorities);
// if (!authenticationToken.isAuthenticated()) {
// throw new RuntimeException("没有权限");
// }
// authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// SecurityContextHolder.getContext().setAuthentication(authenticationToken);
return true;
}
// 目标方法执行后, 该方法在控制器处理请求方法调用之后、解析视图之前执行, 可以通过此方法对请求域中的模型和视图做进一步修改
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// log.debug("postHandle");
}
// 页面渲染后, 该方法在视图渲染结束后执行, 可以通过此方法实现资源清理、记录日志信息等工作
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// log.debug("afterCompletion");
}
}
package cn.netkiller.config;
import jakarta.servlet.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
@Slf4j
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info(" myfilter init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("myfilter execute");
}
@Override
public void destroy() {
log.info("myfilter destroy");
}
}
package cn.netkiller.config;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalCorsFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
HttpServletRequest req = (HttpServletRequest) request;
// 配置CORS响应头
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.setHeader("Access-Control-Max-Age", "3600");
// 处理预检请求
if ("OPTIONS".equalsIgnoreCase(req.getMethod())) {
res.setStatus(HttpServletResponse.SC_OK);
return;
}
chain.doFilter(request, response);
}
}
package cn.netkiller.config.filter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class CustomExceptionFilter extends OncePerRequestFilter {
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (Exception e) {
resolver.resolveException(request, response, null, e);
}
}
}
Map<String, String> pathVars = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
log.info(pathVars.toString());
@Configuration
public class CorsConfiguration
{
@Bean
public WebMvcConfigurer corsConfigurer()
{
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
};
}
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
};
}
@Configuration
@EnableWebMvc
public class CorsConfiguration extends WebMvcConfigurerAdapter
{
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "POST");
}
}
@Configuration
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/info/**")
.allowedOrigins("http://localhost:8080", "http://localhost:8000")
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.allowedHeaders("X-Auth-Token", "Content-Type")
.exposedHeaders("custom-header1", "custom-header2")
.allowCredentials(false)
.maxAge(4800);
}
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// 为所有路径配置跨域(也可指定具体路径如/token/**)
registry.addMapping("/**")
// 允许的跨域源(生产环境指定具体域名,如https://example.com)
.allowedOriginPatterns("*") // Spring 5.3+推荐用allowedOriginPatterns,替代allowedOrigins
// .allowedOrigins("https://example.com") // Spring 5.3前的写法,不支持*且携带Cookie时需指定具体域名
// 允许的请求方法
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
// 允许的请求头
.allowedHeaders("*")
// 允许携带Cookie
.allowCredentials(false)
// 预检请求缓存时间
.maxAge(3600);
}