Play/Scala:如何用orElse组合ActionBuilder实现同接口分角色走不同逻辑
可行实现方案
有两种成熟的实现方式可以满足需求,不需要依赖Play原生没有的orElse Action组合方法:
方案1:单认证Action内做角色分支判断
这是最简洁、性能最优的方案,只走一次认证流程,直接在业务层根据拿到的用户角色做逻辑分发:
// 定义通用JWT认证Action,不提前限制角色范围 val JWTAuthAction: ActionBuilder[Request, AnyContent] = Secure(JWT_CLIENT)(anyContentLarge) def index = JWTAuthAction.async { req => // 从请求属性中取出play-pac4j注入的认证用户信息 val userProfile = req.attrs(Pac4jWebKeys.PROFILE).asInstanceOf[CommonProfile] userProfile.getRoles match { case roles if roles.contains(Authorizers.Role1) => index1(req) case roles if roles.contains(Authorizers.Role2) => index2(req) case _ => Future.successful(Forbidden("当前用户无访问权限")) } } def index1(req: Request[AnyContent]) = // 角色1对应的处理逻辑 def index2(req: Request[AnyContent]) = // 角色2对应的处理逻辑
方案2:封装Action的orElse组合工具
如果需要保留独立的角色Action定义(比如逻辑复用时不想耦合判断代码),可以自己封装通用的组合方法,默认将前一个Action返回403作为角色不匹配、尝试下一个Action的触发条件:
/** * 多Action备选组合工具,按顺序执行传入的Action,只要返回非403就直接返回结果 * 所有Action都返回403则最终返回403 */ def combineActionOrElse(actions: Action[AnyContent]*): Action[AnyContent] = Action.async { req => actions.foldLeft(Future.successful(Option.empty[Result])) { (resultAcc, currentAction) => resultAcc.flatMap { case hitResult @ Some(_) => Future.successful(hitResult) case None => currentAction(req).map { result => if (result.header.status == 403) None else Some(result) } } }.map(_.getOrElse(Forbidden("无匹配的访问权限"))) } // 原有按角色定义的Secure Action无需修改 val ACTION_ONE: ActionBuilder[Request, AnyContent] = Secure(JWT_CLIENT, Authorizers.Role1)(anyContentLarge) val ACTION_TWO: ActionBuilder[Request, AnyContent] = Secure(JWT_CLIENT, Authorizers.Role2)(anyContentLarge) // 直接组合使用即可 def index = combineActionOrElse( ACTION_ONE.async(req => index1(req)), ACTION_TWO.async(req => index2(req)) ) def index1(req: Request[AnyContent]) = // 角色1对应的处理逻辑 def index2(req: Request[AnyContent]) = // 角色2对应的处理逻辑
注意:如果自定义了play-pac4j认证失败的返回码,需要把工具里的403判断改成你自定义的状态码。
内容的提问来源于stack exchange,提问作者JR Utily
相关产品推荐
相关产品推荐

