用中间件验证

This commit is contained in:
xmdhs 2023-10-08 15:32:21 +08:00
parent a51d39b141
commit fc7f3234dc
No known key found for this signature in database
GPG Key ID: E809D6D43DEFCC95
10 changed files with 93 additions and 128 deletions

View File

@ -7,7 +7,6 @@ import (
"github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/service"
utilsService "github.com/xmdhs/authlib-skin/service/utils"
"github.com/xmdhs/authlib-skin/utils"
)
@ -60,10 +59,6 @@ func (h *Handel) UserInfo() http.HandlerFunc {
t := ctx.Value(tokenKey).(*model.TokenClaims)
u, err := h.webService.Info(ctx, t)
if err != nil {
if errors.Is(err, utilsService.ErrTokenInvalid) {
h.handleError(ctx, w, "token 无效", model.ErrAuth, 401, slog.LevelDebug)
return
}
h.handleError(ctx, w, err.Error(), model.ErrService, 500, slog.LevelWarn)
return
}

View File

@ -2,12 +2,11 @@ package yggdrasil
import (
"encoding/json"
"errors"
"net/http"
"github.com/samber/lo"
"github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/model/yggdrasil"
sutils "github.com/xmdhs/authlib-skin/service/utils"
"github.com/xmdhs/authlib-skin/utils"
)
@ -18,18 +17,15 @@ func (y *Yggdrasil) SessionJoin() http.HandlerFunc {
if !has {
return
}
t := ctx.Value(tokenKey).(*model.TokenClaims)
ip, err := utils.GetIP(r)
if err != nil {
y.handleYgError(ctx, w, err)
return
}
err = y.yggdrasilService.SessionJoin(ctx, a, ip)
err = y.yggdrasilService.SessionJoin(ctx, a, t, ip)
if err != nil {
if errors.Is(err, sutils.ErrTokenInvalid) {
y.logger.DebugContext(ctx, err.Error())
handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "Invalid token.", Error: "ForbiddenOperationException"}, 403)
return
}
y.handleYgError(ctx, w, err)
return
}

View File

