Bläddra i källkod

gateway过滤器

yinhao 4 år sedan
förälder
incheckning
0982bff115

+ 14 - 0
huimv-smart-common/pom.xml

@@ -159,6 +159,20 @@
             <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
 
+        <!--jwt-->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+
+        <!--fastjson-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.75</version>
+        </dependency>
+
     </dependencies>
 
 

+ 119 - 0
huimv-smart-common/src/main/java/com/huimv/common/utils/AppJwtUtil.java

@@ -0,0 +1,119 @@
+package com.huimv.common.utils;
+
+import io.jsonwebtoken.*;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.util.*;
+
+public class AppJwtUtil {
+
+    // TOKEN的有效期一小时(S)
+    private static final int TOKEN_TIME_OUT = 3600;
+    // 加密KEY
+    private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
+    // 最小刷新间隔(S)
+    private static final int REFRESH_TIME = 300;
+
+    // 生产ID
+    public static String getToken(Long id){
+        Map<String, Object> claimMaps = new HashMap<>();
+        claimMaps.put("id",id);
+        long currentTime = System.currentTimeMillis();
+        return Jwts.builder()
+                .setId(UUID.randomUUID().toString())
+                .setIssuedAt(new Date(currentTime))  //签发时间
+                .setSubject("system")  //说明
+                .setIssuer("heima") //签发者信息
+                .setAudience("app")  //接收用户
+                .compressWith(CompressionCodecs.GZIP)  //数据压缩方式
+                .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
+                .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳
+                .addClaims(claimMaps) //cla信息
+                .compact();
+    }
+
+    /**
+     * 获取token中的claims信息
+     *
+     * @param token
+     * @return
+     */
+    private static Jws<Claims> getJws(String token) {
+            return Jwts.parser()
+                    .setSigningKey(generalKey())
+                    .parseClaimsJws(token);
+    }
+
+    /**
+     * 获取payload body信息
+     *
+     * @param token
+     * @return
+     */
+    public static Claims getClaimsBody(String token) {
+        try {
+            return getJws(token).getBody();
+        }catch (ExpiredJwtException e){
+            return null;
+        }
+    }
+
+    /**
+     * 获取hearder body信息
+     *
+     * @param token
+     * @return
+     */
+    public static JwsHeader getHeaderBody(String token) {
+        return getJws(token).getHeader();
+    }
+
+    /**
+     * 是否过期
+     *
+     * @param claims
+     * @return -1:有效,0:有效,1:过期,2:过期
+     */
+    public static int verifyToken(Claims claims) {
+        if(claims==null){
+            return 1;
+        }
+        try {
+            claims.getExpiration()
+                    .before(new Date());
+            // 需要自动刷新TOKEN
+            if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
+                return -1;
+            }else {
+                return 0;
+            }
+        } catch (ExpiredJwtException ex) {
+            return 1;
+        }catch (Exception e){
+            return 2;
+        }
+    }
+
+    /**
+     * 由字符串生成加密key
+     *
+     * @return
+     */
+    public static SecretKey generalKey() {
+        byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
+        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
+        return key;
+    }
+
+    public static void main(String[] args) {
+       /* Map map = new HashMap();
+        map.put("id","11");*/
+        System.out.println(AppJwtUtil.getToken(1102L));
+        Jws<Claims> jws = AppJwtUtil.getJws("eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQqEMAwA_5KzhURNt_qb1KZYQSi0wi6Lf9942NsMw3zh6AVW2DYmDGl2WabkZgreCaM6VXzhFBfJMcMARTqsxIG9Z888QLui3e3Tup5Pb81013KKmVzJTGo11nf9n8v4nMUaEY73DzTabjmDAAAA.4SuqQ42IGqCgBai6qd4RaVpVxTlZIWC826QA9kLvt9d-yVUw82gU47HDaSfOzgAcloZedYNNpUcd18Ne8vvjQA");
+        Claims claims = jws.getBody();
+        System.out.println(claims.get("id"));
+
+    }
+
+}

