在 使用Golang和MongoDB构建 RESTful API已经实现了一个简单的 RESTful API应用,但是对于有些API接口需要授权之后才能访问,在这篇文章中就用 jwt 做一个基于Token的身份验证,关于 jwt 请访问 JWT有详细的说明,而且有各个语言实现的库,请根据需要使用对应的版本。
需要先安装 jwt-go 接口 go get github.com/dgrijalva/jwt-go
新增注册登录接口,并在登录时生成token
自定义返回结果,并封装 helper/utils.go
1type Response struct {
2 Code int `json:"code"`
3 Msg string `json:"msg"`
4 Data interface{} `json:"data"`
5}
6
7func ResponseWithJson(w http.ResponseWriter, code int, payload interface{}) {
8 response, _ := json.Marshal(payload)
9 w.Header().Set("Content-Type", "application/json")
10 w.WriteHeader(code)
11 w.Write(response)
12}
模型 models/user.go
1type User struct {
2 UserName string `bson:"username" json:"username"`
3 Password string `bson:"password" json:"password"`
4}type JwtToken struct {
5 Token string `json:"token"`
6}
控制器 controllers/user.go
1func Register(w http.ResponseWriter, r *http.Request) {
2 var user models.User
3 err := json.NewDecoder(r.Body).Decode(&user) if err != nil || user.UserName == "" || user.Password == "" {
4 helper.ResponseWithJson(w, http.StatusBadRequest,
5 helper.Response{Code: http.StatusBadRequest, Msg: "bad params"}) return
6 }
7 err = models.Insert(db, collection, user) if err != nil {
8 helper.ResponseWithJson(w, http.StatusInternalServerError,
9 helper.Response{Code: http.StatusInternalServerError, Msg: "internal error"})
10 }
11}
12
13func Login(w http.ResponseWriter, r *http.Request) {
14 var user models.User
15 err := json.NewDecoder(r.Body).Decode(&user) if err != nil {
16 helper.ResponseWithJson(w, http.StatusBadRequest,
17 helper.Response{Code: http.StatusBadRequest, Msg: "bad params"})
18 }
19 exist := models.IsExist(db, collection, bson.M{"username": user.UserName}) if exist {
20 token, _ := auth.GenerateToken(&user)
21 helper.ResponseWithJson(w, http.StatusOK,
22 helper.Response{Code: http.StatusOK, Data: models.JwtToken{Token: token}})
23 } else {
24 helper.ResponseWithJson(w, http.StatusNotFound,
25 helper.Response{Code: http.StatusNotFound, Msg: "the user not exist"})
26 }
27}
生成Token auth/middleware.go
1func GenerateToken(user *models.User) (string, error) {
2 token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "username": user.UserName,
3 //"exp": time.Now().Add(time.Hour * 2).Unix(),// 可以添加过期时间
4 }) return token.SignedString([]byte("secret"))//对应的字符串请自行生成,最后足够使用加密后的字符串
5}
http中间件
go http的中间件实现起来很简单,只需要实现一个函数签名func(http.Handler) http.Handler的函数即可。
1func middlewareHandler(next http.Handler) http.Handler{ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
2 // 执行handler之前的逻辑
3 next.ServeHTTP(w, r)
4 // 执行完毕handler后的逻辑
5 })
6}
我们使用的 mux 作为路由,本身支持在路由中添加中间件,改造一下之前的路由逻辑
routes/routes.go
1type Route struct {
2 Method string
3 Pattern string
4 Handler http.HandlerFunc
5 Middleware mux.MiddlewareFunc //添加中间件
6}
7
8func NewRouter() *mux.Router {
9 router := mux.NewRouter() for _, route := range routes {
10 r := router.Methods(route.Method).
11 Path(route.Pattern)
12 //如果这个路由有中间件的逻辑,需要通过中间件先处理一下 if route.Middleware != nil {
13 r.Handler(route.Middleware(route.Handler))
14 } else {
15 r.Handler(route.Handler)
16 }
17 } return router
18}
实现身份验证的中间件
auth/middleware.go
验证的信息放在http Header中
1func TokenMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2 tokenStr := r.Header.Get("authorization") if tokenStr == "" {
3 helper.ResponseWithJson(w, http.StatusUnauthorized,
4 helper.Response{Code: http.StatusUnauthorized, Msg: "not authorized"})
5 } else {
6 token, _ := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
7 helper.ResponseWithJson(w, http.StatusUnauthorized,
8 helper.Response{Code: http.StatusUnauthorized, Msg: "not authorized"}) return nil, fmt.Errorf("not authorization")
9 } return []byte("secret"), nil
10 }) if !token.Valid {
11 helper.ResponseWithJson(w, http.StatusUnauthorized,
12 helper.Response{Code: http.StatusUnauthorized, Msg: "not authorized"})
13 } else {
14 next.ServeHTTP(w, r)
15 }
16 }
17 })
18}
对需要验证的路由添加中间件
1register("GET", "/movies", controllers.AllMovies, auth.TokenMiddleware) //需要中间件逻辑
2register("GET", "/movies/{id}", controllers.FindMovie, nil)//不需要中间件复制代码
验证
登录之后,返回对应的token信息
1//请求 post http://127.0.0.1:8080/login
2//返回
3
4{ "code": 200, "msg": "", "data": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNvZGVybWluZXIifQ.pFzJLU8vnzWiweFKzHRsawyWA2jfuDIPlDU4zE92O7c"
5 }
6}
获取所有的电影信息时
1//请求 post http://127.0.0.1:8080/movies
2在 Header中设置 "authorization":token
3如果没有设置header会报 401 错误
4
5{ "code": 401, "msg": "not authorized", "data": null
6}
源码 Github
原文发布时间为:2018-07-19
本文作者:CoderMiner
本文来自云栖社区合作伙伴“Golang语言社区”,了解相关信息可以关注“Golang语言社区”