diff --git a/internal/rpc/rpc_client.go b/internal/rpc/rpc_client.go
index c6818677..d4bf2157 100644
--- a/internal/rpc/rpc_client.go
+++ b/internal/rpc/rpc_client.go
@@ -336,6 +336,10 @@ func (this *RPCClient) UserBillRPC() pb.UserBillServiceClient {
 	return pb.NewUserBillServiceClient(this.pickConn())
 }
 
+func (this *RPCClient) UserAccessKeyRPC() pb.UserAccessKeyServiceClient {
+	return pb.NewUserAccessKeyServiceClient(this.pickConn())
+}
+
 func (this *RPCClient) LoginRPC() pb.LoginServiceClient {
 	return pb.NewLoginServiceClient(this.pickConn())
 }
diff --git a/internal/web/actions/default/users/accesskeys/createPopup.go b/internal/web/actions/default/users/accesskeys/createPopup.go
new file mode 100644
index 00000000..6cc7e7cd
--- /dev/null
+++ b/internal/web/actions/default/users/accesskeys/createPopup.go
@@ -0,0 +1,49 @@
+// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
+
+package accesskeys
+
+import (
+	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+	"github.com/iwind/TeaGo/actions"
+)
+
+type CreatePopupAction struct {
+	actionutils.ParentAction
+}
+
+func (this *CreatePopupAction) Init() {
+	this.Nav("", "", "")
+}
+
+func (this *CreatePopupAction) RunGet(params struct {
+	UserId int64
+}) {
+	this.Data["userId"] = params.UserId
+	this.Show()
+}
+
+func (this *CreatePopupAction) RunPost(params struct {
+	UserId      int64
+	Description string
+
+	Must *actions.Must
+	CSRF *actionutils.CSRF
+}) {
+	params.Must.
+		Field("description", params.Description).
+		Require("请输入备注")
+
+	accessKeyIdResp, err := this.RPC().UserAccessKeyRPC().CreateUserAccessKey(this.AdminContext(), &pb.CreateUserAccessKeyRequest{
+		UserId:      params.UserId,
+		Description: params.Description,
+	})
+	if err != nil {
+		this.ErrorPage(err)
+		return
+	}
+
+	defer this.CreateLogInfo("创建AccessKey %d", accessKeyIdResp.UserAccessKeyId)
+
+	this.Success()
+}
diff --git a/internal/web/actions/default/users/accesskeys/delete.go b/internal/web/actions/default/users/accesskeys/delete.go
new file mode 100644
index 00000000..fd84da1c
--- /dev/null
+++ b/internal/web/actions/default/users/accesskeys/delete.go
@@ -0,0 +1,24 @@
+package accesskeys
+
+import (
+	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+)
+
+type DeleteAction struct {
+	actionutils.ParentAction
+}
+
+func (this *DeleteAction) RunPost(params struct {
+	AccessKeyId int64
+}) {
+	defer this.CreateLogInfo("删除AccessKey %d", params.AccessKeyId)
+
+	_, err := this.RPC().UserAccessKeyRPC().DeleteUserAccessKey(this.AdminContext(), &pb.DeleteUserAccessKeyRequest{UserAccessKeyId: params.AccessKeyId})
+	if err != nil {
+		this.ErrorPage(err)
+		return
+	}
+
+	this.Success()
+}
diff --git a/internal/web/actions/default/users/accesskeys/index.go b/internal/web/actions/default/users/accesskeys/index.go
new file mode 100644
index 00000000..267ccaee
--- /dev/null
+++ b/internal/web/actions/default/users/accesskeys/index.go
@@ -0,0 +1,54 @@
+// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
+
+package accesskeys
+
+import (
+	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users/userutils"
+	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+	"github.com/iwind/TeaGo/maps"
+	timeutil "github.com/iwind/TeaGo/utils/time"
+)
+
+type IndexAction struct {
+	actionutils.ParentAction
+}
+
+func (this *IndexAction) Init() {
+	this.Nav("", "", "accessKey")
+}
+
+func (this *IndexAction) RunGet(params struct {
+	UserId int64
+}) {
+	err := userutils.InitUser(this.Parent(), params.UserId)
+	if err != nil {
+		this.ErrorPage(err)
+		return
+	}
+
+	accessKeysResp, err := this.RPC().UserAccessKeyRPC().FindAllEnabledUserAccessKeys(this.AdminContext(), &pb.FindAllEnabledUserAccessKeysRequest{UserId: params.UserId})
+	if err != nil {
+		this.ErrorPage(err)
+		return
+	}
+
+	accessKeyMaps := []maps.Map{}
+	for _, accessKey := range accessKeysResp.UserAccessKeys {
+		var accessedTime string
+		if accessKey.AccessedAt > 0 {
+			accessedTime = timeutil.FormatTime("Y-m-d H:i:s", accessKey.AccessedAt)
+		}
+		accessKeyMaps = append(accessKeyMaps, maps.Map{
+			"id":           accessKey.Id,
+			"isOn":         accessKey.IsOn,
+			"uniqueId":     accessKey.UniqueId,
+			"secret":       accessKey.Secret,
+			"description":  accessKey.Description,
+			"accessedTime": accessedTime,
+		})
+	}
+	this.Data["accessKeys"] = accessKeyMaps
+
+	this.Show()
+}
diff --git a/internal/web/actions/default/users/accesskeys/updateIsOn.go b/internal/web/actions/default/users/accesskeys/updateIsOn.go
new file mode 100644
index 00000000..7720cecf
--- /dev/null
+++ b/internal/web/actions/default/users/accesskeys/updateIsOn.go
@@ -0,0 +1,28 @@
+package accesskeys
+
+import (
+	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+)
+
+type UpdateIsOnAction struct {
+	actionutils.ParentAction
+}
+
+func (this *UpdateIsOnAction) RunPost(params struct {
+	AccessKeyId int64
+	IsOn        bool
+}) {
+	defer this.CreateLogInfo("设置AccessKey %d 启用状态", params.AccessKeyId)
+
+	_, err := this.RPC().UserAccessKeyRPC().UpdateUserAccessKeyIsOn(this.AdminContext(), &pb.UpdateUserAccessKeyIsOnRequest{
+		UserAccessKeyId: params.AccessKeyId,
+		IsOn:            params.IsOn,
+	})
+	if err != nil {
+		this.ErrorPage(err)
+		return
+	}
+
+	this.Success()
+}
diff --git a/internal/web/actions/default/users/init.go b/internal/web/actions/default/users/init.go
index 3776f131..b08ad8f2 100644
--- a/internal/web/actions/default/users/init.go
+++ b/internal/web/actions/default/users/init.go
@@ -2,6 +2,7 @@ package users
 
 import (
 	"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
+	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users/accessKeys"
 	"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
 	"github.com/iwind/TeaGo"
 )
