美文网首页
go语言与java语言jwt加签和验签的协同问题

go语言与java语言jwt加签和验签的协同问题

作者: 昵称被占用最扎心 | 来源:发表于2022-08-17 14:34 被阅读0次

    某项目1.0版本拿go写的,2.0版本是java写的。但是因为项目业务的原因,1.0版本和2.0版本要并行很长时间,而且,用户数据基本上都在1.0版本服务器上。故需要在2.0版本中解析1.0版本中go语言的jwt,并对jwt完成验签。

    go中的核心代码,已删除业务逻辑,只保留问题相关代码

    #conf.go 程序内容
    package conf
    //go:embed ec-512-private.pem  
    var ES256PrivateKeyPEM string  #读取pem文件
    

    #init.go 程序内容
    package service
    var initOnce sync.Once
    var privateKey *ecdsa.PrivateKey
    var PublicKey *ecdsa.PublicKey
    func init() {
        initOnce.Do(func() {
            // jwt key pair init
            tmpPrivateKey, err := jwt.ParseECPrivateKeyFromPEM([]byte(conf.ES256PrivateKeyPEM))  #读取pem文件获取私钥
            if err != nil {
                glog.Fatalf("ParseECPrivateKeyFromPEM err: %s", err.Error())
            }
            privateKey = tmpPrivateKey
            glog.Info("ES512 private key init success")
            tmpPublicKey := privateKey.PublicKey
            PublicKey = &tmpPublicKey #获取公钥
            glog.Info("ES512 public key init success")
            // wechat access token handler
            glog.Flush()
        })
    }
    

    #base.go 程序内容
    package service
    import (
        "strconv"
        "time"
        "github.com/golang-jwt/jwt"
        "github.com/golang/glog"
    )
    #定义载体结构
    type customClaims struct {
        UserID string `json:"userid"`
        jwt.StandardClaims
    }
    #生成token
    func createToken(userID int) string {
        IDStr := strconv.Itoa(userID)
        claims := &customClaims{
            IDStr,
            jwt.StandardClaims{
                IssuedAt:  time.Now().Unix(),
                ExpiresAt: time.Now().Add(time.Hour * 8640).Unix(),
                Issuer:    "author",
            },
        }
        token := jwt.NewWithClaims(jwt.SigningMethodES512, claims)
        result, err := token.SignedString(privateKey)
        if err != nil {
            glog.Errorf("signedString err: %s", err.Error())
        }
        return result
    }
    

    # jwt.go 程序内容
    package handler
    import (
        "strings"
        "git.wisetv.com.cn/fovecifer/wise-yinfa/service"
        "github.com/gin-gonic/gin"
        "github.com/golang-jwt/jwt"
        "github.com/golang/glog"
    )
    #获取token内容,token验签
    func JWTAuth() gin.HandlerFunc {
        return func(c *gin.Context) {
            token := c.GetHeader("authorization")
            if len(token) == 0 {
                c.AbortWithStatusJSON(200, gin.H{
                    "code": 401,
                    "msg":  "bad token",
                })
                return
            }
            jwtToken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
                return service.PublicKey, nil
            })
            if err != nil {
                glog.Errorf("jwt token parse err: %s", err.Error())
                c.AbortWithStatusJSON(200, gin.H{
                    "code": 401,
                    "msg":  "bad token",
                })
                return
            }
            if !jwtToken.Valid {
                glog.Errorf("jwt token not valid")
                c.AbortWithStatusJSON(200, gin.H{
                    "code": 401,
                    "msg":  "bad token",
                })
                return
            }
            if claims, ok := jwtToken.Claims.(jwt.MapClaims); ok {
                if userID, ok := claims["userid"]; ok {
                    if IDStr, ok := userID.(string); ok {
                        glog.V(5).Infof("set userId: %s", IDStr)
                        c.Set("userId", IDStr)
                        return
                    }
                }
            }
            glog.Errorf("jwt token has no userid")
            c.AbortWithStatusJSON(200, gin.H{
                "code": 401,
                "msg":  "bad token, need userid",
            })
        }
    }
    #从header中获取jwtToken
    func getBearerToken(auth string) string {
        if auth == "" {
            return ""
        }
        if !strings.HasPrefix(auth, "Bearer") && !strings.HasPrefix(auth, "bearer") {
            if auth != "" {
                return auth
            } else {
                return ""
            }
        }
        if len(auth) < 8 {
            return ""
        }
        // space
        if auth[6] != 32 {
            return ""
        }
        token := strings.Trim(auth[7:], " ")
        return token
    }
    

    go项目中的业务逻辑分析

    首先go语言不会,不过逻辑能读懂,通过读代码,go的加签逻辑如下:

    • 通过ec-512-private.pem生成ECPrivateKey作为私钥;
    • 根据ECPrivateKey获取ECPublicKey作为公钥;
    • 用户鉴权成功,则生成jwtToken,生成过程中用ECPublicKey签名;
    • 再次请求,携带jwtToken,获取token中的载体信息,并对token内容验签,确保没有被篡改。

    java要实现的业务逻辑

    • 截获用户再次请求,获取token中的载体信息,并对token内容验签,然后进行2.0版本的业务。

    需要解决的问题

    • 证书有源文件,私钥与公钥需要重新生成,并且与go的生成方式保持一致。
    • 生成的公钥要能正常验签go生成的jwtToken。

    问题解决方案

    解决过程中查询大量文档,为了能与go实现同样的证书生成逻辑,并能成功对go生成的jwtToken进行校验,测试了n中方法。最终测试通过的解决方案如下:

    # pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.sunchen.asc</groupId>
        <artifactId>jwtTest</artifactId>
        <version>1.0-SNAPSHOT</version>
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <spring-boot.version>2.3.2.RELEASE</spring-boot.version>
            <lombok.version>1.18.12</lombok.version>
            <druid.version>1.1.22</druid.version>
            <mysql.version>5.1.47</mysql.version>
            <fastjson.version>1.2.68</fastjson.version>
            <swagger.version>3.0.0</swagger.version>
            <nimbus-jose-jwt.version>8.16</nimbus-jose-jwt.version>
            <hutool-version>5.3.5</hutool-version>
            <knife4j-version>2.0.3</knife4j-version>
            <!--设置源代码、编译JDK版本-->
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
            <!--<dubbo.version>2.7.3</dubbo.version>-->
        </properties>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
    
            </dependencies>
        </dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
    
    
            </dependency>
    
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <optional>true</optional>
            </dependency>
    
            <dependency>
                <groupId>com.nimbusds</groupId>
                <artifactId>nimbus-jose-jwt</artifactId>
                <version>${nimbus-jose-jwt.version}</version>
            </dependency>
    
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>2.11.0</version>
            </dependency>
            <dependency>
                <groupId>org.bouncycastle</groupId>
                <artifactId>bcpkix-jdk15on</artifactId>
                <version>1.68</version>
            </dependency>
        </dependencies>
    
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>2.2.2.RELEASE</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </project>
    

    #testVerify.java
    import com.nimbusds.jose.JWSObject;
    import com.nimbusds.jose.crypto.ECDSAVerifier;
    import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.security.*;
    import java.security.interfaces.ECPrivateKey;
    import java.security.interfaces.ECPublicKey;
    import java.security.spec.ECPrivateKeySpec;
    import java.security.spec.ECPublicKeySpec;
    
    import org.bouncycastle.openssl.PEMKeyPair;
    import org.bouncycastle.openssl.PEMParser;
    import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
    
    
    public class TestParse {
        public static void main(String[] args) throws Exception {
            Security.addProvider(BouncyCastleProviderSingleton.getInstance());
            PEMParser pemParser = new PEMParser(new InputStreamReader(new FileInputStream("{你自己的pem文件路径}")));
            PEMKeyPair pemKeyPair = null;
            try {
                pemKeyPair = (PEMKeyPair)pemParser.readObject();
                JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
                KeyPair keyPair = converter.getKeyPair(pemKeyPair);
                pemParser.close();
                Security.removeProvider("BC");
                ECPrivateKey privateKey = (ECPrivateKey)keyPair.getPrivate();
                privateKey = fixAlg(privateKey); // BC parses key alg as ECDSA instead of EC
                System.out.println(privateKey.getS());
                ECPublicKey publicKey = (ECPublicKey)keyPair.getPublic();
                publicKey = fixAlg(publicKey); // BC parses key alg as ECDSA instead of EC
                System.out.println("key init finished");
                String token = "{从go中测试拿到的jwtToken字符串}";
                //解析token,拿token中的传输数据
                JWSObject jwsObject = JWSObject.parse(token);
                //用公钥验签
                Boolean verify = jwsObject.verify(new ECDSAVerifier(publicKey));
                System.out.println(verify);
            } catch (IOException e) {
                e.printStackTrace();
    
            }
        }
        private static ECPrivateKey fixAlg(final ECPrivateKey key)
                throws Exception {
    
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
    
            return (ECPrivateKey)keyFactory.generatePrivate(
                    new ECPrivateKeySpec(key.getS(), key.getParams()));
        }
    
        private static ECPublicKey fixAlg(final ECPublicKey key)
                throws Exception {
    
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
    
            return (ECPublicKey)keyFactory.generatePublic(
                    new ECPublicKeySpec(key.getW(), key.getParams()));
        }
    }
    

    相关文章

      网友评论

          本文标题:go语言与java语言jwt加签和验签的协同问题

          本文链接:https://www.haomeiwen.com/subject/ojvjgrtx.html