diff --git a/object/application.go b/object/application.go index 3ea00ed3..efd7f216 100644 --- a/object/application.go +++ b/object/application.go @@ -97,6 +97,7 @@ type Application struct { ClientSecret string `xorm:"varchar(100)" json:"clientSecret"` RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"` TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"` + TokenSigningMethod string `xorm:"varchar(100)" json:"tokenSigningMethod"` TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"` ExpireInHours int `json:"expireInHours"` RefreshExpireInHours int `json:"refreshExpireInHours"` diff --git a/object/oidc_discovery.go b/object/oidc_discovery.go index 22b5e9eb..f6671f57 100644 --- a/object/oidc_discovery.go +++ b/object/oidc_discovery.go @@ -112,7 +112,7 @@ func GetOidcDiscovery(host string) OidcDiscovery { ResponseModesSupported: []string{"query", "fragment", "login", "code", "link"}, GrantTypesSupported: []string{"password", "authorization_code"}, SubjectTypesSupported: []string{"public"}, - IdTokenSigningAlgValuesSupported: []string{"RS256"}, + IdTokenSigningAlgValuesSupported: []string{"RS256", "RS512", "ES256", "ES384", "ES512"}, ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"}, ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isForbidden", "signupApplication", "ldap"}, RequestParameterSupported: true, diff --git a/object/token_jwt.go b/object/token_jwt.go index 0d009d9a..14ccde1d 100644 --- a/object/token_jwt.go +++ b/object/token_jwt.go @@ -17,6 +17,7 @@ package object import ( "fmt" "reflect" + "strings" "time" "github.com/casdoor/casdoor/util" @@ -378,36 +379,52 @@ func generateJwtToken(application *Application, user *User, nonce string, scope application.TokenFormat = "JWT" } + var jwtMethod jwt.SigningMethod + + if application.TokenSigningMethod == "RS256" { + jwtMethod = jwt.SigningMethodRS256 + } else if application.TokenSigningMethod == "RS512" { + jwtMethod = jwt.SigningMethodRS512 + } else if application.TokenSigningMethod == "ES256" { + jwtMethod = jwt.SigningMethodES256 + } else if application.TokenSigningMethod == "ES512" { + jwtMethod = jwt.SigningMethodES512 + } else if application.TokenSigningMethod == "ES384" { + jwtMethod = jwt.SigningMethodES384 + } else { + jwtMethod = jwt.SigningMethodRS256 + } + // the JWT token length in "JWT-Empty" mode will be very short, as User object only has two properties: owner and name if application.TokenFormat == "JWT" { claimsWithoutThirdIdp := getClaimsWithoutThirdIdp(claims) - token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp) + token = jwt.NewWithClaims(jwtMethod, claimsWithoutThirdIdp) claimsWithoutThirdIdp.ExpiresAt = jwt.NewNumericDate(refreshExpireTime) claimsWithoutThirdIdp.TokenType = "refresh-token" - refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp) + refreshToken = jwt.NewWithClaims(jwtMethod, claimsWithoutThirdIdp) } else if application.TokenFormat == "JWT-Empty" { claimsShort := getShortClaims(claims) - token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort) + token = jwt.NewWithClaims(jwtMethod, claimsShort) claimsShort.ExpiresAt = jwt.NewNumericDate(refreshExpireTime) claimsShort.TokenType = "refresh-token" - refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort) + refreshToken = jwt.NewWithClaims(jwtMethod, claimsShort) } else if application.TokenFormat == "JWT-Custom" { claimsCustom := getClaimsCustom(claims, application.TokenFields) - token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsCustom) + token = jwt.NewWithClaims(jwtMethod, claimsCustom) refreshClaims := getClaimsCustom(claims, application.TokenFields) refreshClaims["exp"] = jwt.NewNumericDate(refreshExpireTime) refreshClaims["TokenType"] = "refresh-token" - refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, refreshClaims) + refreshToken = jwt.NewWithClaims(jwtMethod, refreshClaims) } else if application.TokenFormat == "JWT-Standard" { claimsStandard := getStandardClaims(claims) - token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsStandard) + token = jwt.NewWithClaims(jwtMethod, claimsStandard) claimsStandard.ExpiresAt = jwt.NewNumericDate(refreshExpireTime) claimsStandard.TokenType = "refresh-token" - refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsStandard) + refreshToken = jwt.NewWithClaims(jwtMethod, claimsStandard) } else { return "", "", "", fmt.Errorf("unknown application TokenFormat: %s", application.TokenFormat) } @@ -425,34 +442,57 @@ func generateJwtToken(application *Application, user *User, nonce string, scope } } - // RSA private key - key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey)) + var ( + tokenString string + refreshTokenString string + key interface{} + ) + + if strings.Contains(application.TokenSigningMethod, "RS") || application.TokenSigningMethod == "" { + // RSA private key + key, err = jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey)) + } else if strings.Contains(application.TokenSigningMethod, "ES") { + // ES private key + key, err = jwt.ParseECPrivateKeyFromPEM([]byte(cert.PrivateKey)) + } else if strings.Contains(application.TokenSigningMethod, "Ed") { + // Ed private key + key, err = jwt.ParseEdPrivateKeyFromPEM([]byte(cert.PrivateKey)) + } if err != nil { return "", "", "", err } token.Header["kid"] = cert.Name - tokenString, err := token.SignedString(key) + tokenString, err = token.SignedString(key) if err != nil { return "", "", "", err } - refreshTokenString, err := refreshToken.SignedString(key) + refreshTokenString, err = refreshToken.SignedString(key) return tokenString, refreshTokenString, name, err } func ParseJwtToken(token string, cert *Cert) (*Claims, error) { t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } + var ( + certificate interface{} + err error + ) if cert.Certificate == "" { return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert) } - // RSA certificate - certificate, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate)) + if _, ok := token.Method.(*jwt.SigningMethodRSA); ok { + // RSA certificate + certificate, err = jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate)) + } else if _, ok := token.Method.(*jwt.SigningMethodECDSA); ok { + // ES certificate + certificate, err = jwt.ParseECPublicKeyFromPEM([]byte(cert.Certificate)) + } else { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + if err != nil { return nil, err } diff --git a/web/src/ApplicationEditPage.js b/web/src/ApplicationEditPage.js index 04984005..8bba3d37 100644 --- a/web/src/ApplicationEditPage.js +++ b/web/src/ApplicationEditPage.js @@ -407,6 +407,16 @@ class ApplicationEditPage extends React.Component { /> + + + {Setting.getLabel(i18next.t("application:Token signing method"), i18next.t("application:Token signing method - Tooltip"))} : + + +