Skip to content

Commit

Permalink
Token refresh & related API is done
Browse files Browse the repository at this point in the history
  • Loading branch information
songquanpeng committed Nov 5, 2022
1 parent 31654c8 commit 4605ba7
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 18 deletions.
37 changes: 20 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,34 @@
> 用以微信公众号的后端,提供登录验证功能
## 功能
+ [ ] 登录验证
+ [x] Access Token 自动刷新 & 提供外部访问接口
+ [ ] 自定义菜单
+ [ ] 登录验证
+ [ ] 自定义回复
+ [ ] Access Token 中控服务器

## 用法
## 配置
1.[GitHub Releases](https://github.com/songquanpeng/wechat-server/releases/latest) 下载可执行文件。
2. 运行:
2. 系统本身开箱即用,有一些环境变量可供配置:
1. `REDIS_CONN_STRING`: 设置之后,将启用 Redis。
+ 例如:`REDIS_CONN_STRING=redis://default:redispw@localhost:49153`
2. `SESSION_SECRET`:设置之后,将使用给定会话密钥。
+ 例如:`SESSION_SECRET=random_string`
3. `SQL_DSN`: 设置之后,将使用目标数据库而非 SQLite。
+ 例如:`SQL_DSN=root:123456@tcp(localhost:3306)/gofile`
3. 运行:
1. `chmod u+x wechat-server`
2. `./wechat-server --port 3000`
3. 初始账户用户名为 `root`,密码为 `123456`,记得登录后立刻修改密码。
4. 前往[微信公众号配置页面 -> 设置与开发 -> 基本配置](https://mp.weixin.qq.com/)获取 AppID 和 AppSecret,并在我们的配置页面填入上述信息,另外还需要配置 IP 白名单,按照页面上的提示完成即可。
5. 前往[微信公众号配置页面 -> 设置与开发 -> 基本配置](https://mp.weixin.qq.com/)填写以下配置:
4. 初始账户用户名为 `root`,密码为 `123456`,记得登录后立刻修改密码。
5. 前往[微信公众号配置页面 -> 设置与开发 -> 基本配置](https://mp.weixin.qq.com/)获取 AppID 和 AppSecret,并在我们的配置页面填入上述信息,另外还需要配置 IP 白名单,按照页面上的提示完成即可。
6. 前往[微信公众号配置页面 -> 设置与开发 -> 基本配置](https://mp.weixin.qq.com/)填写以下配置:
1. `URL` 填:`https://<your.domain>/api/wechat_verification`
2. `Token` 首先在我们的配置页面随便填写一个 Token,然后在微信公众号的配置页面填入同一个 Token 即可。
3. `EncodingAESKey` 点随机生成,然后在我们的配置页面填入该值。
4. 消息加解密方式选择明文模式。
7. 之后保存设置并启用设置。

## 配置
系统本身开箱即用。

有一些环境遍历可供配置:
1. `REDIS_CONN_STRING`: 设置之后,将启用 Redis。
+ 例如:`REDIS_CONN_STRING=redis://default:redispw@localhost:49153`
2. `SESSION_SECRET`:设置之后,将使用给定会话密钥。
+ 例如:`SESSION_SECRET=random_string`
3. `SQL_DSN`: 设置之后,将使用目标数据库而非 SQLite。
+ 例如:`SQL_DSN=root:123456@tcp(localhost:3306)/gofile`
## API
### 获取 Access Token
1. 请求方法:`GET`
2. URL:`/api/access_token`
3. 无参数,但是需要设置 HTTP 头部:`Authorization: your token`
68 changes: 68 additions & 0 deletions common/access-token-store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package common

import (
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
)

type accessTokenStore struct {
AccessToken string
Mutex sync.RWMutex
ExpirationSeconds int
}

type response struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}

var s accessTokenStore

func InitAccessTokenStore() {
go func() {
for {
RefreshAccessToken()
s.Mutex.RLock()
sleepDuration := Max(s.ExpirationSeconds, 60)
s.Mutex.RUnlock()
time.Sleep(time.Duration(sleepDuration) * time.Second)
}
}()
}

func RefreshAccessToken() {
// https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
client := http.Client{
Timeout: 5 * time.Second,
}
req, err := http.NewRequest("GET", fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", WeChatAppID, WeChatAppSecret), nil)
if err != nil {
SysError(err.Error())
return
}
responseData, err := client.Do(req)
if err != nil {
SysError("failed to refresh access token: " + err.Error())
return
}
var res response
err = json.NewDecoder(responseData.Body).Decode(&res)
if err != nil {
SysError("failed to decode response: " + err.Error())
return
}
s.Mutex.Lock()
s.AccessToken = res.AccessToken
s.ExpirationSeconds = res.ExpiresIn
s.Mutex.Unlock()
SysLog("access token refreshed")
}

func GetAccessToken() (string, int) {
s.Mutex.RLock()
defer s.Mutex.RUnlock()
return s.AccessToken, s.ExpirationSeconds
}
5 changes: 5 additions & 0 deletions common/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ func SysLog(s string) {
_, _ = fmt.Fprintf(gin.DefaultWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
}

func SysError(s string) {
t := time.Now()
_, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
}

func FatalLog(v ...any) {
t := time.Now()
_, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[FATAL] %v | %v \n", t.Format("2006/01/02 - 15:04:05"), v)
Expand Down
2 changes: 1 addition & 1 deletion common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func UnescapeHTML(x string) interface{} {
return template.HTML(x)
}

func IntMax(a int, b int) int {
func Max(a int, b int) int {
if a >= b {
return a
} else {
Expand Down
10 changes: 10 additions & 0 deletions controller/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ func WeChatVerification(c *gin.Context) {
}
}

func GetAccessToken(c *gin.Context) {
accessToken, expiration := common.GetAccessToken()
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"access_token": accessToken,
"expiration": expiration,
})
}

func SendEmailVerification(c *gin.Context) {
email := c.Query("email")
if err := common.Validate.Var(email, "required,email"); err != nil {
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ func main() {
// Initialize options
model.InitOptionMap()

// Initialize access token store
common.InitAccessTokenStore()

// Initialize HTTP server
server := gin.Default()
server.Use(middleware.CORS())
Expand Down
1 change: 1 addition & 0 deletions router/api-router.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func SetApiRouter(router *gin.Engine) {
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), controller.SendPasswordResetEmail)
apiRouter.GET("/user/reset", controller.SendNewPasswordEmail)
apiRouter.GET("/oauth/github", controller.GitHubOAuth)
apiRouter.GET("/access_token", middleware.AdminAuth(), controller.GetAccessToken)

userRoute := apiRouter.Group("/user")
{
Expand Down

0 comments on commit 4605ba7

Please sign in to comment.