某项目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()));
}
}
网友评论