在此示例中,使用了Spring Boot和Go gin架构,实现了java项目生成jwt token,go项目验证token。我们把jwt rsa相关信息配置在application.yml中。
Spring Boot中的相关代码和配置
JwtProperties
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("jwt")
public class JwtProperties {
private String publicKey;
private String privateKey;
public String getPublicKey() {
return publicKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
}
JwtConfiguration
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
@Configuration
@EnableConfigurationProperties(JwtProperties.class)
public class JwtConfiguration {
@Autowired
private JwtProperties jwtProperties;
@Bean
RSAPublicKey rsaPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
String publicKeyPEM = jwtProperties.getPublicKey().replace("-----BEGIN PUBLIC KEY-----", "")
.replaceAll(System.lineSeparator(), "").replace("-----END PUBLIC KEY-----", "").replaceAll("\\s+", "");
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
}
@Bean
public RSAPrivateKey rsaPrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
String privateKeyPEM = jwtProperties.getPrivateKey().replace("-----BEGIN PRIVATE KEY-----", "")
.replaceAll(System.lineSeparator(), "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");
byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
}
@Bean
public JwtDecoder jwtDecoder(RSAPublicKey rsaPublicKey) {
return NimbusJwtDecoder.withPublicKey(rsaPublicKey).build();
}
@Bean
public JwtEncoder jwtEncoder(RSAPublicKey rsaPublicKey, RSAPrivateKey rsaPrivateKey) {
JWK jwk = new RSAKey.Builder(rsaPublicKey).privateKey(rsaPrivateKey).build();
JWKSource jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
}
application.yml
jwt:
public-key: |-
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FlqJr5TRskIQIgdE3Dd
7D9lboWdcTUT8a+fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRv
c5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4/1tfRgG6ii4Uhxh6
iI8qNMJQX+fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2
kJdJ/ZIV+WW4noDdzpKqHcwmB8FsrumlVY/DNVvUSDIipiq9PbP4H99TXN1o746o
RaNa07rq1hoCgMSSy+85SagCoxlmyE+D+of9SsMY8Ol9t0rdzpobBuhyJ/o5dfvj
KwIDAQAB
-----END PUBLIC KEY-----
private-key: |-
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcWWomvlNGyQhA
iB0TcN3sP2VuhZ1xNRPxr58lHswC9Cbtdc2hiSbe/sxAvU1i0O8vaXwICdzRZ1JM
g1TohG9zkqqjZDhyw1f1Ic6YR/OhE6NCpqERy97WMFeW6gJd1i5inHj/W19GAbqK
LhSHGHqIjyo0wlBf58t+qFt9h/EFBVE/LAGQBsg/jHUQCxsLoVI2aSELGIw2oSDF
oiljwLaQl0n9khX5ZbiegN3OkqodzCYHwWyu6aVVj8M1W9RIMiKmKr09s/gf31Nc
3WjvjqhFo1rTuurWGgKAxJLL7zlJqAKjGWbIT4P6h/1Kwxjw6X23St3OmhsG6HIn
+jl1++MrAgMBAAECggEBAMf820wop3pyUOwI3aLcaH7YFx5VZMzvqJdNlvpg1jbE
E2Sn66b1zPLNfOIxLcBG8x8r9Ody1Bi2Vsqc0/5o3KKfdgHvnxAB3Z3dPh2WCDek
lCOVClEVoLzziTuuTdGO5/CWJXdWHcVzIjPxmK34eJXioiLaTYqN3XKqKMdpD0ZG
mtNTGvGf+9fQ4i94t0WqIxpMpGt7NM4RHy3+Onggev0zLiDANC23mWrTsUgect/7
62TYg8g1bKwLAb9wCBT+BiOuCc2wrArRLOJgUkj/F4/gtrR9ima34SvWUyoUaKA0
bi4YBX9l8oJwFGHbU9uFGEMnH0T/V0KtIB7qetReywkCgYEA9cFyfBIQrYISV/OA
+Z0bo3vh2aL0QgKrSXZ924cLt7itQAHNZ2ya+e3JRlTczi5mnWfjPWZ6eJB/8MlH
Gpn12o/POEkU+XjZZSPe1RWGt5g0S3lWqyx9toCS9ACXcN9tGbaqcFSVI73zVTRA
8J9grR0fbGn7jaTlTX2tnlOTQ60CgYEA5YjYpEq4L8UUMFkuj+BsS3u0oEBnzuHd
I9LEHmN+CMPosvabQu5wkJXLuqo2TxRnAznsA8R3pCLkdPGoWMCiWRAsCn979TdY
QbqO2qvBAD2Q19GtY7lIu6C35/enQWzJUMQE3WW0OvjLzZ0l/9mA2FBRR+3F9A1d
rBdnmv0c3TcCgYEAi2i+ggVZcqPbtgrLOk5WVGo9F1GqUBvlgNn30WWNTx4zIaEk
HSxtyaOLTxtq2odV7Kr3LGiKxwPpn/T+Ief+oIp92YcTn+VfJVGw4Z3BezqbR8lA
Uf/+HF5ZfpMrVXtZD4Igs3I33Duv4sCuqhEvLWTc44pHifVloozNxYfRfU0CgYBN
HXa7a6cJ1Yp829l62QlJKtx6Ymj95oAnQu5Ez2ROiZMqXRO4nucOjGUP55Orac1a
FiGm+mC/skFS0MWgW8evaHGDbWU180wheQ35hW6oKAb7myRHtr4q20ouEtQMdQIF
snV39G1iyqeeAsf7dxWElydXpRi2b68i3BIgzhzebQKBgQCdUQuTsqV9y/JFpu6H
c5TVvhG/ubfBspI5DhQqIGijnVBzFT//UfIYMSKJo75qqBEyP2EJSmCsunWsAFsM
TszuiGTkrKcZy9G0wJqPztZZl2F2+bJgnA6nBEV7g5PA4Af+QSmaIhRwqGDAuROR
47jndeyIaMTNETEmOnms+as17g==
-----END PRIVATE KEY-----
Go gin中相关代码和配置
token_subject
package model
type TokenSubject struct {
Id int64 `json:"id"`
Name string `json:"name"`
UserType int `json:"userType"`
Platform string `json:"platform"`
App string `json:"app"`
}
jwt_middleware
package middleware
import (
"fmt"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func AuthorizeJWT() gin.HandlerFunc {
return func(c *gin.Context) {
const BEARER_SCHEMA = "Bearer"
authHeader := c.GetHeader("Authorization")
if len(authHeader) == 0 || len(authHeader) < len(BEARER_SCHEMA)+1 {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
tokenStr := strings.TrimSpace(authHeader[len(BEARER_SCHEMA)+1:])
if tokenSubject, err := service.JwtAuthService.ValidAuthToken(tokenStr); err != nil {
fmt.Println(err)
c.AbortWithStatus(http.StatusUnauthorized)
return
} else if tokenSubject == nil || tokenSubject.Id == 0 {
c.AbortWithStatus(http.StatusUnauthorized)
return
} else {
c.Set("tokenSubject", tokenSubject)
c.Next()
}
}
}
jwt_auth_service
package service
import (
"encoding/json"
"log"
"github.com/golang-jwt/jwt/v4"
)
type jwtAuthService struct {
}
var JwtAuthService = new(jwtAuthService)
func (s *jwtAuthService) ValidAuthToken(tokenStr string) (*model.TokenSubject, error) {
if tokenStr == "" {
return nil, errors.New("参数tokenStr为空")
}
claims := &jwt.RegisteredClaims{}
result, _, err := s.VerifyJWTRSA(tokenStr, config.GetConfig().Jwt.PublicKey, claims)
if !result {
return nil, errors.New("验证失败")
}
if err != nil {
log.Println(tokenStr, config.GetConfig().Jwt.PublicKey, err)
return nil, err
}
tokenSubject := &model.TokenSubject{}
if err := json.Unmarshal([]byte(claims.Subject), &tokenSubject); err != nil {
log.Println(err)
return nil, err
}
if tokenSubject.Id == 0 {
return nil, err
}
return tokenSubject, nil
}
func (s *jwtAuthService) VerifyJWTRSA(token, publicKey string, claims *jwt.RegisteredClaims) (bool, *jwt.Token, error) {
var parsedToken *jwt.Token
// parse token
state, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
// ensure signing method is correct
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, errors.New("unknown signing method")
}
parsedToken = token
// verify
key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(publicKey))
if err != nil {
return nil, err
}
return key, nil
})
if err != nil {
return false, &jwt.Token{}, err
}
if !state.Valid {
return false, &jwt.Token{}, errors.New("invalid jwt token")
}
return true, parsedToken, nil
}