+ 4 - 0
huimv-smart-gateway/src/main/java/com/huimv/gateway/HuimvSmartGatewayApplication.java

@@ -7,6 +7,10 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 
+/**
+ * @author huimv
+ * @since 2021/4/29
+ */
 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class, ExceptionAutoConfiguration.class, Knife4jConfiguration.class})
 public class HuimvSmartGatewayApplication {
 

+ 127 - 0
huimv-smart-gateway/src/main/java/com/huimv/gateway/filter/SignVerifyFilter.java

@@ -0,0 +1,127 @@
+package com.huimv.gateway.filter;
+
+import com.alibaba.fastjson.JSONObject;
+import com.huimv.gateway.utils.JdkSignatureUtil;
+import io.netty.buffer.ByteBufAllocator;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.core.io.buffer.NettyDataBufferFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.net.URI;
+import java.net.URLDecoder;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * @Author yinhao
+ * @Date 2021/5/6 9:26
+ * @Description
+ */
+@Slf4j
+@Component
+public class  SignVerifyFilter implements GlobalFilter, Ordered {
+
+    @Value("${huimv.public-key}")
+    private String PUBLIC_KEY;
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+
+        ServerHttpRequest serverHttpRequest = exchange.getRequest();
+        ServerHttpResponse response = exchange.getResponse();
+        String method = serverHttpRequest.getMethodValue();
+        if ("POST".equals(method)) {
+            //从请求里获取Post请求体
+            String bodyStr = resolveBodyFromRequest(serverHttpRequest);
+            //TODO 得到Post请求的请求参数后,做你想做的事
+            JSONObject jsonObject = JSONObject.parseObject(bodyStr);
+            String sign = jsonObject.getString("sign");
+            try {
+                String decode = URLDecoder.decode(sign, "UTF-8");
+                StringBuilder sb = new StringBuilder();
+                jsonObject.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach(entry ->
+                        sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&"));
+                String paramStr = sb.toString().substring(0, sb.length() - 1);
+                boolean result = JdkSignatureUtil.verifySignature(PUBLIC_KEY,decode,paramStr);
+                if (!result) {
+                    response.setStatusCode(HttpStatus.FORBIDDEN);
+                    return response.setComplete();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
+
+            //下面的将请求体再次封装写回到request里,传到下一级,否则,由于请求体已被消费,后续的服务将取不到值
+            URI uri = serverHttpRequest.getURI();
+            ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();
+            DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
+            Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
+
+            request = new ServerHttpRequestDecorator(request) {
+                @Override
+                public Flux<DataBuffer> getBody() {
+                    return bodyFlux;
+                }
+            };
+            //封装request,传给下一级
+            return chain.filter(exchange.mutate().request(request).build());
+        } else if ("GET".equals(method)) {
+            Map requestQueryParams = serverHttpRequest.getQueryParams();
+            //TODO 得到Get请求的请求参数后,做你想做的事
+
+            return chain.filter(exchange);
+        }
+        return chain.filter(exchange);
+    }
+
+    /**
+     * 从Flux<DataBuffer>中获取字符串的方法
+     *
+     * @return 请求体
+     */
+    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
+        //获取请求体
+        Flux<DataBuffer> body = serverHttpRequest.getBody();
+
+        AtomicReference<String> bodyRef = new AtomicReference<>();
+        body.subscribe(buffer -> {
+            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
+            DataBufferUtils.release(buffer);
+            bodyRef.set(charBuffer.toString());
+        });
+        //获取request body
+        return bodyRef.get();
+    }
+
+    private DataBuffer stringBuffer(String value) {
+        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
+
+        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
+        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
+        buffer.write(bytes);
+        return buffer;
+    }
+
+    @Override
+    public int getOrder() {
+        return 1;
+    }
+}

+ 76 - 0
huimv-smart-gateway/src/main/java/com/huimv/gateway/filter/TokenSignFilter.java

@@ -0,0 +1,76 @@
+package com.huimv.gateway.filter;
+
+
+import com.huimv.gateway.utils.JwtUtils;
+import io.jsonwebtoken.Claims;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+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.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * @Author yinhao
+ * @Date 2021/4/30 18:37
+ * @Description
+ */
+@Slf4j
+@Component
+public class TokenSignFilter implements GlobalFilter, Ordered {
+
+    @Autowired
+    private JwtUtils jwtUtils;
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        ServerHttpResponse response = exchange.getResponse();
+
+        //登录接口不参与过滤
+        if (request.getURI().getPath().contains("/login")) {
+            return chain.filter(exchange);
+        }
+
+        String token = request.getHeaders().getFirst(jwtUtils.getHeader());
+        if (StringUtils.isEmpty(token)) {
+            response.setStatusCode(HttpStatus.UNAUTHORIZED);
+            return response.setComplete();
+        }
+
+        try {
+
+            Claims claims = jwtUtils.getClaimByToken(token);
+            if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {
+                response.setStatusCode(HttpStatus.UNAUTHORIZED);
+                return response.setComplete();
+                //throw new RRException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());
+            }
+
+            Long id = Long.parseLong(claims.getSubject());
+            log.info("find userId: {} from uri: {}", id, request.getURI());
+            ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> httpHeaders.add("userId", id + "")).build();
+            exchange.mutate().request(serverHttpRequest).build();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            //向客户端返回错误提示信息
+            response.setStatusCode(HttpStatus.UNAUTHORIZED);
+            return response.setComplete();
+        }
+
+        return chain.filter(exchange);
+    }
+
+
+    @Override
+    public int getOrder() {
+        return 0;
+    }
+}

