自定義註解實現權限校驗實戰

最近剛入職新公司。維護一個老的系統。然後我把權限邏輯基於AOP給簡單優化了一下。之後使用註解就可以了。作爲一個自定義註解+AOP的實戰記錄吧。

1、提供了一個註解:@CheckPermission

/**
 * @author zhangjiahui01
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface CheckPermission {
 
    /**
     * 權限校驗類型:PermissionTypeEnum
     *
     * @return
     */
    PermissionTypeEnum type() default PermissionTypeEnum.ONLY_CHECK_PATH;
 
    /**
     * 校驗如果需要參數列表裏的額外參數,使用該字段指定參數名字
     *
     * 1.如果直接是參數列表的參數:參數名字
     * 2.如果需要的值是對象的屬性:參數名.屬性名
     *
     * 注意:目前只支持兩層嵌套的數據結構參數
     * eg:
     *
     * @CheckPermission(param ="param")
     * public void getTempCardList(String param, HttpServletRequest request, HttpServletResponse response){
     *     ......
     * }
     *
     * @CheckPermission(param ="eduExperienceAddRequest.number")
     * public void getTempCardList(EduExperienceAddRequest eduExperienceAddRequest, HttpServletRequest request, HttpServletResponse response){
     *     ......
     * }
     *
     * @return
     */
    String param() default "";

2、PermissionTypeEnum 權限類型枚舉

 
/**
 * @author zhangjiahui01
 */
public enum PermissionTypeEnum {
 
    EMPLOY_INFO_ACTION("Employ Info Action", ""),
 
    EMPLOY_PAY_ACTION("Employ Pay Action", ""),
 
    ONLY_CHECK_PATH("Only Check Path", "是否有權限訪問URL");
 
    private String code;
 
    private String desc;
 
    PermissionTypeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}

3、AOP實現解析註解的Handler

/**
 * @author zhangjiahui01
 */
@Aspect
@Component
public class PermissionAspectHandler {
 
    private static final Logger LOG  = LoggerFactory.getLogger(PermissionAspectHandler.class);
 
    private static final String SIGN = "\\.";
 
    private static final String PARAM_SIGN = ",";
 
    @Autowired
    private PermissionService permissionService;
 
    @Before("@annotation(com.bjhl.erp.annotation.CheckPermission)")
    public void doBefore(JoinPoint joinPoint) throws Exception{
 
        // get Login UserName
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String loginUserName = RequestUtil.getUserFromSession(request);
 
        // get target method
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
 
        // get Uri from @RequestMapping.value
        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
        String[] requestMappingUri = requestMapping.value();
 
        // parse @CheckPermission annotation
        CheckPermission checkPermissionAnnotation = method.getAnnotation(CheckPermission.class);
        PermissionTypeEnum permissionTypeEnum = checkPermissionAnnotation.type();
 
        List<Long> variableValues = new ArrayList<>();
        String param = checkPermissionAnnotation.param();
        if(!StringUtils.isEmpty(param)){
            String[] paramSplit = param.split(SIGN);
            Map<String, Object> mappingParamters = mappingParamter(joinPoint);
            if(mappingParamters != null && mappingParamters.containsKey(paramSplit[0])) {
                Object targetParamInstance = mappingParamters.get(paramSplit[0]);
                // the parameter values is field of object
                if (paramSplit.length > 1) {
                    Field variableValueField = targetParamInstance.getClass().getDeclaredField(paramSplit[1]);
                    variableValueField.setAccessible(true);
                    targetParamInstance = variableValueField.get(paramSplit[1]);
                }
                if(targetParamInstance instanceof Number){
                    variableValues.add((Long)targetParamInstance);
                }
                if(targetParamInstance instanceof String){
                    String variableListStr = (String)targetParamInstance;
                    if(variableListStr.contains(PARAM_SIGN)){
                        variableValues = Arrays.stream(variableListStr.split(PARAM_SIGN)).map(e->Long.valueOf(e)).collect(Collectors.toList());
                    }
                }
            }
        }
        if(!checkPermission(permissionTypeEnum,loginUserName,requestMappingUri[0],variableValues)){
            LOG.info("checkPermission result:no permissio userName:{} uri:{}",loginUserName,requestMappingUri[0]);
            throw new NoPermissionException("權限不足");
        }
    }
 
    /**
     * check Permission main function
     *
     * @param type
     * @param userName
     * @param requestMappingUri
     * @param params
     * @return
     */
    private Boolean checkPermission(PermissionTypeEnum type, String userName, String requestMappingUri, List<Long> params){
        LOG.info("checkPermission main function param.permissionTypeEnum:{} UserName:{},Uri:{},params:{}",
                type,userName,requestMappingUri, JSON.toJSONString(params));
        // check paramter
        if(null == type || StringUtils.isEmpty(userName) || StringUtils.isEmpty(requestMappingUri)){
            LOG.error("checkPermission param error,permissionType or userName or requestMapping uri is null");
            return false;
        }
        switch (type){
            case EMPLOY_INFO_ACTION:
                if(!CollectionUtils.isEmpty(params)){
                    return permissionService.hasOptAuthority(userName, params.get(0), requestMappingUri);
                }else{
                    return permissionService.hasOptAuthority(userName,null, requestMappingUri);
                }
            case EMPLOY_PAY_ACTION:
                return permissionService.hasStructAuthority(userName,params,requestMappingUri);
            case ONLY_CHECK_PATH:
                return permissionService.onlyCheckPath(userName,requestMappingUri);
        }
        return false;
    }
 
    /**
     * mapping method paramter list
     *
     * @param joinPoint
     * @return fieldName : value
     */
    public Map<String, Object> mappingParamter(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        if(args.length != 0 && parameterNames.length != 0 && args.length == parameterNames.length){
            for (int i = 0; i < args.length-1; i++) {
                Map<String,Object> argMapping = new HashMap<>();
                argMapping.put(parameterNames[i],args[i]);
                LOG.info("request handler mappingParamter result:{}",JSON.toJSONString(argMapping));
                return argMapping;
            }
        }
        return null;
    }
 
}

4、Controller的統一異常處理:

@RestControllerAdvice
public class ExceptionHandler {
 
    @ExceptionHandler({ NoPermissionException.class })
    @ResponseStatus(HttpStatus.OK)
    public WebResponse doNoPermissionException(Exception e) {
        log.error(e.getMessage(), e);
        WebResponse<String> webResponse = new WebResponse();
        webResponse.setStatus(WebResponseStatus.WEB_STATUS_NO_PERMISSION);
        webResponse.setError(new ErrorCode(1, e.getMessage()));
        return webResponse;
    }
}

完事,這是一種比較通用模版,完整的解決方案。權限校驗邏輯可以按照自己系統的規則進行自定義實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章