@@ -18,6 +19,14 @@ func init() {
 			GetPost("/update", new(UpdateAction)).
 			Post("/delete", new(DeleteAction)).
 			GetPost("/features", new(FeaturesAction)).
+
+			// AccessKeys
+			Prefix("/users/accessKeys").
+			Get("", new(accesskeys.IndexAction)).
+			GetPost("/createPopup", new(accesskeys.CreatePopupAction)).
+			Post("/delete", new(accesskeys.DeleteAction)).
+			Post("/updateIsOn", new(accesskeys.UpdateIsOnAction)).
+
 			EndAll()
 	})
 }
diff --git a/web/views/@default/users/@user_menu.html b/web/views/@default/users/@user_menu.html
index fe9b5c47..8f2b4b29 100644
--- a/web/views/@default/users/@user_menu.html
+++ b/web/views/@default/users/@user_menu.html
@@ -4,4 +4,5 @@
     
暂时还没有AccessKey。
+ +| AccessKey ID | +AccessKey密钥 | +备注 | +最后访问 | +状态 | +操作 | +
|---|---|---|---|---|---|
| {{accessKey.uniqueId}} | +{{accessKey.secret}} | +{{accessKey.description}} | ++ {{accessKey.accessedTime}} + 尚无访问 + | ++ 已启用 + 已禁用 + | ++ 禁用 + 启用 + 删除 + | +