目录
背景
方案设计
技术点
代码实现
1.切面
2.配置类
3.切面
测试
总结
背景
上线初期提供可配置的白名单用户进行访问验证,把控整体运行风险。
解决痛点:
APOLLO企业控制也可以,多个业务功能,要配置多个apollo,如ASwitch,BSwitch,这样会非常耗时耗力,而且等功能稳定,要放开企业/用户限制时,只能将APOLLO的值置为空串或者某个特殊值(已表示对全部企业/用户放开),日后会残留在代码里。
方案设计
白名单属于业务系统开发过程中可重复使用的通用功能,所以我们把这样的工具型功能提炼为技术组件,各个需要的使用此功能的系统引入此组件。
技术点
自定义注解、切面和springboot读取配置文件的处理
代码实现
1.切面
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoWhiteList {
/**
* key:当前接口入参需要提取的属性
* @return
*/
String key() default "";
/**
* returnJson: 拦截用户请求后返回的提示msg
* @return
*/
String returnJson() default "";
}
扩展
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
字如其名,目标,即自定义注解DoWhiteList是要作用在类、接口还是方法上
2.配置类
/**
* @ConfigurationProperties:用于创建指定前缀的自定义配置信息,这样就在ymL或者properties中读取到配置信息
*/
@ConfigurationProperties("com.whitelist")
public class WhiteListProperties {
private String users;
public String getUsers() {
return users;
}
public void setUsers(String users) {
this.users = users;
}
}
/**
*
* @ConditionalOnMissingBean
* 只会实例化一个Bean对象
*/
@Configuration
@EnableConfigurationProperties(WhiteListProperties.class)
public class WhiteListAutoConfigure {
@Bean("whiteListConfig")
@ConditionalOnMissingBean
public String whiteListConfig(WhiteListProperties properties) {
return properties.getUsers();
}
}
3.切面
@Aspect
@Component
public class DoJoinPoint {
private Logger logger = LoggerFactory.getLogger(DoJoinPoint.class);
@Resource
private String whiteListConfig;
@Pointcut("@annotation(cn.bugstack.middleware.whitelist.annotation.DoWhiteList)")
public void aopPoint() {
}
@Around("aopPoint()")
public Object doRouter(ProceedingJoinPoint jp) throws Throwable {
// 获取内容
Method method = getMethod(jp);
DoWhiteList whiteList = method.getAnnotation(DoWhiteList.class);
// 获取字段值
String keyValue = getFiledValue(whiteList.key(), jp.getArgs());
logger.info("middleware whitelist handler method:{} value:{}", method.getName(), keyValue);
if (null == keyValue || "".equals(keyValue)) return jp.proceed();
String[] split = whiteListConfig.split(",");
// 白名单过滤
for (String str : split) {
if (keyValue.equals(str)) {
return jp.proceed();
}
}
// 拦截
return returnObject(whiteList, method);
}
private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
Signature sig = jp.getSignature();
MethodSignature methodSignature = (MethodSignature) sig;
return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
}
// 返回对象
private Object returnObject(DoWhiteList whiteList, Method method) throws IllegalAccessException, InstantiationException {
Class<?> returnType = method.getReturnType();
String returnJson = whiteList.returnJson();
if ("".equals(returnJson)) {
return returnType.newInstance();
}
return JSON.parseObject(returnJson, returnType);
}
// 获取属性值
private String getFiledValue(String filed, Object[] args) {
String filedValue = null;
for (Object arg : args) {
try {
if (null == filedValue || "".equals(filedValue)) {
filedValue = BeanUtils.getProperty(arg, filed);
} else {
break;
}
} catch (Exception e) {
if (args.length == 1) {
return args[0].toString();
}
}
}
return filedValue;
}
}
测试
白名单中只允许userId为123的访问
测试场景一
userId为123:
切面拦截判断该userid在白名单配置中,放行
测试场景二
userId为【非123】:
切面拦截判断该userid不在白名单配置中,拦截
总结
日后每上线一个新功能/接口,用该注解可以实现批量梯度控制访问来把控整体运行风险,等功能稳定后,将该注解干掉即可。