@ -12,8 +12,10 @@ import (
"strings"
"github.com/go-chi/chi/v5"
"github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/model/yggdrasil"
"github.com/xmdhs/authlib-skin/service/utils"
yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil"
)
func (y *Yggdrasil) getTokenbyAuthorization(ctx context.Context, w http.ResponseWriter, r *http.Request) string {
@ -51,10 +53,7 @@ func (y *Yggdrasil) PutTexture() http.HandlerFunc {
if !ok {
return
}
token := y.getTokenbyAuthorization(ctx, w, r)
if token == "" {
return
}
t := ctx.Value(tokenKey).(*model.TokenClaims)
model := r.FormValue("model")
@ -100,9 +99,9 @@ func (y *Yggdrasil) PutTexture() http.HandlerFunc {
return
}
err = y.yggdrasilService.PutTexture(ctx, token, skin, model, uuid, textureType)
err = y.yggdrasilService.PutTexture(ctx, t, skin, model, uuid, textureType)
if err != nil {
if errors.Is(err, utils.ErrTokenInvalid) {
if errors.Is(err, yggdrasilS.ErrUUIDNotEq) {
y.logger.DebugContext(ctx, err.Error())
w.WriteHeader(401)
return
@ -120,7 +119,7 @@ func getUUIDbyParams(ctx context.Context, l *slog.Logger, w http.ResponseWriter)
textureType := chi.URLParamFromCtx(ctx, "textureType")
if uuid == "" {
l.DebugContext(ctx, "路径中缺少参数 uuid")
handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "路径中缺少参数 uuid / textureType"}, 400)
handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "路径中缺少参数 uuid"}, 400)
return "", "", false
}
if textureType != "skin" && textureType != "cape" {
@ -139,13 +138,10 @@ func (y *Yggdrasil) DelTexture() http.HandlerFunc {
if !ok {
return
}
token := y.getTokenbyAuthorization(ctx, w, r)
if token == "" {
return
}
err := y.yggdrasilService.DelTexture(ctx, uuid, token, textureType)
t := ctx.Value(tokenKey).(*model.TokenClaims)
err := y.yggdrasilService.DelTexture(ctx, uuid, t, textureType)
if err != nil {
if errors.Is(err, utils.ErrTokenInvalid) {
if errors.Is(err, yggdrasilS.ErrUUIDNotEq) {
y.logger.DebugContext(ctx, err.Error())
w.WriteHeader(401)
return

View File

@ -7,8 +7,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/samber/lo"
"github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/model/yggdrasil"
sutils "github.com/xmdhs/authlib-skin/service/utils"
yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil"
)
@ -36,21 +36,6 @@ func (y *Yggdrasil) Authenticate() http.HandlerFunc {
func (y *Yggdrasil) Validate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cxt := r.Context()
a, has := getAnyModel[yggdrasil.ValidateToken](cxt, w, r.Body, y.validate, y.logger)
if !has {
return
}
err := y.yggdrasilService.ValidateToken(cxt, a)
if err != nil {
if errors.Is(err, sutils.ErrTokenInvalid) {
y.logger.DebugContext(cxt, err.Error())
handleYgError(cxt, w, yggdrasil.Error{ErrorMessage: "Invalid token.", Error: "ForbiddenOperationException"}, 403)
return
}
y.handleYgError(cxt, w, err)
return
}
w.WriteHeader(204)
}
}
@ -80,16 +65,11 @@ func (y *Yggdrasil) Invalidate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(204)
cxt := r.Context()
a, has := getAnyModel[yggdrasil.ValidateToken](cxt, w, r.Body, y.validate, y.logger)
if !has {
return
}
err := y.yggdrasilService.Invalidate(cxt, a.AccessToken)
t := cxt.Value(tokenKey).(*model.TokenClaims)
err := y.yggdrasilService.Invalidate(cxt, t)
if err != nil {
if errors.Is(err, sutils.ErrTokenInvalid) {
y.logger.DebugContext(cxt, err.Error())
return
}
y.logger.WarnContext(cxt, err.Error())
}
}
@ -98,17 +78,9 @@ func (y *Yggdrasil) Invalidate() http.HandlerFunc {
func (y *Yggdrasil) Refresh() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cxt := r.Context()
a, has := getAnyModel[yggdrasil.RefreshToken](cxt, w, r.Body, y.validate, y.logger)
if !has {
return
}
t, err := y.yggdrasilService.Refresh(cxt, a)
token := cxt.Value(tokenKey).(*model.TokenClaims)
t, err := y.yggdrasilService.Refresh(cxt, token)
if err != nil {
if errors.Is(err, sutils.ErrTokenInvalid) {
y.logger.DebugContext(cxt, err.Error())
handleYgError(cxt, w, yggdrasil.Error{ErrorMessage: "Invalid token.", Error: "ForbiddenOperationException"}, 403)
return
}
y.handleYgError(cxt, w, err)
return
}
@ -176,17 +148,9 @@ func (y *Yggdrasil) BatchProfile() http.HandlerFunc {
func (y *Yggdrasil) PlayerCertificates() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
token := y.getTokenbyAuthorization(ctx, w, r)
if token == "" {
return
}
c, err := y.yggdrasilService.PlayerCertificates(ctx, token)
t := ctx.Value(tokenKey).(*model.TokenClaims)
c, err := y.yggdrasilService.PlayerCertificates(ctx, t)
if err != nil {
if errors.Is(err, sutils.ErrTokenInvalid) {
y.logger.DebugContext(ctx, err.Error())
handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "Invalid token.", Error: "ForbiddenOperationException"}, 403)
return
}
y.handleYgError(ctx, w, err)
return
}

View File

@ -3,6 +3,7 @@ package yggdrasil
import (
"context"
"encoding/json"
"errors"
"io"
"log/slog"
"net"
@ -13,6 +14,7 @@ import (
"github.com/samber/lo"
"github.com/xmdhs/authlib-skin/config"
"github.com/xmdhs/authlib-skin/model/yggdrasil"
utilsS "github.com/xmdhs/authlib-skin/service/utils"
yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil"
"github.com/xmdhs/authlib-skin/utils"
)
@ -86,3 +88,33 @@ func (y *Yggdrasil) TextureAssets() http.HandlerFunc {
http.StripPrefix("/texture/", http.FileServer(http.Dir(y.config.TexturePath))).ServeHTTP(w, r)
}
}
type tokenValue string
const tokenKey = tokenValue("token")
func (y *Yggdrasil) Auth(handle http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
a, err := utils.DeCodeBody[yggdrasil.ValidateToken](r.Body, y.validate)
if err != nil {
token := y.getTokenbyAuthorization(ctx, w, r)
if token == "" {
return
}
a.AccessToken = token
}
t, err := y.yggdrasilService.Auth(ctx, a)
if err != nil {
if errors.Is(err, utilsS.ErrTokenInvalid) {
y.logger.DebugContext(ctx, err.Error())
handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "Invalid token.", Error: "ForbiddenOperationException"}, 403)
return
}
y.handleYgError(ctx, w, err)
return
}
r = r.WithContext(context.WithValue(ctx, tokenKey, t))
handle.ServeHTTP(w, r)
})
}

