由于是非阻塞的开发模式,所以 springboot 的拦截器不起作用了 只能用 WebFilter
这个不适用与spring clould gateway 因为用的注解判断接口权限
普通的小项目 不建议用 webflux 开发起来很复杂,一个请求全程必须全部用 异步Mono。 否则就会变成同步 的,例如你的数据库操作不支持 Mono ,你的controller service 都用了Mono 也没有,结果也是同步处理的
package com.example.springboot3demo.frame.filter;
import com.alibaba.fastjson.JSONObject;
import com.example.springboot3demo.frame.R;
import com.example.springboot3demo.frame.auth.MethodAuth;
import com.example.springboot3demo.frame.constant.SInfo;
import com.example.springboot3demo.frame.constant.SuperAdmin;
import com.example.springboot3demo.frame.constant.YesNo;
import com.example.springboot3demo.module.system.bean.SystemAccount;
import com.example.springboot3demo.module.system.service.SystemAccountService;
import com.example.springboot3demo.utils.HttpUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.Optional;
@Component
@Slf4j
@Order(1)
public class LoginFilter implements WebFilter {
/**
* WHITE_URI : 白名单
*/
private static final String[] WHITE_URI = {
"/admin/user/account/getByToken",
"/admin/news/img",
"/web/user/login"
};
/**
* 记录了所有的controller定义
*/
@Resource
private RequestMappingHandlerMapping requestMappingHandlerMapping;
@Resource
private Environment env;
@Resource
private MethodAuth methodAuth;
@Resource
private SystemAccountService serviceAccountService;
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest req = exchange.getRequest();
HttpHeaders httpHeaders = req.getHeaders();
String token = HttpUtils.getToken(req);
// 区别不同前端的用的flag
String ref = Optional.ofNullable(httpHeaders.getFirst("ref")).orElse("");
String uri = req.getURI().getPath();
if (ref.equals("1")) {
return chain.filter(exchange);
}
String contextPath = env.getProperty("spring.webflux.base-path", "");
if (uri.startsWith(contextPath)) {
uri = uri.substring(contextPath.length());
}
// 白名单过滤
for (String u : WHITE_URI) {
if (u.equals(uri)) {
return chain.filter(exchange);
}
}
// 拿到HandlerMethod 与springboot2 的拦截器里的一样
Mono<HandlerMethod> handlerMethodMono = requestMappingHandlerMapping
.getHandler(exchange).cast(HandlerMethod.class);
return handlerMethodMono.flatMap(handlerMethod -> {
R r = adminFilter(token, handlerMethod);
if (r.getCode() != 0) {
log.info("权限校验失败: {}", r.getMsg());
return returnErr(exchange, r);
}
return chain.filter(exchange);
});
}
// R 是自定义的
private R adminFilter(String token, HandlerMethod handlerMethod) {
if (token.isEmpty()) {
return R.err(SInfo.SInfo_2.N);
}
SystemAccount account = serviceAccountService.getByToken(token);
if (Objects.isNull(account)) {
return R.err(SInfo.SInfo_2.N);
}
// 判断是否为超管
if (SuperAdmin.isSuperAdmin(account.getUsername(), account.getPassword())) {
return R.ok();
}
account = serviceAccountService.getById(account.getId());
if (account == null || YesNo.YES.V == account.getIsDeleted()) {
return R.err(SInfo.SInfo_4.N);
}
if (0 == account.getStatus()) {
return R.err(SInfo.SInfo_5.N);
}
// 校验接口权限
if (!methodAuth.hasMethodAuth(handlerMethod, account)) {
return R.err(SInfo.SInfo_16.N);
}
return R.ok();
}
// Mono 全是流操作
private Mono<Void> returnErr(ServerWebExchange exchange, R r) {
ServerHttpResponse res = exchange.getResponse();
res.setStatusCode(HttpStatus.UNAUTHORIZED);
// ServerHttpResponse 没有body 只能从 DataBuffer里取 或者 放入
String resStr = JSONObject.toJSONString(r);
DataBuffer db = res.bufferFactory().wrap(resStr.getBytes(StandardCharsets.UTF_8));
return res.writeWith(Mono.just(db));
}
}
利用注解 判断接口权限
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuth {
/**
* 是否开启权限认证
*/
boolean value() default true;
/**
* 权限编码
*/
String code();
}
import com.example.springboot3demo.frame.constant.MenuType;
import com.example.springboot3demo.module.system.bean.SystemAccount;
import com.example.springboot3demo.module.system.bean.SystemMenu;
import com.example.springboot3demo.module.system.dao.SystemMenuDao;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 接口权限校验类
*/
@Component
@Slf4j
public class MethodAuth {
@Resource
private SystemMenuDao systemMenuDao;
/**
* 校验接口权限
* 没配权限注解 认为不需要校验接口权限
* 公共接口(AuthCode.COMMON)不需要校验权限
* 只有配置了权限注解 且 value=true and code!=AuthCode.COMMON 才校验接口权限,
* 校验值取至 数据库(需要的话可以改成配置文件等)
* @param handlerMethod org. springframework. web. method
* @param account 账号信息
*/
public boolean hasMethodAuth(HandlerMethod handlerMethod, SystemAccount account) {
Method method = handlerMethod.getMethod();
PreAuth annotation = getPreAuth(handlerMethod, method);
// 没配权限注解 认为不需要校验接口权限
if (annotation == null) {
return true;
}
String authCode = annotation.code();
// 公共接口不需要校验权限
if (Objects.equals(AuthCode.COMMON, authCode) || !annotation.value()) {
return true;
}
log.info("authCode:{}", authCode);
return checkAuth(authCode, account.getId());
}
/**
* 取得权限注解 顺序 方法-》当前类-》方法所在类
*/
private static PreAuth getPreAuth(HandlerMethod handlerMethod, Method method) {
PreAuth annotation = null;
// 接口上有权限注解
if (method.isAnnotationPresent(PreAuth.class)){
annotation = method.getAnnotation(PreAuth.class);
} else if (handlerMethod.getBeanType().isAnnotationPresent(PreAuth.class)) {
// 当前类上有权限注解
annotation = handlerMethod.getBeanType().getAnnotation(PreAuth.class);
} else if (method.getDeclaringClass().isAnnotationPresent(PreAuth.class)) {
// 方法所在类上有权限注解
annotation = method.getDeclaringClass().getAnnotation(PreAuth.class);
}
return annotation;
}
/**
* 查询数据库 判断该用户是否有该接口权限
*/
private boolean checkAuth(String code, Integer accountId) {
。。。
return false;
}
}