232 lines
6.1 KiB
Go
232 lines
6.1 KiB
Go
|
package jwt
|
|||
|
|
|||
|
import (
|
|||
|
"fmt"
|
|||
|
"net/http"
|
|||
|
"time"
|
|||
|
|
|||
|
"github.com/gin-gonic/gin"
|
|||
|
gojwt "github.com/golang-jwt/jwt"
|
|||
|
"github.com/ycyxuehan/zelda/apiserver/auth/api"
|
|||
|
)
|
|||
|
|
|||
|
type TokenClaim struct {
|
|||
|
Username string `json:"username"`
|
|||
|
Group string `json:"group"`
|
|||
|
gojwt.StandardClaims
|
|||
|
}
|
|||
|
|
|||
|
func (t *TokenClaim) Expired() bool {
|
|||
|
return t.ExpiresAt <= time.Now().Unix()
|
|||
|
}
|
|||
|
|
|||
|
type tokenCache struct {
|
|||
|
kubernetesToken string
|
|||
|
claim *TokenClaim
|
|||
|
cert string
|
|||
|
}
|
|||
|
|
|||
|
type manager struct {
|
|||
|
tokenStore map[string]tokenCache
|
|||
|
secret string
|
|||
|
term time.Duration
|
|||
|
}
|
|||
|
|
|||
|
func NewManager(secret string, term time.Duration) api.AuthManager {
|
|||
|
m := manager{
|
|||
|
tokenStore: make(map[string]tokenCache),
|
|||
|
secret: secret,
|
|||
|
term: term,
|
|||
|
}
|
|||
|
return &m
|
|||
|
}
|
|||
|
|
|||
|
//登录
|
|||
|
func (m *manager) Login(f api.IdentifyFunc, data *api.AuthentitionRequest) *api.AuthentitionResponse {
|
|||
|
if _, ok := m.IsLogined(data.Username); ok {
|
|||
|
return &api.AuthentitionResponse{Error: fmt.Errorf("user is already logined"), Code: 1000}
|
|||
|
}
|
|||
|
res, err := f(data)
|
|||
|
if err != nil {
|
|||
|
return &api.AuthentitionResponse{Error: err, Code: 1001}
|
|||
|
}
|
|||
|
tokenClaim, err := m.GenerateTokenClaim(data)
|
|||
|
if err != nil {
|
|||
|
return &api.AuthentitionResponse{Error: err, Code: 1002}
|
|||
|
}
|
|||
|
tokenString, err := m.GenerateToken(tokenClaim)
|
|||
|
if err != nil {
|
|||
|
return &api.AuthentitionResponse{Error: err, Code: 1003}
|
|||
|
}
|
|||
|
//存储登录状态
|
|||
|
m.StoreLoginState(data.Username, res, tokenClaim)
|
|||
|
|
|||
|
return &api.AuthentitionResponse{Token: tokenString}
|
|||
|
}
|
|||
|
|
|||
|
func (m *manager) Logout(data *api.AuthentitionRequest) *api.AuthentitionResponse {
|
|||
|
//解析token,确认有logout权限
|
|||
|
claim, err := m.ParseTokenClaim(data.Token)
|
|||
|
if err != nil {
|
|||
|
return &api.AuthentitionResponse{Error: err, Code: 1004}
|
|||
|
|
|||
|
}
|
|||
|
if c, ok := m.IsLogined(claim.Username); ok {
|
|||
|
if claim.ExpiresAt == c.ExpiresAt {
|
|||
|
delete(m.tokenStore, data.Username)
|
|||
|
return &api.AuthentitionResponse{Message: "success"}
|
|||
|
}
|
|||
|
}
|
|||
|
return &api.AuthentitionResponse{Error: fmt.Errorf("user not login or token invalid"), Code: 1005}
|
|||
|
}
|
|||
|
|
|||
|
func (m *manager) Refresh(data *api.AuthentitionRequest) *api.AuthentitionResponse {
|
|||
|
cliam, err := m.ParseTokenClaim(data.Token)
|
|||
|
if err != nil {
|
|||
|
return &api.AuthentitionResponse{Error: err, Code: 1006}
|
|||
|
}
|
|||
|
//已过期
|
|||
|
if cliam.Valid() != nil {
|
|||
|
return &api.AuthentitionResponse{Error: fmt.Errorf("cannot use an invalid token to refresh: %v", err), Code: 1007}
|
|||
|
|
|||
|
}
|
|||
|
//找不到登陆状态
|
|||
|
if _, ok := m.IsLogined(cliam.Username); !ok {
|
|||
|
return &api.AuthentitionResponse{Error: fmt.Errorf("token invalid, user maybe not login"), Code: 1005}
|
|||
|
|
|||
|
}
|
|||
|
//重置有效期
|
|||
|
cliam.ExpiresAt = time.Now().Add(m.term).Unix()
|
|||
|
m.ReSetTokenClaim(cliam)
|
|||
|
|
|||
|
tokenStr, err := m.GenerateToken(cliam)
|
|||
|
|
|||
|
if err != nil {
|
|||
|
return &api.AuthentitionResponse{Error: err, Code: 1003}
|
|||
|
}
|
|||
|
|
|||
|
return &api.AuthentitionResponse{Token: tokenStr}
|
|||
|
}
|
|||
|
|
|||
|
func (m *manager) KubernetesToken(data *api.AuthentitionRequest) (string, error) {
|
|||
|
if t, ok := m.tokenStore[data.Username]; ok {
|
|||
|
return t.kubernetesToken, nil
|
|||
|
}
|
|||
|
return "", fmt.Errorf("not found")
|
|||
|
}
|
|||
|
|
|||
|
func (m *manager) KubernetesCert(data *api.AuthentitionRequest) (string, error) {
|
|||
|
if t, ok := m.tokenStore[data.Username]; ok {
|
|||
|
return t.cert, nil
|
|||
|
}
|
|||
|
return "", fmt.Errorf("not found")
|
|||
|
}
|
|||
|
|
|||
|
//生成token claim对象
|
|||
|
func (m *manager) GenerateTokenClaim(data *api.AuthentitionRequest) (*TokenClaim, error) {
|
|||
|
now := time.Now()
|
|||
|
claim := TokenClaim{
|
|||
|
Username: data.Username,
|
|||
|
Group: data.Group,
|
|||
|
StandardClaims: gojwt.StandardClaims{
|
|||
|
ExpiresAt: now.Add(m.term).Unix(),
|
|||
|
},
|
|||
|
}
|
|||
|
return &claim, nil
|
|||
|
}
|
|||
|
|
|||
|
//从string解析token claim
|
|||
|
func (m *manager) ParseTokenClaim(token string) (*TokenClaim, error) {
|
|||
|
tokenClaims, err := gojwt.ParseWithClaims(token, &TokenClaim{}, func(token *gojwt.Token) (interface{}, error) {
|
|||
|
return m.secret, nil
|
|||
|
})
|
|||
|
|
|||
|
if tokenClaims != nil {
|
|||
|
if claims, ok := tokenClaims.Claims.(*TokenClaim); ok && tokenClaims.Valid {
|
|||
|
return claims, nil
|
|||
|
}
|
|||
|
}
|
|||
|
//
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
|
|||
|
//从token claim 生成 string token
|
|||
|
func (m *manager) GenerateToken(claim *TokenClaim) (string, error) {
|
|||
|
tokenClaims := gojwt.NewWithClaims(gojwt.SigningMethodHS256, claim)
|
|||
|
token, err := tokenClaims.SignedString(m.secret)
|
|||
|
return token, err
|
|||
|
}
|
|||
|
|
|||
|
//存储登录状态
|
|||
|
func (m *manager) StoreLoginState(user string, result api.IdentifyResult, claim *TokenClaim) {
|
|||
|
//储存登录状态
|
|||
|
t := tokenCache{
|
|||
|
kubernetesToken: result.KubernetesToken,
|
|||
|
claim: claim,
|
|||
|
cert: result.Cert,
|
|||
|
}
|
|||
|
m.tokenStore[user] = t
|
|||
|
}
|
|||
|
|
|||
|
//是否已登录
|
|||
|
func (m *manager) IsLogined(user string) (*TokenClaim, bool) {
|
|||
|
t, ok := m.tokenStore[user]
|
|||
|
if ok {
|
|||
|
return t.claim, true
|
|||
|
}
|
|||
|
return nil, false
|
|||
|
}
|
|||
|
|
|||
|
//重置token状态
|
|||
|
func (m *manager) ReSetTokenClaim(claim *TokenClaim) {
|
|||
|
if t, ok := m.tokenStore[claim.Username]; ok {
|
|||
|
t.claim = claim
|
|||
|
m.tokenStore[claim.Username] = t
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//token是否有效
|
|||
|
func (m *manager) TokenValid(claim *TokenClaim) bool {
|
|||
|
if t, ok := m.tokenStore[claim.Username]; ok {
|
|||
|
if t.claim.ExpiresAt == claim.ExpiresAt && claim.Valid() == nil {
|
|||
|
return true
|
|||
|
}
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
//中间件
|
|||
|
func (m *manager) MiddleWare() gin.HandlerFunc {
|
|||
|
return func(c *gin.Context) {
|
|||
|
tokenString := c.GetHeader("AccessToken")
|
|||
|
if tokenString == "" {
|
|||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
|||
|
return
|
|||
|
}
|
|||
|
tokenClaim, err := m.ParseTokenClaim(tokenString)
|
|||
|
if err != nil {
|
|||
|
c.AbortWithError(http.StatusUnauthorized, err)
|
|||
|
return
|
|||
|
}
|
|||
|
if m.TokenValid(tokenClaim) {
|
|||
|
c.AbortWithError(http.StatusUnauthorized, fmt.Errorf("token invalid"))
|
|||
|
return
|
|||
|
}
|
|||
|
t, err := m.KubernetesToken(&api.AuthentitionRequest{Username: tokenClaim.Username})
|
|||
|
if err != nil || t == "" {
|
|||
|
c.AbortWithError(http.StatusUnauthorized, fmt.Errorf("k8s token not found"))
|
|||
|
}
|
|||
|
c.Request.Header.Set("Authorization", fmt.Sprintf("bearer %s", t))
|
|||
|
cert, _ := m.KubernetesCert(&api.AuthentitionRequest{Username: tokenClaim.Username})
|
|||
|
c.Request.Header.Set("cert", cert)
|
|||
|
c.Next()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//初始化auth接口
|
|||
|
func (m *manager) InitAuthRoute(identifyFunc api.IdentifyFunc, authGroup *gin.RouterGroup) {
|
|||
|
authGroup.POST("/login", m.HandlerLogin(identifyFunc))
|
|||
|
authGroup.POST("/logout", m.HandlerLogout())
|
|||
|
authGroup.POST("/refresh", m.HandlerRefreshToken())
|
|||
|
}
|