JWT
SIGNINKEYを環境変数に設定しておく。なんでもいいがわかりにくいもの。→これを元に照合する
code: jwt.go
package auth
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
jwt "github.com/dgrijalva/jwt-go"
"github.com/keiya01/eserver/model"
)
type JWTToken struct {
UserID int json:"userID"
UserEmail string json:"userEmail"
jwt.StandardCraims
}
// GetTokenHandler get token
func (j *JWTToken) GetJWTToken() string {
// headerのセット
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"admin": true,
"aud": []string{ // 自分のAPIのURIと許可するURI
},
"iat": time.Now(), // JWT作成時間
"exp": time.Now().Add(time.Hour * 24).Unix(), // JWT削除時間
// ここから下は自由に定義してかまわない。おもにユーザー情報を含める。(パスワード以外)
"userEmail": j.UserEmail,
"userID": j.UserID,
})
// 電子署名
tokenString, _ := token.SignedString([]byte(os.Getenv("SIGNINGKEY")))
return tokenString
}
//ここでログインしているかどうかの検証をしている
func JWTAuthentication(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//List of endpoints that doesn't require auth
notAuth := []string{"/api/users/create", "/api/users/login"}
//current request path
requestPath := r.URL.Path
//check if request does not need authentication, serve the request if it doesn't need it
for _, value := range notAuth {
if value == requestPath {
next.ServeHTTP(w, r)
return
}
}
var resp model.Response
//Grab the token from the header
tokenHeader := r.Header.Get("Authorization")
//Token is missing, returns with error code 403 Unauthorized
if tokenHeader == "" {
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
resp.Error = model.NewError("認証エラーが発生しました")
json.NewEncoder(w).Encode(resp)
return
}
//The token normally comes in format Bearer {token-body}, we check if the retrieved token matched this requirement
splitted := strings.Split(tokenHeader, " ")
if len(splitted) != 2 {
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
resp.Error = model.NewError("トークンがサーバーと一致しませんでした")
json.NewEncoder(w).Encode(resp)
return
}
tokenPart := splitted1 //Grab the token part, what we are truly interested in // ここではJWTのトークンと環境変数のトークンを比較して一致すればCraims(ユーザー情報部分)を返す
token, err := jwt.Parse(tokenPart, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("SIGNINGKEY")), nil
})
//Malformed token, returns with http code 403 as usual
if err != nil {
log.Printf("jwtParse: %v , value = %v", err, tk)
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
resp.Error = model.NewError("トークンを認証できませんでした")
json.NewEncoder(w).Encode(resp)
return
}
//Token is invalid, maybe not signed on this server
if !token.Valid {
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
resp.Error = model.NewError("トークンが認証されませんでした")
json.NewEncoder(w).Encode(resp)
return
}
// Craimsを独自定義している場合、MapCraims型から値を取り出せる
craims := token.craims.(jwt.MapCraims)
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
w.WriteHeader(http.StatusForbidden)
w.Header().Add("Content-Type", "application/json")
resp.Error = model.NewError("トークンが認証されませんでした")
json.NewEncoder(w).Encode(resp)
return
}
//Everything went well, proceed with the request and set the caller to the user retrieved from the parsed token
ctx := context.WithValue(r.Context(), "userID", claims"userID") // "userID"にclaimsUserIdを入れる // 値を取り出すにはr.Context().Value()と書く
// interface{}で返されるため型変換が必要だが、数値はfloat64型に変換する必要がある
r = r.WithContext(ctx)
next.ServeHTTP(w, r) //proceed in the middleware chain!
})
}
code: user_controller.go
package controller
import (
"encoding/json"
"log"
"net/http"
"os"
jwt "github.com/dgrijalva/jwt-go"
"github.com/keiya01/eserver/auth"
"github.com/keiya01/eserver/model"
"github.com/keiya01/eserver/service"
"github.com/keiya01/eserver/service/database"
)
type UserController struct{}
func (u UserController) Login(w http.ResponseWriter, r *http.Request) {
db := database.NewHandler()
s := service.NewService(db)
defer s.Close()
var params model.User
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
panic(err)
}
var user model.User
var response model.Response
if err := s.FindOne(&user, "email = ?", params.Email); err != nil {
response.Error = model.NewError("メールアドレスが見つかりませんでした")
json.NewEncoder(w).Encode(response)
return
}
if auth.ComparePassword(user.Password, params.Password) {
//Create JWT token
tk := &auth.JWTToken{UserID: user.ID, UserEmail: user.Email}
token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk)
tokenString, _ := token.SignedString([]byte(os.Getenv("token_password")))
response = model.Response{
Token: tokenString, //Store the token in the response
Message: "ログインに成功しました",
Data: user,
}
} else {
response.Error = model.NewError("パスワードが一致しませんでした")
}
json.NewEncoder(w).Encode(response)
}
func (u UserController) Create(w http.ResponseWriter, r *http.Request) {
db := database.NewHandler()
s := service.NewService(db)
defer s.Close()
log.Printf("create: %v", r.Body)
var params model.User
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
panic(err)
}
encryptedPassword, err := auth.EncryptPassword(params.Password)
if err != nil {
panic(err)
}
user := model.User{
Email: params.Email,
Password: encryptedPassword,
}
var resp model.Response
if err := s.Create(&user); err != nil {
resp.Error = model.NewError("データを保存できませんでした")
json.NewEncoder(w).Encode(resp)
return
}
token := auth.JWTToken{
UserID: user.ID,
UserEmail: user.Email,
}
jwtToken := token.GetJWTToken()
resp.Token = jwtToken
resp.Message = "データを保存しました"
json.NewEncoder(w).Encode(resp)
}
参考