+ 78 - 0
huimv-smart-gateway/src/main/java/com/huimv/gateway/utils/JdkSignatureUtil.java

@@ -0,0 +1,78 @@
+package com.huimv.gateway.utils;
+
+import java.security.*;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+/**
+ * @description RSA签名工具类
+ * 
+ * @author admin
+ *
+ */
+public class JdkSignatureUtil {
+    
+    private final static String RSA = "RSA";
+    
+    private final static String MD5_WITH_RSA = "MD5withRSA";
+    
+    /**
+     * 执行签名
+     * 
+     * @param rsaPrivateKey  私钥
+     * @param toSignStr  参数内容
+     * @return  签名后的内容,base64后的字符串
+     * @throws NoSuchAlgorithmException
+     * @throws InvalidKeySpecException
+     * @throws InvalidKeyException
+     * @throws SignatureException
+     */
+    public static String executeSignature(String rsaPrivateKey, String toSignStr) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
+        // base64解码私钥
+        byte[] decodePrivateKey = Base64.getDecoder().decode(rsaPrivateKey.replace("\r\n", ""));
+        
+        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decodePrivateKey);
+        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
+        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
+        Signature signature = Signature.getInstance(MD5_WITH_RSA);
+        signature.initSign(privateKey);
+        signature.update(toSignStr.getBytes());
+        
+        // 生成签名
+        byte[] result = signature.sign();
+        
+        // base64编码签名为字符串
+        return Base64.getEncoder().encodeToString(result);
+    }
+    
+    /**
+     * 验证签名
+     * 
+     * @param rsaPublicKey  公钥
+     * @param sign  签名
+     * @param src  参数内容
+     * @return  验证结果
+     * @throws NoSuchAlgorithmException
+     * @throws InvalidKeySpecException
+     * @throws InvalidKeyException
+     * @throws SignatureException
+     */
+    public static boolean verifySignature(String rsaPublicKey, String sign, String src) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
+        // base64解码公钥
+        byte[] decodePublicKey = Base64.getDecoder().decode(rsaPublicKey.replace("\r\n", ""));
+        
+        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(decodePublicKey);
+        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
+        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
+        Signature signature = Signature.getInstance(MD5_WITH_RSA);
+        signature.initVerify(publicKey);
+        signature.update(src.getBytes());
+        // base64解码签名为字节数组
+        byte[] decodeSign = Base64.getDecoder().decode(sign);
+
+        // 验证签名
+        return signature.verify(decodeSign);
+    }
+}

