【问题标题】:Vapor 4/Swift: Middleware developmentVapor 4/Swift:中间件开发
【发布时间】:2023-03-20 07:47:02
【问题描述】:

我对 Swift 和 Vapor 完全陌生;

我正在尝试开发一个授权中间件,该中间件是从我的 UserModel(Fluent Model 对象)调用的,并且在所需的访问级别(int)和路径(字符串)和

  1. 确保用户通过身份验证,
  2. 检查 UserModel.Accesslevel 的实例是否 => 传入的必需访问级别,如果不是,则将用户重定向到“路径”。

我已经尝试了几天,总是很接近,但从来没有完全得到我所追求的。

我有以下内容(请原谅命名不佳的对象):

import Vapor

public protocol Authorizable: Authenticatable {
    associatedtype ProfileAccess: UserAuthorizable
}

/// status cached using  `UserAuthorizable'
public protocol UserAuthorizable: Authorizable {
    /// Session identifier type.
    associatedtype AccessLevel: LosslessStringConvertible

    /// Identifier identifier.
    var accessLevel: AccessLevel { get }
}

extension Authorizable {
    /// Basic middleware to redirect unauthenticated requests to the supplied path
    ///
    /// - parameters:
    ///    - path: The path to redirect to if the request is not authenticated
    public static func authorizeMiddleware(levelrequired: Int, path: String) -> Middleware {
            return AuthorizeUserMiddleware<Self>(Self.self, levelrequired: levelrequired, path: path)
    }
}

// Helper for creating authorization middleware.
///
//private final class AuthRedirectMiddleware<A>: Middleware
//where A: Authenticatable
final class AuthorizeUserMiddleware<A>: Middleware where A: Authorizable {
    let levelrequired: Int
    let path: String
    let authLevel : Int

init(_ authorizableType: A.Type = A.self, levelrequired: Int, path: String) {
    self.levelrequired = levelrequired
    self.path = path
}

/// See Middleware.respond
   public func respond(to req: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
        if req.auth.has(A.self) {
            print("--------")
            print(path)
            print(levelrequired)
**//            print(A.ProfileAccess.AccessLevel) <- list line fails because the A.ProfileAccess.AccessLevel is a type not value**
            print("--------")
        }
        return next.respond(to: req)
    }
}

我将以下内容添加到我的用户模型中

extension UserModel: Authorizable
{
    typealias ProfileAccess = UserModel
}

extension UserModel: UserAuthorizable {
    typealias AccessLevel = Int
    var accessLevel: AccessLevel { self.userprofile! }
}

这样的路线

    // setup the authentication process
    let session = app.routes.grouped([
      UserModelSessionAuthenticator(),
      UserModelCredentialsAuthenticator(),
      UserModel.authorizeMiddleware(levelrequired: 255, path: "/login"), // this will redirect the user when unauthenticted
      UserModel.authRedirectMiddleware(path: "/login"), // this will redirect the user when unauthenticted
    ])

正确传递了路径和所需级别,但我无法从当前用户获取 AccessLevel 的实例。 (我确信我已经戴上了 C++ 帽子,并且从我可以“猜测”的内容来看,即使身份验证已经完成,UserModel 实际上并不是用户的填充实例)

我尝试在使用关联类型传递帐户信息的“SessionAuthenticator”过程中进行合并。

我的另一个想法是检查用户是否经过身份验证,如果是,我可以安全地假设会话 Cookie 包含我的用户 ID,因此我可以(再次)从数据库中提取用户并从那里检查用户访问级别。

我可能会离开这里,几天后我不知道哪种方法是最好的方法,非常感谢任何指导。

干杯

【问题讨论】:

    标签: swift middleware vapor vapor-fluent


    【解决方案1】:

    我使用 Vapor 4 方法使我的 User 模型符合 ModelAuthenticatable

    extension User:ModelAuthenticatable
    {
        static let usernameKey = \User.$email
        static let passwordHashKey = \User.$password
    
        func verify(password: String) throws -> Bool
        {
            try Bcrypt.verify(password, created:self.password)
        }
    }
    

    在我检查了用户是否存在并且密码已如上验证的时候,然后它将用户登录:

    request.auth.login(user)
    

    登录后,我使用自定义Middleware 检查用户是否已登录和“超级用户”,如果是,则将响应传递给链中的下一个Middleware,否则重新- 如果不是,则指向主页/登录页面。

    struct SuperUserMiddleware:Middleware
    {
        func respond(to request:Request, chainingTo next:Responder) -> EventLoopFuture<Response>
        {
            do
            {
                let user = try request.auth.require(User.self)
                if user.superUser { return next.respond(to:request) }
            }
            catch {}
            let redirect = request.redirect(to:"UNPRIVILEGED")
            return request.eventLoop.makeSucceededFuture(redirect)
        }
    }
    

    我注册了SuperUserMiddleware 并与configure.swift 中的某些路由组一起使用:

    app.middleware.use(SessionsMiddleware(session:MemorySessions(storage:MemorySessions.Storage())))
    app.middleware.use(User.sessionAuthenticator(.mysql))
    let superUserMW = SuperUserMiddleware()
    let userAuthSessionsMW = User.authenticator()
    let events = app.grouped("/events").grouped(userAuthSessionsMW, superUserMW)
    try EventRoutes(events)
    

    【讨论】:

    • 我现在不在我的电脑附近,您是否已将超级用户变量/属性添加到用户模型中?我问是因为我有多个级别,不仅是标准用户和超级用户,所以必须调整自定义中间件,但是当我使用时:让用户 = 尝试 request.auth.require(User.self)。返回的使用对象不是我的用户模型,因此我没有自定义属性。
    • 是的,它是一个自定义属性。只要您使您的 User 模型符合 ModelAuthenticatable ,您就可以添加您喜欢的内容。我有大约 15-20 个额外的变量。
    • 太好了,谢谢,我会试一试,看看我能做什么。
    • 好的,所以我只需要一行:let user = try request.auth.require(User.self) 并且我能够访问我定义的属性。 (虽然我认为我以前使用过 ti,但我没有,或者至少不正确)
    猜你喜欢
    • 2016-12-13
    • 1970-01-01
    • 2018-05-14
    • 1970-01-01
    • 2022-01-12
    • 2017-07-28
    • 1970-01-01
    • 1970-01-01
    • 2018-03-16
    相关资源
    最近更新 更多