View File

@ -34,23 +34,28 @@ func newYggdrasil(handelY *yggdrasil.Yggdrasil) http.Handler {
r := chi.NewRouter()
r.Use(warpHJSON)
r.Post("/authserver/authenticate", handelY.Authenticate())
r.Group(func(r chi.Router) {
r.Use(handelY.Auth)
r.Post("/authserver/validate", handelY.Validate())
r.Post("/authserver/signout", handelY.Signout())
r.Post("/authserver/invalidate", handelY.Invalidate())
r.Post("/authserver/refresh", handelY.Refresh())
r.Put("/api/user/profile/{uuid}/{textureType}", handelY.PutTexture())
r.Delete("/api/user/profile/{uuid}/{textureType}", handelY.DelTexture())
r.Post("/sessionserver/session/minecraft/join", handelY.SessionJoin())
r.Post("/minecraftservices/player/certificates", handelY.PlayerCertificates())
})
r.Post("/authserver/authenticate", handelY.Authenticate())
r.Post("/authserver/signout", handelY.Signout())
r.Get("/sessionserver/session/minecraft/profile/{uuid}", handelY.GetProfile())
r.Post("/api/profiles/minecraft", handelY.BatchProfile())
r.Post("/sessionserver/session/minecraft/join", handelY.SessionJoin())
r.Get("/sessionserver/session/minecraft/hasJoined", handelY.HasJoined())
r.Post("/minecraftservices/player/certificates", handelY.PlayerCertificates())
r.Get("/", handelY.YggdrasilRoot())
return r
}

View File

