说说SpringBoot以及微服务中的几种鉴权方式

2023-12-15 11:39:56 浏览数 (5)

AOP鉴权 或 拦截器/过滤器鉴权

这个方法是利用Spring-AOP的机制,进行鉴权,可以使用execution进行鉴权

也可以使用@annotion进行鉴权,方式很多

这里再来说说网关鉴权

GateWay鉴权

我们可以在网关层面进行鉴权,在这个时候防止流量下放

在最开始的时候接口进入鉴权白名单,我们可以直接放行,在各个服务进行鉴权

代码语言:javascript复制
 @Around("@annotation(authCheck)")
    public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
        // 获取管理信息
        String mustRole = authCheck.mustRole();
        List<String> anyRole = Arrays.stream(authCheck.anyRole())
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.toList());
        Boolean checkLogin = authCheck.checkLogin();
        // 获取请求信息
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        // 获取路由信息
        String method = request.getMethod().toUpperCase(Locale.ROOT);
        String requestURI = request.getRequestURI();
        String contextPath = request.getContextPath();
        // 判断在gateway上是否鉴权
        String authed = request.getHeader("Authed");
        if (!"gateway".equals(authed)) {
            log.info("n新注册到认证中心的接口,路由信息如下:n"  
                    "method: [{}]n"  
                    "requestURI: [{}]n"  
                    "contextPath:  [{}]",method,requestURI,contextPath);
            // 通过rpc获取 认证中心 的 route,曾经是否注册过
                Auth one = authService.getOne(method, requestURI, contextPath);
            // 如果是新增的路由,那么就加上
                if (ObjectUtils.isEmpty(one)) {
                    one = new Auth()
                            .setMethod(method)
                            .setUri(requestURI)
                            .setRoute(contextPath)
                            .setIsDef(true)
                            .setOpen(true);
                }
            // 必须有该权限才通过
                if (StringUtils.isNotBlank(mustRole)) {
                    if (!anyRole.contains(mustRole)) {
                        anyRole.add(mustRole);
                    }
                }
                one.setOpen(true).setIsDef(true).setAuthRoles(anyRole).setCheckLogin(checkLogin).setIsDef(true);    //设置成真值,一样的交给网关执行
                boolean state = authService.saveOrUpdate(one);
            log.info("认证中心保存更新状态state: {}",state);
            // 网关没有鉴权,那么在这里鉴权
            log.info("AOP鉴权开始");
            String token = request.getHeader(CommonConstant.TOKEN_HEADER);
            User userFromToken = AuthUtil.getUserFromToken(token);
            if (ObjectUtils.isEmpty(userFromToken)) {
                throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR, "未登录或者状态过期,请能够重新登录!");
            }
            if (checkLogin==false&&!anyRole.contains(userFromToken.getUserRole())||UserConstant.BAN_ROLE.equals(userFromToken.getUserRole())) {
                throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
            }
        }
        else{
            //如果已经鉴权,通过流量染色
            log.info("已在gateway进行鉴权,即将进入业务层,路由信息如下n"  
                    "method: [{}]n"  
                    "requestURI: [{}]n"  
                    "contextPath:  [{}]",method,requestURI,contextPath);
        }
        // 进行响应日志记录
        return handelResponse(joinPoint);
    }

鉴权之前,我们通过RPC注册到鉴权中心,后续通过gateway的时候先流量脱色,再鉴权,最后再染色

代码语言:javascript复制
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 请求
        ServerHttpRequest request = exchange.getRequest();
        // 响应
        ServerHttpResponse response = exchange.getResponse();
        // 获取路由路径
        RequestPath path = request.getPath();
        log.info("forward path:{}",path);
        // 这里是一个重要的鉴权逻辑,防止伪造染色
        exchange=exchange.mutate().request(request.mutate().headers(httpHeaders -> httpHeaders.remove("Authed")  ).build()).build();
        String requestMethod = request.getMethod().name();
        // 如果不是get请求,那么进行重放检测
        if(!HttpMethod.GET.matches(requestMethod)){
            Mono<Void> voidMono = doReplayDetection(exchange);
            if (ObjectUtils.isNotEmpty(voidMono)){
                return voidMono;
            }
        }
        request.mutate().headers(headers -> headers.add("x-forwarded-for",request.getRemoteAddress().getAddress().getHostAddress()));
        //通过 全局鉴权容器进行遍历、匹配
        for(GlobalContainer.Route v:GlobalContainer.authRouteList){
            String method = v.getMethod();
            String url = v.getUrl();
            //当 当前转发路由、方法 和 需要鉴权的路由方法一直时,进行鉴权
            log.info("url:{} path:{}",url,path);
            log.info("method:{} name:{}",method,requestMethod);
            if (url.equals(path.value())&&method.equals(requestMethod)){
                // 进行鉴权操作
                // 通过Token获取用户信息
                User currentUser = AuthUtil.getUserFromToken(exchange.getRequest().getHeaders().getFirst("Authorization"));
                if (ObjectUtils.isEmpty(currentUser)){
                    return handleOther(null,
                            ResultUtils.error(ErrorCode.FORBIDDEN_ERROR,"Token无效,请重新登录"),
                            response);
                }
                // 是否只需要检查登录状态
                Boolean checkLogin = v.getCheckLogin();
                log.info("v.getCheckLogin():{}", checkLogin);
                if (BooleanUtils.isTrue(checkLogin)){
                    return handleResponse(exchange, chain);
                }
                // 获取用户权限信息
                String userRole = currentUser.getUserRole();
                // 被BAN了的用户
                if (UserConstant.BAN_ROLE.equals(userRole)){
                    return handleNoAuth(response);
                }
                log.info("userRole:{} roles:{} user:{}",userRole,v.getRole(),currentUser);
                boolean contains = v.getRole().contains(userRole);
                if (!contains){
                   return handleNoAuth(response);
                }
                // 鉴权完毕,修改请求头
                ServerHttpRequest build = request.mutate().headers(header-> header.add("Authed","gateway")).build();
                exchange=exchange.mutate().request(build).build();
                break;
            }
        }
        // 交给响应事件
        return handleResponse(exchange, chain);
    }
​

继承HandlerMethodArgumentResolver鉴权

代码语言:javascript复制
public interface HandlerMethodArgumentResolver {
    boolean supportsParameter(MethodParameter var1);
​
    @Nullable
    Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}

这个类的作用主要是用于方法拦截

第一个supportsParmeter是拦截条件

第二个resolveArgument是执行的计划,满足supportsParmeter的话就执行resolveArgument,执行完后我们会把返回值给到满足条件的参数上面

然后我们对编写的类进行一个注册

代码语言:javascript复制
@Configuration
public class WebConfig  extends WebMvcConfigurerAdapter{
    @Autowired
    类 对象;
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(对象);
    }
}

所以说,可玩性很高,我们可以判断一个注解是否在某个参数上,而那个参数是用户类型,然后我们可以在resolveArgument中进行鉴权,并且将鉴权后的结果(.e.g: 登录用户对象 等)返回给参数

1 人点赞