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