最近剛入職新公司。維護一個老的系統。然後我把權限邏輯基於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;
}
}
完事,這是一種比較通用模版,完整的解決方案。權限校驗邏輯可以按照自己系統的規則進行自定義實現。