+ 89 - 0
huimv-smart-gateway/src/main/java/com/huimv/gateway/utils/JwtUtils.java

@@ -0,0 +1,89 @@
+package com.huimv.gateway.utils;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+/**
+ * jwt工具类
+ *
+ * @author huimv
+ */
+@ConditionalOnMissingBean(JwtUtils.class)
+@ConfigurationProperties(prefix = "huimv.jwt")
+@Component
+public class JwtUtils {
+    private Logger logger = LoggerFactory.getLogger(getClass());
+
+    private String secret;
+    private long expire;
+    private String header;
+
+    /**
+     * 生成jwt token
+     */
+    public String generateToken(long userId) {
+        Date nowDate = new Date();
+        //过期时间
+        Date expireDate = new Date(nowDate.getTime() + expire * 1000);
+
+        return Jwts.builder()
+                .setHeaderParam("typ", "JWT")
+                .setSubject(userId+"")
+                .setIssuedAt(nowDate)
+                .setExpiration(expireDate)
+                .signWith(SignatureAlgorithm.HS512, secret)
+                .compact();
+    }
+
+    public Claims getClaimByToken(String token) {
+        try {
+            return Jwts.parser()
+                    .setSigningKey(secret)
+                    .parseClaimsJws(token)
+                    .getBody();
+        }catch (Exception e){
+            logger.debug("validate is token error ", e);
+            return null;
+        }
+    }
+
+    /**
+     * token是否过期
+     * @return  true:过期
+     */
+    public boolean isTokenExpired(Date expiration) {
+        return expiration.before(new Date());
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+
+    public long getExpire() {
+        return expire;
+    }
+
+    public void setExpire(long expire) {
+        this.expire = expire;
+    }
+
+    public String getHeader() {
+        return header;
+    }
+
+    public void setHeader(String header) {
+        this.header = header;
+    }
+}

+ 11 - 0
huimv-smart-gateway/src/main/resources/application.yml

@@ -40,3 +40,14 @@ spring:
 
 server:
   port: 88
+
+
+huimv:
+  public-key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIvJs0JQAJJZ9VYKyo69ByDYzDpCTjyu/bUcw+7SI1PqZtliUYmjUmaQdzKigCjpQH2Sq3x+VSmfnprZhP6COYkCAwEAAQ==
+  # APP模块,是通过jwt认证的,如果要使用APP模块,则需要修改【加密秘钥】
+  jwt:
+    # 加密秘钥
+    secret: f4e2e52034348f86b67cde581c0f9eb5[www.huimv.com]
+    # token有效时长,7天,单位秒
+    expire: 604800
+    header: token

+ 4 - 2
renren-fast/src/main/java/io/renren/modules/app/utils/JwtUtils.java

@@ -13,6 +13,7 @@ import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.SignatureAlgorithm;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
@@ -21,9 +22,10 @@ import java.util.Date;
 /**
  * jwt工具类
  *
- * @author Mark sunlightcs@gmail.com
+ * @author huimv
  */
-@ConfigurationProperties(prefix = "renren.jwt")
+@ConditionalOnMissingBean(JwtUtils.class)
+@ConfigurationProperties(prefix = "huimv.jwt")
 @Component
 public class JwtUtils {
     private Logger logger = LoggerFactory.getLogger(getClass());

+ 3 - 1
renren-fast/src/main/resources/application.yml

@@ -73,10 +73,12 @@ renren:
     open: false
   shiro:
     redis: false
+
+huimv:
   # APP模块,是通过jwt认证的,如果要使用APP模块,则需要修改【加密秘钥】
   jwt:
     # 加密秘钥
-    secret: f4e2e52034348f86b67cde581c0f9eb5[www.renren.io]
+    secret: f4e2e52034348f86b67cde581c0f9eb5[www.huimv.com]
     # token有效时长,7天,单位秒
     expire: 604800
     header: token