mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 02:35:49 +08:00
291 lines
7.6 KiB
Go
291 lines
7.6 KiB
Go
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package idp
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
type AlipayIdProvider struct {
|
|
Client *http.Client
|
|
Config *oauth2.Config
|
|
}
|
|
|
|
// NewAlipayIdProvider ...
|
|
func NewAlipayIdProvider(clientId string, clientSecret string, redirectUrl string) *AlipayIdProvider {
|
|
idp := &AlipayIdProvider{}
|
|
|
|
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
|
idp.Config = config
|
|
|
|
return idp
|
|
}
|
|
|
|
// SetHttpClient ...
|
|
func (idp *AlipayIdProvider) SetHttpClient(client *http.Client) {
|
|
idp.Client = client
|
|
}
|
|
|
|
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
|
func (idp *AlipayIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
|
endpoint := oauth2.Endpoint{
|
|
AuthURL: "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm",
|
|
TokenURL: "https://openapi.alipay.com/gateway.do",
|
|
}
|
|
|
|
config := &oauth2.Config{
|
|
Scopes: []string{"", ""},
|
|
Endpoint: endpoint,
|
|
ClientID: clientId,
|
|
ClientSecret: clientSecret,
|
|
RedirectURL: redirectUrl,
|
|
}
|
|
|
|
return config
|
|
}
|
|
|
|
type AlipayAccessToken struct {
|
|
Response AlipaySystemOauthTokenResponse `json:"alipay_system_oauth_token_response"`
|
|
Sign string `json:"sign"`
|
|
}
|
|
|
|
type AlipaySystemOauthTokenResponse struct {
|
|
AccessToken string `json:"access_token"`
|
|
AlipayUserId string `json:"alipay_user_id"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
ReExpiresIn int `json:"re_expires_in"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
UserId string `json:"user_id"`
|
|
}
|
|
|
|
// GetToken use code to get access_token
|
|
func (idp *AlipayIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|
pTokenParams := &struct {
|
|
ClientId string `json:"app_id"`
|
|
CharSet string `json:"charset"`
|
|
Code string `json:"code"`
|
|
GrantType string `json:"grant_type"`
|
|
Method string `json:"method"`
|
|
SignType string `json:"sign_type"`
|
|
TimeStamp string `json:"timestamp"`
|
|
Version string `json:"version"`
|
|
}{idp.Config.ClientID, "utf-8", code, "authorization_code", "alipay.system.oauth.token", "RSA2", time.Now().Format("2006-01-02 15:04:05"), "1.0"}
|
|
|
|
data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pToken := &AlipayAccessToken{}
|
|
err = json.Unmarshal(data, pToken)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
token := &oauth2.Token{
|
|
AccessToken: pToken.Response.AccessToken,
|
|
Expiry: time.Unix(time.Now().Unix()+int64(pToken.Response.ExpiresIn), 0),
|
|
}
|
|
return token, nil
|
|
}
|
|
|
|
/*
|
|
{
|
|
"alipay_user_info_share_response":{
|
|
"code":"10000",
|
|
"msg":"Success",
|
|
"avatar":"https:\/\/tfs.alipayobjects.com\/images\/partner\/T1.QxFXk4aXXXXXXXX",
|
|
"nick_name":"zhangsan",
|
|
"user_id":"2099222233334444"
|
|
},
|
|
"sign":"m8rWJeqfoa5tDQRRVnPhRHcpX7NZEgjIPTPF1QBxos6XXXXXXXXXXXXXXXXXXXXXXXXXX"
|
|
}
|
|
*/
|
|
|
|
type AlipayUserResponse struct {
|
|
AlipayUserInfoShareResponse AlipayUserInfoShareResponse `json:"alipay_user_info_share_response"`
|
|
Sign string `json:"sign"`
|
|
}
|
|
|
|
type AlipayUserInfoShareResponse struct {
|
|
Code string `json:"code"`
|
|
Msg string `json:"msg"`
|
|
Avatar string `json:"avatar"`
|
|
NickName string `json:"nick_name"`
|
|
UserId string `json:"user_id"`
|
|
}
|
|
|
|
// GetUserInfo Use access_token to get UserInfo
|
|
func (idp *AlipayIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|
atUserInfo := &AlipayUserResponse{}
|
|
accessToken := token.AccessToken
|
|
|
|
pTokenParams := &struct {
|
|
ClientId string `json:"app_id"`
|
|
CharSet string `json:"charset"`
|
|
AuthToken string `json:"auth_token"`
|
|
Method string `json:"method"`
|
|
SignType string `json:"sign_type"`
|
|
TimeStamp string `json:"timestamp"`
|
|
Version string `json:"version"`
|
|
}{idp.Config.ClientID, "utf-8", accessToken, "alipay.user.info.share", "RSA2", time.Now().Format("2006-01-02 15:04:05"), "1.0"}
|
|
data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = json.Unmarshal(data, atUserInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
userInfo := UserInfo{
|
|
Id: atUserInfo.AlipayUserInfoShareResponse.UserId,
|
|
Username: atUserInfo.AlipayUserInfoShareResponse.NickName,
|
|
DisplayName: atUserInfo.AlipayUserInfoShareResponse.NickName,
|
|
AvatarUrl: atUserInfo.AlipayUserInfoShareResponse.Avatar,
|
|
}
|
|
|
|
return &userInfo, nil
|
|
}
|
|
|
|
func (idp *AlipayIdProvider) postWithBody(body interface{}, targetUrl string) ([]byte, error) {
|
|
bs, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bodyJson := make(map[string]interface{})
|
|
err = json.Unmarshal(bs, &bodyJson)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
formData := url.Values{}
|
|
for k := range bodyJson {
|
|
formData.Set(k, bodyJson[k].(string))
|
|
}
|
|
|
|
sign, err := rsaSignWithRSA256(getStringToSign(formData), idp.Config.ClientSecret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
formData.Set("sign", sign)
|
|
|
|
resp, err := idp.Client.PostForm(targetUrl, formData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func(Body io.ReadCloser) {
|
|
err := Body.Close()
|
|
if err != nil {
|
|
return
|
|
}
|
|
}(resp.Body)
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// get the string to sign, see https://opendocs.alipay.com/common/02kf5q
|
|
func getStringToSign(formData url.Values) string {
|
|
keys := make([]string, 0, len(formData))
|
|
for k := range formData {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
str := ""
|
|
for _, k := range keys {
|
|
if k == "sign" || formData[k][0] == "" {
|
|
continue
|
|
} else {
|
|
str += "&" + k + "=" + formData[k][0]
|
|
}
|
|
}
|
|
str = strings.Trim(str, "&")
|
|
return str
|
|
}
|
|
|
|
// use privateKey to sign the content
|
|
func rsaSignWithRSA256(signContent string, privateKey string) (string, error) {
|
|
privateKey = formatPrivateKey(privateKey)
|
|
block, _ := pem.Decode([]byte(privateKey))
|
|
if block == nil {
|
|
panic("fail to parse privateKey")
|
|
}
|
|
|
|
h := sha256.New()
|
|
h.Write([]byte(signContent))
|
|
hashed := h.Sum(nil)
|
|
|
|
privateKeyRSA, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKeyRSA.(*rsa.PrivateKey), crypto.SHA256, hashed)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return base64.StdEncoding.EncodeToString(signature), nil
|
|
}
|
|
|
|
// privateKey in database is a string, format it to PEM style
|
|
func formatPrivateKey(privateKey string) string {
|
|
// each line length is 64
|
|
preFmtPrivateKey := ""
|
|
for i := 0; ; {
|
|
if i+64 <= len(privateKey) {
|
|
preFmtPrivateKey = preFmtPrivateKey + privateKey[i:i+64] + "\n"
|
|
i += 64
|
|
} else {
|
|
preFmtPrivateKey = preFmtPrivateKey + privateKey[i:]
|
|
break
|
|
}
|
|
}
|
|
privateKey = strings.Trim(preFmtPrivateKey, "\n")
|
|
|
|
// add pkcs#8 BEGIN and END
|
|
PemBegin := "-----BEGIN PRIVATE KEY-----\n"
|
|
PemEnd := "\n-----END PRIVATE KEY-----"
|
|
if !strings.HasPrefix(privateKey, PemBegin) {
|
|
privateKey = PemBegin + privateKey
|
|
}
|
|
if !strings.HasSuffix(privateKey, PemEnd) {
|
|
privateKey = privateKey + PemEnd
|
|
}
|
|
return privateKey
|
|
}
|