Golang connects to ali Cloud private Bucket to upload pictures and authorize access to pictures
1. Why private buckets
- Public read/write: Any user on the Internet can access the files in the Bucket and write data to the Bucket. This may cause your data leakage and cost surge, if maliciously written illegal information may infringe on your legitimate rights and interests
- Private: Only the owner of the storage space can read and write files in the storage space. Others cannot access files in the storage space
In view of the above, the company requires that buckets be made private and accessible only to authorized users
2, preparation,
2.1 Creating a RAM Account
After opening OSS account, create RAM account, use STS temporary access certificate to access OSS, refer to Ali Cloud documentation
Tip: In Step 4, grant AliyunOSSFullAccess to the RAM roleI’ve been looking for problems all day, everyoneKeep in mind thatWriting this blog is also to record my way to fill the hole
2.2 set the Bucket
- In object storage, the Bucket list creates private buckets
- Then in this Bucket authorization just create OSS account ID, authorization operation is full control
3, serving
3.1 Constants and their structures
- According to their own Ali cloud information to configure
const (
AccessKeyId = "* *"AK / / oss account
AccessKeySecret = "* *"ST / / oss account
stsEndpoint = "* *"// STS. Ali Cloud object storage address
RoleArn = "* *"// To create a role
RoleSessionName = "* *"// To create a role
BucketName = "* *" // Enter a Bucket name, such as exampleBucket
Endpoint = "* *"// Address of aliCloud object storage
UploadOssUrl = "* *" // Return to the front-end OSS upload address
)
type StsTokenInfo struct {
StatusCode int `json:"StatusCode"`
AccessKeyId string `json:"AccessKeyId"`
AccessKeySecret string `json:"AccessKeySecret"`
SecurityToken string `json:"SecurityToken"`
Expiration string `json:"Expiration"`
}
type StsErrorInfo struct {
StatusCode int `json:"StatusCode"`
ErrorCode string `json:"ErrorCode"`
ErrorMessage string `json:"ErrorMessage"`
}
Copy the code
3.2 Obtaining STS Temporary User Information
package aliyun
import (
"fmt"
openapi "github.com/alibabacloud-go/darabonba-openapi/client"
sts "github.com/alibabacloud-go/sts-20150401/client"
"github.com/alibabacloud-go/tea/tea"
log "github.com/sirupsen/logrus"
)
//
// GetAliyunStsInfo
// @description: Get the STS temporary user information
// @param isReturnAll
// @return *sts.AssumeRoleResponseBody
//
func GetAliyunStsInfo(a) *sts.AssumeRoleResponseBody {
return generateStsInfo()
}
/** * Generate STS temporary user information */
func generateStsInfo(a) *sts.AssumeRoleResponseBody {
client, _err := createClient(tea.String(AccessKeyId), tea.String(AccessKeySecret))
if_err ! =nil {
fmt.Print(_err.Error())
}
assumeRoleRequest := &sts.AssumeRoleRequest{
RoleArn: tea.String(RoleArn),
RoleSessionName: tea.String(RoleSessionName),
}
resp, err := client.AssumeRole(assumeRoleRequest)
iferr ! =nil {
fmt.Print(err.Error())
}
fmt.Printf("Get STS temporary user information :%v", resp)
log.Info("To obtain STS temporary user information :", resp)
return (*resp).Body
}
/** * Initialize the account using AK&SK Client * @param accessKeyId * @param accessKeySecret * @return Client * @throws Exception */
func createClient(accessKeyId *string, accessKeySecret *string) (_result *sts.Client, _err error) {
config := &openapi.Config{
AccessKeyId: accessKeyId,
AccessKeySecret: accessKeySecret,
}
// The domain name to visit
config.Endpoint = tea.String(stsEndpoint)
_result = &sts.Client{}
_result, _err = sts.NewClient(config)
return _result, _err
}
Copy the code
- I am using a new version of the SDK Ali Cloud demo, modified, specific reference documents
- The method of obtaining STS temporary user information needs to be used in many places, so I extract it for other interfaces to call (for example, granting access requires STS temporary user information to generate signUrl).
3.3 Interface Invocation
3.3.1 STS temporary user information that does not contain the signature
- I use gin framework here. This interface does not contain signatures (policy and Signature) required by the front end, which requires the front end to use oss plug-in (all information returned is written in another interface).
/** interface: to get the STS user front end, load the OSS plug-in. If not, call AppAliyunPolicy */ in the policy file
func AppAliyunSts(c *gin.Context) {
response := GetAliyunStsInfo()
c.JSON(http.StatusOK, gin.H{
"code": 1."data": response,
})
return
}
Copy the code
- API call, return information
3.3.2 STS temporary user information with signature
- The advantage of this interface is that no extra packages need to be loaded at the front end
package aliyun
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"hash"
"io"
"net/http"
"time"
)
/** Signature direct upload service for small programs to upload pictures without loading library */
// The prefix specified when the user uploads the file.
//var upload_dir string = "user-dir/"
// Expiration time is 3000 seconds
var expire_time int64 = 3000
type ConfigStruct struct {
Expiration string `json:"expiration"`
Conditions [][]string `json:"conditions"`
}
type PolicyToken struct {
StsTokenInfo
Expire int64 `json:"expire"`
Signature string `json:"signature"`
Policy string `json:"policy"`
Directory string `json:"dir"`
UploadOssUrl string `json:"uploadOssUrl"`
}
func AppAliyunPolicy(c *gin.Context) {
uploadDir := c.DefaultQuery("dir"."user-dir/")
/ / get the accessKeyId token2 accessKeySecret
resp := GetAliyunStsInfo()
log.Info(resp)
now := time.Now().Unix()
expire_end := now + expire_time
var tokenExpire = getGmtIso8601(expire_end)
//create post policy json
var config ConfigStruct
config.Expiration = tokenExpire
var condition []string
condition = append(condition, "starts-with")
condition = append(condition, "$key")
condition = append(condition, uploadDir)
config.Conditions = append(config.Conditions, condition)
//calucate signature
result, err := json.Marshal(config)
debyte := base64.StdEncoding.EncodeToString(result)
h := hmac.New(func(a) hash.Hash { return sha1.New() }, []byte(*resp.Credentials.AccessKeySecret))
io.WriteString(h, debyte)
signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))
policyToken := &PolicyToken{}
policyToken.AccessKeyId = *resp.Credentials.AccessKeyId
policyToken.AccessKeySecret = *resp.Credentials.AccessKeySecret
policyToken.SecurityToken = *resp.Credentials.SecurityToken
policyToken.Expiration = *resp.Credentials.Expiration
policyToken.Expire = expire_end
policyToken.Signature = string(signedStr)
policyToken.Directory = uploadDir
policyToken.Policy = string(debyte)
policyToken.UploadOssUrl = UploadOssUrl
iferr ! =nil {
fmt.Println("json err:", err)
}
c.JSON(http.StatusOK, gin.H{
"code": 1."data": policyToken,
})
return
}
func getGmtIso8601(expireEnd int64) string {
var tokenExpire = time.Unix(expireEnd, 0).UTC().Format("2006-01-02T15:04:05Z")
return tokenExpire
}
Copy the code
- The Get interface carries the dir parameter to specify which folder to put in
- Interface call, returns information
3.4 Authorized Access
3.4.1 Interface Invocation
- Ali Cloud provides two authorization methods, URL authorization and header authorization. I choose URL for image authorization. Images need to be authorized to carry the signature parameters can request to the specific reference document [] ([picture archived failure outside the chain, the source station might be hotlinking prevention mechanism, proposed to directly upload picture preserved (img – W41fNy75-1648959247715) (media / 16489520500281/1648956337 6848.jpg)]
)
package aliyun
import (
"fmt"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/gin-gonic/gin"
"net/http"
)
/** Image authorization returns the signed URL */
func GetSignURL(c *gin.Context) {
accessKeyId := c.DefaultQuery("accessKeyId"."")
accessKeySecret := c.DefaultQuery("accessKeySecret"."")
securityToken := c.DefaultQuery("securityToken"."")
fullImgPath := c.DefaultQuery("fullImgPath"."") // Image full path
// If empty, request STS temporary user information
if accessKeyId == "" || accessKeySecret == "" || securityToken == "" {
stsTokenInfo := GetAliyunStsInfo()
accessKeyId = *stsTokenInfo.Credentials.AccessKeyId
accessKeySecret = *stsTokenInfo.Credentials.AccessKeySecret
securityToken = *stsTokenInfo.Credentials.SecurityToken
}
// After obtaining the STS temporary credential, you can generate OSSClient from the SecurityToken and temporary access key (AccessKeyId and AccessKeySecret) contained in it.
client, err := oss.New(Endpoint, accessKeyId, accessKeySecret, oss.SecurityToken(securityToken))
iferr ! =nil {
fmt.Print(err.Error())
}
/ / fill in the full path to the file, such as exampledir/exampleobject. TXT. The full file path cannot contain a Bucket name.
objectName := fullImgPath
// Get storage space.
bucket, err := client.Bucket(BucketName)
iferr ! =nil {
fmt.Print(err.Error())
}
// Signature direct.
signedURL, err := bucket.SignURL(objectName, oss.HTTPGet, 6000)
iferr ! =nil {
fmt.Print(err.Error())
}
c.JSON(http.StatusOK, gin.H{
"code": 1."data": signedURL,
})
}
Copy the code
- There are four parameters here. The previous parameter was obtained by the STS temporary user and passed from the front end (this scenario applies to the image echo after uploading the image, so the image URL with signature is required). If the front end does not transmit these three parameters, we need to call the previous GetAliyunStsInfo() to generate them.
- FullImgPath parameter is the photo of the full path and fill in the full path to the file, such as exampledir/exampleobject. TXT. The full file path cannot contain a Bucket name.
3.4.2 API Call information is returned
- If there is no authorization, it will prompt
I ran into this problem because I did not set OSS maximum permissions for RAM roles, keep in mind
- If the expiration date has passed, it will prompt
<Error>
<Code>AccessDenied</Code>
<Message>Request has expired.</Message>
<RequestId>* * *</RequestId>
<HostId>* * * *</HostId>
<Expires>The 2022-04-02 T08:42:47. 000 z</Expires>
<ServerTime>The 2022-04-03 T03:56:52. 000 z</ServerTime>
</Error>
Copy the code
Ok, the above interface has been tested and adjusted by myself, which takes a day and a half. There is nothing wrong with the code. The problem lies in the configuration permissions