@ -17,17 +17,11 @@ type sessionWithIP struct {
IP string
}
func (y *Yggdrasil) SessionJoin(ctx context.Context, s yggdrasil.Session, ip string) error {
t, err := sutils.Auth(ctx, yggdrasil.ValidateToken{
AccessToken: s.AccessToken,
}, y.client, y.cache, &y.prikey.PublicKey, true)
if err != nil {
return fmt.Errorf("SessionJoin: %w", err)
}
func (y *Yggdrasil) SessionJoin(ctx context.Context, s yggdrasil.Session, t *model.TokenClaims, ip string) error {
if s.SelectedProfile != t.Subject {
return fmt.Errorf("SessionJoin: %w", sutils.ErrTokenInvalid)
}
err = cache.CacheHelp[sessionWithIP]{Cache: y.cache}.Put([]byte("session"+s.ServerID), sessionWithIP{
err := cache.CacheHelp[sessionWithIP]{Cache: y.cache}.Put([]byte("session"+s.ServerID), sessionWithIP{
User: *t,
IP: ip,
}, time.Now().Add(30*time.Second))

View File

@ -15,8 +15,7 @@ import (
"github.com/xmdhs/authlib-skin/db/ent/user"
"github.com/xmdhs/authlib-skin/db/ent/userprofile"
"github.com/xmdhs/authlib-skin/db/ent/usertexture"
"github.com/xmdhs/authlib-skin/model/yggdrasil"
utilsService "github.com/xmdhs/authlib-skin/service/utils"
"github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/utils"
)
@ -78,13 +77,9 @@ func (y *Yggdrasil) delTexture(ctx context.Context, userProfileID int, textureTy
return nil
}
func (y *Yggdrasil) DelTexture(ctx context.Context, uuid string, token string, textureType string) error {
t, err := utilsService.Auth(ctx, yggdrasil.ValidateToken{AccessToken: token}, y.client, y.cache, &y.prikey.PublicKey, true)
if err != nil {
return fmt.Errorf("DelTexture: %w", err)
}
func (y *Yggdrasil) DelTexture(ctx context.Context, uuid string, t *model.TokenClaims, textureType string) error {
if uuid != t.Subject {
return fmt.Errorf("PutTexture: %w", errors.Join(ErrUUIDNotEq, utilsService.ErrTokenInvalid))
return fmt.Errorf("PutTexture: %w", ErrUUIDNotEq)
}
up, err := y.client.UserProfile.Query().Where(userprofile.HasUserWith(user.ID(t.UID))).First(ctx)
if err != nil {
@ -101,13 +96,9 @@ func (y *Yggdrasil) DelTexture(ctx context.Context, uuid string, token string, t
return nil
}
func (y *Yggdrasil) PutTexture(ctx context.Context, token string, texturebyte []byte, model string, uuid string, textureType string) error {
t, err := utilsService.Auth(ctx, yggdrasil.ValidateToken{AccessToken: token}, y.client, y.cache, &y.prikey.PublicKey, true)
if err != nil {
return fmt.Errorf("PutTexture: %w", err)
}
func (y *Yggdrasil) PutTexture(ctx context.Context, t *model.TokenClaims, texturebyte []byte, model string, uuid string, textureType string) error {
if uuid != t.Subject {
return fmt.Errorf("PutTexture: %w", errors.Join(ErrUUIDNotEq, utilsService.ErrTokenInvalid))
return fmt.Errorf("PutTexture: %w", ErrUUIDNotEq)
}
up, err := y.client.UserProfile.Query().Where(userprofile.HasUserWith(user.ID(t.UID))).First(ctx)

View File

@ -23,6 +23,7 @@ import (
"github.com/xmdhs/authlib-skin/db/ent/user"
"github.com/xmdhs/authlib-skin/db/ent/userprofile"
"github.com/xmdhs/authlib-skin/db/ent/usertoken"
"github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/model/yggdrasil"
sutils "github.com/xmdhs/authlib-skin/service/utils"
"github.com/xmdhs/authlib-skin/utils"
@ -120,14 +121,6 @@ func (y *Yggdrasil) Authenticate(cxt context.Context, auth yggdrasil.Authenticat
}, nil
}
func (y *Yggdrasil) ValidateToken(ctx context.Context, t yggdrasil.ValidateToken) error {
_, err := sutils.Auth(ctx, t, y.client, y.cache, &y.prikey.PublicKey, true)
if err != nil {
return fmt.Errorf("ValidateToken: %w", err)
}
return nil
}
func (y *Yggdrasil) SignOut(ctx context.Context, t yggdrasil.Pass) error {
u, err := y.validatePass(ctx, t.Username, t.Password)
if err != nil {
@ -152,12 +145,8 @@ func (y *Yggdrasil) SignOut(ctx context.Context, t yggdrasil.Pass) error {
return nil
}
func (y *Yggdrasil) Invalidate(ctx context.Context, accessToken string) error {
t, err := sutils.Auth(ctx, yggdrasil.ValidateToken{AccessToken: accessToken}, y.client, y.cache, &y.prikey.PublicKey, true)
if err != nil {
return fmt.Errorf("Invalidate: %w", err)
}
err = y.client.UserToken.Update().Where(usertoken.HasUserWith(user.ID(t.UID))).AddTokenID(1).Exec(ctx)
func (y *Yggdrasil) Invalidate(ctx context.Context, t *model.TokenClaims) error {
err := y.client.UserToken.Update().Where(usertoken.HasUserWith(user.ID(t.UID))).AddTokenID(1).Exec(ctx)
if err != nil {
return fmt.Errorf("Invalidate: %w", err)
}
@ -168,11 +157,7 @@ func (y *Yggdrasil) Invalidate(ctx context.Context, accessToken string) error {
return nil
}
func (y *Yggdrasil) Refresh(ctx context.Context, token yggdrasil.RefreshToken) (yggdrasil.Token, error) {
t, err := sutils.Auth(ctx, yggdrasil.ValidateToken{AccessToken: token.AccessToken, ClientToken: token.ClientToken}, y.client, y.cache, &y.prikey.PublicKey, false)
if err != nil {
return yggdrasil.Token{}, fmt.Errorf("Refresh: %w", err)
}
func (y *Yggdrasil) Refresh(ctx context.Context, t *model.TokenClaims) (yggdrasil.Token, error) {
jwts, err := newJwtToken(y.prikey, t.Tid, t.CID, t.Subject, t.UID)
if err != nil {
return yggdrasil.Token{}, fmt.Errorf("Refresh: %w", err)
@ -315,11 +300,7 @@ func (y *Yggdrasil) BatchProfile(ctx context.Context, names []string) ([]yggdras
// privateKey 为 PKCS #8 pem type 为 RSA PUBLIC KEY
// 签名使用 rsaWIthsha1
func (y *Yggdrasil) PlayerCertificates(ctx context.Context, token string) (yggdrasil.Certificates, error) {
t, err := sutils.Auth(ctx, yggdrasil.ValidateToken{AccessToken: token}, y.client, y.cache, &y.prikey.PublicKey, false)
if err != nil {
return yggdrasil.Certificates{}, fmt.Errorf("PlayerCertificates: %w", err)
}
func (y *Yggdrasil) PlayerCertificates(ctx context.Context, t *model.TokenClaims) (yggdrasil.Certificates, error) {
rsa2048, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return yggdrasil.Certificates{}, fmt.Errorf("PlayerCertificates: %w", err)

View File

@ -1,6 +1,7 @@
package yggdrasil
import (
"context"
"crypto/rsa"
"encoding/binary"
"fmt"
@ -11,6 +12,8 @@ import (
"github.com/xmdhs/authlib-skin/db/cache"
"github.com/xmdhs/authlib-skin/db/ent"
"github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/model/yggdrasil"
sutils "github.com/xmdhs/authlib-skin/service/utils"
)
type Yggdrasil struct {
@ -82,3 +85,11 @@ func newJwtToken(jwtKey *rsa.PrivateKey, tokenID, clientToken, UUID string, user
}
return jwts, nil
}
func (y *Yggdrasil) Auth(ctx context.Context, t yggdrasil.ValidateToken) (*model.TokenClaims, error) {
u, err := sutils.Auth(ctx, t, y.client, y.cache, &y.prikey.PublicKey, true)
if err != nil {
return nil, fmt.Errorf("ValidateToken: %w", err)
}
return u, nil
}