Newer
Older
newfiber_standard_algae / newfiber-gateway / src / main / java / com / newfiber / gateway / filter / AuthFilter.java
@xiongkai xiongkai on 17 Jun 7 KB 初始化
package com.newfiber.gateway.filter;

import com.alibaba.fastjson2.JSONArray;
import com.newfiber.common.core.constant.HttpStatus;
import com.newfiber.common.core.constant.SecurityConstants;
import com.newfiber.common.core.constant.TokenConstants;
import com.newfiber.common.core.domain.ClientInfo;
import com.newfiber.common.core.domain.ClientResource;
import com.newfiber.common.core.enums.EAuthenticationSource;
import com.newfiber.common.core.enums.EBoolean;
import com.newfiber.common.core.enums.EClientResourceType;
import com.newfiber.common.core.utils.JwtUtils;
import com.newfiber.common.core.utils.ServletUtils;
import com.newfiber.common.core.utils.StringUtils;
import com.newfiber.common.core.utils.ip.IpUtils;
import com.newfiber.common.redis.service.RedisService;
import com.newfiber.gateway.config.properties.IgnoreWhiteProperties;
import com.newfiber.gateway.enums.EGatewayRedisKey;
import io.jsonwebtoken.Claims;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 网关鉴权
 * 
 * @author newfiber
 */
@Component
public class AuthFilter implements GlobalFilter, Ordered{
    private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);

    // 排除过滤的 uri 地址,nacos自行添加
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;

    @Autowired
    private RedisService redisService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpRequest.Builder mutate = request.mutate();

        String url = request.getURI().getPath();
        // 跳过不需要验证的路径
        if (StringUtils.matches(url, ignoreWhite.getWhites())){
            return chain.filter(exchange);
        }
        String token = getToken(request);
        if (StringUtils.isEmpty(token)){
            return unauthorizedResponse(exchange, "令牌不能为空");
        }
        Claims claims = JwtUtils.parseToken(token);
        if (claims == null){
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
        }
        String userkey = JwtUtils.getUserKey(claims);
	    EAuthenticationSource authenticationSource = loginCheck(userkey);
        if (null == authenticationSource){
            return unauthorizedResponse(exchange, "登录状态已过期");
        }

        String userid = JwtUtils.getUserId(claims);
        String username = JwtUtils.getUserName(claims);
	    if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)){
		    return unauthorizedResponse(exchange, "令牌验证失败");
	    }

	    if(EAuthenticationSource.Client.equals(authenticationSource)){
		    Mono<Void> x = clientCheck(exchange, url, userid);
		    if (x != null){ return x; }
	    }

        // 设置用户信息到请求
	    addHeader(mutate, SecurityConstants.AUTHORIZATION_SOURCE, authenticationSource.getCode());
        addHeader(mutate, SecurityConstants.USER_KEY, userkey);
        addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
        addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
        // 内部请求来源参数清除
        removeHeader(mutate, SecurityConstants.FROM_SOURCE);
        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }

	@Nullable
	public Mono<Void> clientCheck(ServerWebExchange exchange, String url, String userid) {
		ClientInfo clientInfo = redisService.getCacheObject(EGatewayRedisKey.ClientInfo, userid);
		if(null != clientInfo && !EBoolean.True.getStringValue().equals(clientInfo.getStatus())){
			return unauthorizedResponse(exchange, "客户端未启用:" + clientInfo.getClientKey());
		}

		if(null != clientInfo && !StringUtils.isBlank(clientInfo.getIpWhiteList()) && !"*".equals(clientInfo.getIpWhiteList())){
			List<String> ipWhiteList = Arrays.asList(clientInfo.getIpWhiteList().split(","));
			String clientIp = IpUtils.getIpAddr(exchange.getRequest());
			if(!ipWhiteList.contains(clientIp)){
				return unauthorizedResponse(exchange, "客户端IP未授权:" + clientIp);
			}
		}

		JSONArray clientResources = redisService.getCacheObject(EGatewayRedisKey.ClientResource, userid);
		List<ClientResource> clientResourceList = new ArrayList<>();
		if (StringUtils.isNotNull(clientResources)){
			clientResourceList = clientResources.toList(ClientResource.class);
		}
		Optional<ClientResource> clientResourceOptional = clientResourceList.stream().filter(t -> EClientResourceType.URL.getCode().equals(t.getResourceType()) &&
			url.equals(t.getResourceContent())).findFirst();
		// 匹配全限定和通配符
		if(!clientResourceOptional.isPresent()){
			clientResourceOptional = clientResourceList.stream().filter(t -> EClientResourceType.URL.getCode().equals(t.getResourceType()) &&
				StringUtils.isMatch(t.getResourceContent(), url)).findFirst();
			if(!clientResourceOptional.isPresent()){
				return unauthorizedResponse(exchange, "客户端资源未授权:" + url);
			}
		}

		ClientResource clientResource = clientResourceOptional.get();
		if(null != clientResource.getHourFrequency()){
			String clientFrequencyKey = String.format("%s - %s", userid, url);
			Integer clientFrequency = redisService.getCacheObject(EGatewayRedisKey.ClientFrequency, clientFrequencyKey);
			if (clientFrequency == null){
				clientFrequency = 0;
			}
			if(clientFrequency >= clientResource.getHourFrequency()){
				return unauthorizedResponse(exchange, "小时内请求次数已达限流量:" + clientResource.getHourFrequency());
			}
			clientFrequency = clientFrequency + 1;
			redisService.setCacheObject(EGatewayRedisKey.ClientFrequency, clientFrequencyKey, clientFrequency, 60L, TimeUnit.MINUTES);
		}

		return null;
	}

    private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value){
        if (value == null){
            return;
        }
        String valueStr = value.toString();
        String valueEncode = ServletUtils.urlEncode(valueStr);
        mutate.header(name, valueEncode);
    }

    private void removeHeader(ServerHttpRequest.Builder mutate, String name){
        mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
    }

    private EAuthenticationSource loginCheck(String userkey){
//	    boolean islogin = redisService.hasKey(EGatewayRedisKey.Token, userkey);
//	    if (!islogin){
//		    islogin = redisService.hasKey(EGatewayRedisKey.ClientToken, userkey);
//		    if (!islogin){
//		    	return null;
//		    }else{
//		    	return EAuthenticationSource.Client;
//		    }
//	    }else{
	    	return EAuthenticationSource.User;
//	    }
    }
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg){
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
    }

    /**
     * 获取请求token
     */
    private String getToken(ServerHttpRequest request){
        String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
        // 如果前端设置了令牌前缀,则裁剪掉前缀
        if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)){
            token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
        }
        return token;
    }

    @Override
    public int getOrder(){
        return -200;
    }
}