Created
September 11, 2016 00:52
-
-
Save guotie/248d6dd21d0b8903d3ddeb94f20bc41c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"dxmall/models/user" | |
"dxmall/utils" | |
"fmt" | |
"net/http" | |
"strings" | |
"github.com/gin-gonic/gin" | |
"github.com/jinzhu/gorm" | |
) | |
var ( | |
AcessTokenInvalid = fmt.Errorf("access token not found in header or invalid") | |
) | |
func getUser(c *gin.Context) (*user.DxUser, error) { | |
token, ok := getToken(c.Request) | |
if !ok { | |
return nil, AcessTokenInvalid | |
} | |
return user.GetUserByToken(utils.ContextDB(c), utils.ContextRedis(c), token) | |
} | |
// 从request头部提取access token | |
// Authorization: Bearer F_9.B5f-4.1JqM | |
func getToken(req *http.Request) (string, bool) { | |
auth := req.Header.Get("Authorization") | |
if auth == "" { | |
return "", false | |
} | |
return parseBearer(auth) | |
} | |
// parseBearer parses an Bearer Authentication string. | |
// "Bearer F_9.B5f-4.1JqM" returns F_9.B5f-4.1JqM, true | |
func parseBearer(auth string) (token string, ok bool) { | |
const prefix = "Bearer " | |
if !strings.HasPrefix(auth, prefix) { | |
return | |
} | |
return auth[len(prefix):], true | |
} | |
// 登出 | |
func logoutEndpoint(c *gin.Context) { | |
token, ok := getToken(c.Request) | |
if !ok { | |
utils.JsonError(c, utils.ERROR_NO_ACCESS_TOKEN, "access token not exist or invalid") | |
return | |
} | |
rc := utils.ContextRedis(c) | |
err := user.RemoveTokens(rc, token) | |
if err != nil { | |
utils.JsonError(c, utils.ERROR_RM_TOKEN_FAILED, "remove token failed: %v", err) | |
return | |
} | |
utils.JsonData(c, "logout success") | |
} | |
type RegFrom struct { | |
Phone string `binding:"required" json:"phone"` | |
Password string `binding:"required" json:"password"` | |
Password2 string `binding:"required" json:"password2"` | |
UserName string `json:"user_name"` | |
} | |
// POST | |
// 注册 | |
// 注册时, 客户端必须把客户端的Basic 认证提交 | |
func registerEndpoint(c *gin.Context) { | |
var form RegFrom | |
db := utils.ContextDB(c) | |
rc := utils.ContextRedis(c) | |
client, err := getClient(db, c.Request) | |
if err != nil { | |
utils.JsonError(c, utils.ERROR_OAUTH_CLIENT, "get oauth2 client failed: %v", err) | |
return | |
} | |
err = c.BindJSON(&form) | |
if err != nil { | |
utils.JsonError(c, utils.ERROR_OAUTH_FORM, "register form invalid: %v", err) | |
return | |
} | |
if form.Password != form.Password2 { | |
utils.JsonError(c, utils.ERROR_PASSWD_NOT_EQUAL, "password not equal") | |
return | |
} | |
ipaddr := c.Request.RemoteAddr | |
u, code, err := user.CreateUser(db, form.Phone, form.Password, form.UserName, ipaddr, 0) | |
if err != nil { | |
utils.JsonError(c, code, "create user failed: %v", err) | |
return | |
} | |
// 这里生成token, 并同时把token下发 | |
accessToken, refreshToken, err := u.GetTokens(utils.ContextRedis(c), client) | |
if err != nil { | |
utils.JsonError(c, utils.ERROR_CREATE_TOKEN, "create token for user %d client %s failed: %v", | |
u.Id, client.Name, err.Error()) | |
return | |
} | |
utils.JsonData(c, map[string]interface{}{ | |
"uid": u.Id, | |
"phone": u.UserPhone, | |
"name": u.UserName, | |
"access_token": accessToken, | |
"refresh_token": refreshToken, | |
}) | |
} | |
type TokenForm struct { | |
GrantType string `binding:"required" json:"grant_type"` | |
Username string `binding:"required" json:"username"` | |
Password string `binding:"required" json:"password"` | |
} | |
type RefreshForm struct { | |
GrantType string `binding:"required" json:"grant_type"` | |
RefreshToken string `binding:"required" json:"refresh_token"` | |
} | |
// client | |
func getClient(db *gorm.DB, req *http.Request) (*user.DxClient, error) { | |
// client name, client secret | |
cname, secret, ok := req.BasicAuth() | |
if !ok { | |
return nil, fmt.Errorf("request header Authorization error") | |
} | |
return user.AuthenClient(db, cname, secret) | |
} | |
// | |
// POST /api/v1/auth/token | |
// oauth2用密码方式登陆 | |
// | |
// 首先, 在request header中要有Basic Authorization 头部 | |
// 然后, 根据头部的Basic Authorization字段验证用户的client | |
// 然后, 从request body中取出登陆用户的用户名(手机号码)和用户密码, 验证用户名、密码 | |
// 最后, 返回token, user id等信息 | |
// | |
// 该接口返回一个验证成功的token json结构体 | |
func tokenEndpoint(c *gin.Context) { | |
var ( | |
err error | |
u *user.DxUser | |
form TokenForm | |
) | |
db := utils.ContextDB(c) | |
client, err := getClient(db, c.Request) | |
if err != nil { | |
utils.JsonError(c, utils.ERROR_OAUTH_CLIENT, "get oauth2 client failed: %v", err) | |
return | |
} | |
// bind json to form | |
err = c.BindJSON(&form) | |
if err != nil { | |
utils.JsonError(c, utils.ERROR_OAUTH_FORM, "bind token form failed: %s", err.Error()) | |
return | |
} | |
if strings.ToLower(form.GrantType) != "password" { | |
utils.JsonError(c, utils.ERROR_OAUTH_GRANT, "only accept grant type password") | |
return | |
} | |
// authenticate user | |
u, err = user.AuthenUser(db, form.Username, form.Password) | |
if err != nil { | |
utils.JsonError(c, utils.ERROR_OAUTH_USER, "Auth failed: %s", err.Error()) | |
return | |
} | |
accessToken, refreshToken, err := u.GetTokens(utils.ContextRedis(c), client) | |
if err != nil { | |
utils.JsonError(c, utils.ERROR_CREATE_TOKEN, "create token for user %d client %s failed: %v", | |
u.Id, client.Name, err.Error()) | |
return | |
} | |
utils.JsonData(c, map[string]interface{}{ | |
"uid": u.Id, | |
"phone": u.UserPhone, | |
"name": u.UserName, | |
"token_type": accessToken.TokenType, | |
"expires_in": accessToken.ExpiresIn, | |
"access_token": accessToken.Token, | |
"refresh_token": refreshToken, | |
}) | |
//c.JSON(200, gin.H{) | |
} | |
/* | |
http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html | |
如果用户访问的时候,客户端的"访问令牌"已经过期,则需要使用"更新令牌"申请一个新的访问令牌。 | |
客户端发出更新令牌的HTTP请求,包含以下参数: | |
granttype:表示使用的授权模式,此处的值固定为"refreshtoken",必选项。 | |
refresh_token:表示早前收到的更新令牌,必选项。 | |
scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。 | |
下面是一个例子。 | |
POST /token HTTP/1.1 | |
Host: server.example.com | |
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW | |
Content-Type: application/x-www-form-urlencoded | |
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA | |
*/ | |
func refreshEndpoint(c *gin.Context) { | |
db := utils.ContextDB(c) | |
client, err := getClient(db, c.Request) | |
if err != nil { | |
utils.JsonError(c, utils.ERROR_OAUTH_CLIENT, "get oauth2 client failed: %v", err) | |
return | |
} | |
var form RefreshForm | |
// bind json to form | |
err = c.BindJSON(&form) | |
if err != nil { | |
utils.JsonError(c, utils.ERROR_OAUTH_FORM, "bind token form failed: %s", err.Error()) | |
return | |
} | |
if strings.ToLower(form.GrantType) != "refresh_token" { | |
utils.JsonError(c, utils.ERROR_OAUTH_GRANT, "only accept grant type refresh_token") | |
return | |
} | |
rc := utils.ContextRedis(c) | |
token, err := user.RefreshAccessToken(rc, client.Name, form.RefreshToken, "", client.AccessSeconds) | |
if err != nil { | |
utils.JsonError(c, utils.ERROR_REFRESH_TOKEN, "refresh token failed: %s", err.Error()) | |
return | |
} | |
utils.JsonData(c, map[string]interface{}{ | |
"token_type": token.TokenType, | |
"expires_in": token.ExpiresIn, | |
"access_token": token, | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment