<ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>


      前言

      在實際系統(tǒng)應(yīng)用中,普遍存在這樣的一種業(yè)務(wù)場景,需要實現(xiàn)用戶對要訪問的資源進行動態(tài)權(quán)限校驗。
      譬如,在某平臺的商家系統(tǒng)中,存在商家、品牌、商品等業(yè)務(wù)資源。它們之間的關(guān)系為:一個商家可以擁有多個品牌,一個品牌下可以擁有多個商品。



      一個商家用戶可以擁有多個賬戶,每個賬戶擁有不同級別的權(quán)限。
      例如,小王負責商家A下的所有資源的運營工作,小張負責品牌A和品牌A下所有商品的運營工作。而小李負責品牌B




      Shiro本身提供了RequiresAuthentication、RequiresPermissions和RequiresRoles等注解用于實現(xiàn)靜態(tài)權(quán)限認證,
      但不適合對于這種細粒度的動態(tài)資源的權(quán)限認證校驗。基于以上描述,這篇文章就是補充了一種對細粒度動態(tài)資源的訪問權(quán)限校驗。

      大概的設(shè)計思路

      * 1.新增一個自定義注解Permitable,用于將資源轉(zhuǎn)換為shiro的權(quán)限表示字符串(支持SpEL表達式)
      * 2.新增加一個AOP切面,用于將自定義注解標注的方法和Shiro權(quán)限校驗關(guān)聯(lián)起來
      * 3.校驗當前用戶是否擁有足夠的權(quán)限去訪問受保護的資源
      編碼實現(xiàn)

      * 1、新建PermissionResolver接口 import java.util.Collections; import
      java.util.List; import java.util.Optional; import static
      java.util.stream.Collectors.toList; /** * 資源權(quán)限解析器 * * @author wuyue * @since
      1.0, 2019-09-07 */ public interface PermissionResolver { /** * 解析資源 * * @return
      資源的權(quán)限表示字符串 */ String resolve(); /** * 批量解析資源 */ static List<String>
      resolve(List<PermissionResolver> list) { return
      Optional.ofNullable(list).map(obj ->
      obj.stream().map(PermissionResolver::resolve).collect(toList()))
      .orElse(Collections.emptyList()); } }
      * 2、新增業(yè)務(wù)資源實體類,并實現(xiàn)PermissionResolver接口,此處以商品資源為例,例如新建Product.java import
      com.wuyue.shiro.shiro.PermissionResolver; import lombok.Getter; import
      lombok.Setter; import lombok.ToString; import
      org.hibernate.annotations.GenericGenerator; import javax.persistence.*; import
      java.util.Date; @Getter @Setter @ToString @Entity @Table(name = "product")
      public class Product implements PermissionResolver { @Override public String
      resolve() { return merchantId + ":" + brandId + ":" + id; } @Id
      @GenericGenerator(name = "idGen", strategy = "uuid") @GeneratedValue(generator
      = "idGen") private String id; @Column(name = "merchant_id") private String
      merchantId; @Column(name = "brand_id") private String brandId; @Column(name =
      "name") private String name; @Column(name = "create_time") private Date
      createTime; @Column(name = "update_time") private Date updateTime; }
      * 3、新增自定義注解Permitable import java.lang.annotation.*; /** *
      自定義細粒度權(quán)限校驗注解,配合SpEL表達式使用 */ @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Permitable {
      /** * 前置校驗資源權(quán)限表達式 * * @return 資源的權(quán)限字符串表示(如“字節(jié)跳動”下的“抖音”可以表達為BYTE_DANCE:TIK_TOK)
      */ String pre() default ""; /** * 后置校驗資源權(quán)限表達式 * * @return */ String post()
      default ""; }
      * 4、新增權(quán)限校驗切面 import lombok.extern.slf4j.Slf4j; import
      org.aopalliance.intercept.MethodInterceptor; import
      org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils;
      import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
      import org.springframework.context.expression.MethodBasedEvaluationContext;
      import org.springframework.core.DefaultParameterNameDiscoverer; import
      org.springframework.core.annotation.AnnotationUtils; import
      org.springframework.expression.EvaluationContext; import
      org.springframework.expression.spel.standard.SpelExpressionParser; import
      java.lang.annotation.Annotation; import java.lang.reflect.Method; import
      java.util.List; /** * 靜態(tài)自定義權(quán)限認證切面 */ @Slf4j public class PermitAdvisor extends
      StaticMethodMatcherPointcutAdvisor { private static final Class<? extends
      Annotation>[] AUTHZ_ANNOTATION_CLASSES = new Class[] { Permitable.class };
      public PermitAdvisor(SpelExpressionParser parser) { //
      構(gòu)造一個通知,當方法上有加入Permitable注解時,會觸發(fā)此通知執(zhí)行權(quán)限校驗 MethodInterceptor advice = mi -> {
      Method method = mi.getMethod(); Object targetObject = mi.getThis(); Object[]
      args = mi.getArguments(); Permitable permitable =
      method.getAnnotation(Permitable.class); // 前置權(quán)限認證 checkPermission(parser,
      permitable.pre(), method, args, targetObject, null); Object proceed =
      mi.proceed(); // 后置權(quán)限認證 checkPermission(parser, permitable.post(), method,
      args, targetObject, proceed); return proceed; }; setAdvice(advice); } /** *
      匹配加了Permitable注解的方法,用于通知權(quán)限校驗 */ @Override public boolean matches(Method method,
      Class<?> targetClass) { Method m = method; if (isAuthzAnnotationPresent(m)) {
      return true; } return false; } private boolean isAuthzAnnotationPresent(Method
      method) { for (Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES)
      { Annotation a = AnnotationUtils.findAnnotation(method, annClass); if ( a !=
      null ) { return true; } } return false; } /** * 動態(tài)權(quán)限認證 */ private void
      checkPermission(SpelExpressionParser parser, String expr, Method method,
      Object[] args, Object target, Object result){ if (StringUtils.isBlank(expr)){
      return; } // 解析SpEL表達式,獲得資源的權(quán)限表示字符串 Object resources =
      parser.parseExpression(expr) .getValue(createEvaluationContext(method, args,
      target, result), Object.class); // 調(diào)用Shiro進行權(quán)限校驗 if (resources instanceof
      String) { SecurityUtils.getSubject().checkPermission((String) resources); }
      else if (resources instanceof List){ List<Object> list = (List) resources;
      list.stream().map(obj -> (String)
      obj).forEach(SecurityUtils.getSubject()::checkPermission); } } /** *
      構(gòu)造SpEL表達式上下文 */ private EvaluationContext createEvaluationContext(Method
      method, Object[] args, Object target, Object result) {
      MethodBasedEvaluationContext evaluationContext = new
      MethodBasedEvaluationContext( target, method, args, new
      DefaultParameterNameDiscoverer()); evaluationContext.setVariable("result",
      result); try { evaluationContext.registerFunction("resolve",
      PermissionResolver.class.getMethod("resolve", List.class)); } catch
      (NoSuchMethodException e) { log.error("Get method error:", e); } return
      evaluationContext; } }
      * 5、實現(xiàn)對用戶的授權(quán) /** * 授權(quán) */ @Override protected AuthorizationInfo
      doGetAuthorizationInfo(PrincipalCollection principals) { Map<String, Object>
      principal = (Map<String, Object>) principals.getPrimaryPrincipal(); String
      accountId = (String) principal.get("accountId"); // 擁有的商家資源權(quán)限
      List<AccountMerchantLink> merchantLinks =
      accountService.findMerchantLinks(accountId); Set<String> merchantPermissions =
      merchantLinks.stream().map(AccountMerchantLink::getMerchantId).collect(toSet());
      SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
      authzInfo.addStringPermissions(merchantPermissions); // 擁有的品牌資源權(quán)限
      List<AccountBrandLink> brandLinks = accountService.findBrandLinks(accountId);
      Set<String> brandPermissions = brandLinks.stream().map(link ->
      link.getMerchantId() + ":" + link.getBrandId()).collect(toSet());
      authzInfo.addStringPermissions(brandPermissions); return authzInfo; }
      *
      6、自定義注解的應(yīng)用

      6.1、根據(jù)id獲取商家信息
      @Permitable(pre = "#id") @Override public Optional<Merchant> findById(String
      id) { if (StringUtils.isBlank(id)) { return Optional.empty(); } return
      merchantDao.findById(id); }
      6.2、根據(jù)id獲取商品信息
      @Permitable(post = "#result?.get().resolve()") @Override public
      Optional<Product> findById(String id) { if (StringUtils.isBlank(id)) { return
      Optional.empty(); } return productDao.findById(id); }
      6.3、查找品牌下的商品列表
      @Permitable(post = "#resolve(#result)") @Override public List<Product>
      findByBrandId(String brandId) { if (StringUtils.isBlank(brandId)) { return
      Collections.emptyList(); } return productDao.findByBrandId(brandId); }
      *
      7、測試

      7.1、按照上面描述的業(yè)務(wù)場景,準備3個用戶數(shù)據(jù)



      7.2、使用小王登錄后測試



      7.2.1、獲取商家信息(擁有權(quán)限)



      7.2.2、獲取商品信息(擁有權(quán)限)



      7.3、使用小李登錄后測試



      7.3.1、獲取商家信息(權(quán)限不足)



      7.3.2、獲取商品信息(權(quán)限不足)



      7.3.3、獲取商品信息(擁有權(quán)限)



      7.4、小結(jié)

      從上面的接口測試截圖中可以看出,此方案符合我們設(shè)計之初要實現(xiàn)的業(yè)務(wù)場景。

      完整源碼 <https://github.com/Felix0525/shiro-spring>

      友情鏈接
      ioDraw流程圖
      API參考文檔
      OK工具箱
      云服務(wù)器優(yōu)惠
      阿里云優(yōu)惠券
      騰訊云優(yōu)惠券
      京東云優(yōu)惠券
      站點信息
      問題反饋
      郵箱:[email protected]
      QQ群:637538335
      關(guān)注微信

        <ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>
          男人插女人的动态图 | 超碰天天搞 | 午夜操逼网站 | 国产三级精品一区二区三区视频 | 人人草人人摸人人干 | 孕妇大肚婆孕交毛片 | 97乱伦视频 | 淫片免费观看视频直播 | 免费看男阳茎进女阳道播放 | 亚洲欧美性爱视频 |