网站建设的公司上海,上海纯设计公司,用jsp做肯德基的网站,辽宁建设工程信息网登录入口文章目录 一、逻辑分析二、校验规则1.规则类型2.规则划分3.规则配置信息4.规则案例说明5.规则加载 三、拦截器定义1.自定义拦截器2.注册拦截器 四、获取请求参数1.获取get提交方式参数2.获取post提交方式参数#xff08;1#xff09;定义RequestWrapper类#xff08;2#… 文章目录 一、逻辑分析二、校验规则1.规则类型2.规则划分3.规则配置信息4.规则案例说明5.规则加载 三、拦截器定义1.自定义拦截器2.注册拦截器 四、获取请求参数1.获取get提交方式参数2.获取post提交方式参数1定义RequestWrapper类2定义过滤器3注册过滤器4获取post提交方式参数 3.上传文件参数处理4.获取动态接口参数5.获取系统固定参数 五、拦截请求1.获取校验规则2.固定接口地址匹配3.动态接口地址匹配 六、执行校验1.执行校验入口2.反射执行校验3.执行sql校验数量4.校验服务截止时间5.禁用接口校验6.允许操作类型校验 背景 传统的管理系统一般是这样进行权限设置的用户与角色绑定角色与菜单绑定这样某个用户可以访问哪些菜单就已经定下来了为了防止绕过权限去调用没有分配菜单对应的接口java项目可以结合着spring security权限框架使用注解方式对具体的接口配置权限码访问接口的用户绑定的角色下有此权限码才能访问接口这是基于接口维度进行权限控制。 像有些对权限细粒度划分的场景传统的权限控制就满足不了例如下面这样的场景 场景一对同一个接口的操作若接口处理的资源是A用户在A下是管理员权限可以正常访问此接口若接口处理的资源是B用户在B下是查看者权限此时就需要拦截请求这样的需求就不能单纯的从接口是否能访问来限制。 场景二用户购买服务花费不同的价格购买不同的套餐每种套餐有不同的限制初级版限制可以新建的数量为10中级版为20高级版不限制这样的需求可以在具体的接口上做判断先获取用户购买的服务等级然后查询已有的数量大于阈值则进行拦截。这样的方式对代码侵入性太强后期有调整数量或者再增加版本划分都是不好扩展的。 为了满足权限细粒度的划分、减轻业务代码的侵入性、易于扩展可以使用拦截器进行权限校验权限规则使用配置的方式添加。
一、逻辑分析 定义好权限校验规则key为请求的接口名value为校验的规则集合当请求进来时拦截器拦截请求获取接口名判断规则中是否配置了此接口的校验若是配置了校验则获取请求参数作为校验规则需要的参数执行校验校验通过才放行。流程图
二、校验规则 权限校验规则需要做成配置的方式允许动态增减可以使用配置文件或者数据库存储在程序启动时加载到内存中供拦截校验使用。校验规则的key使用接口名value为规则的集合加载到内存中使用map的方式存放这样拦截器拦截到一个接口时判断这个接口是否有配置校验规则可以使用map.containsKey()在时间复杂度为O(1)的情况下完成。 这里使用Json文件的方式存储校验规则校验规则有不同的类型例如校验资源数量、校验是否有权访问、校验是否已过期等。我们可以使用java的多态来接收不同的规则定义不同的实体类来接收配置信息每种实体类约定好怎么去处理校验。当对某个接口进行校验时遍历它配置的规则集合根据规则的实体类是哪种类型来调用对应的校验方法。
1.规则类型 校验类型需要根据具体业务来定我们来定义下面几种类型后面也是基于这些类型来实现类型如下1一个用户关联着多个空间在不同的空间下有不同的权限分为管理员、编辑者、查看者管理员可以进行删除操作编辑者可以修改数据查看者只能查看数据。当操作空间下的资源时需要判断用户在此空间下是哪种权限符合权限要求才能操作资源一个空间下包含多个图表当用户操作某个图表时需要判断此图表属于哪个空间用户在此空间下是哪种权限这样就涉及到联查的操作出于性能考虑需要使用缓存redis记录用户在某个空间下的权限图表属于哪个空间这样的信息类型记为workspace。
2用户购买不同的服务版本可以享受不同的服务例如初级版只能创建10个图表中级版可以创建20个这就需要对数量进行限制类型记为num。
3用户购买的服务到截止时间以后不能再访问某些接口需要做限制类型记为deadline。
4用户购买了初级版需要对中级版才能访问的业务接口进行限制类型记为disabled。
5用户购买了初级版需要对操作的业务数据类型进行限制总的业务类型包含5种初级版只能操作里面的2种类型记为disabledtype。 设置多少种类型需要根据具体的需求来定。
2.规则划分 规则类型定义好后基于需求有些规则是用户购买任何版本都需要做校验有些规则是初级版校验有些规则是中极版校验例如数量这样的校验初级和中级分别对应不同的值。这里按公共校验记为publicConfig、初级版校验记为noviceConfig、中级版校验记为intermeConfig划分若还有其它版本再建对应的划分。每种划分使用list集合存放规则这样在拦截到请求时先获取用户开通的是哪种版本然后遍历公共校验、开通版本对应的校验集合进行校验。 划分为多少种大类需要根据具体的需求来定。
3.规则配置信息 每种规则类型都约定好按怎么的逻辑去执行执行规则校验需要相应的参数和配置信息每种类型创建对应的实体类接收配置信息。基于上面定义的5种规则类型进行配置说明
1workspace需要校验用户在操作资源所属的空间下是哪种权限有什么样的权限码才可以操作此资源并且这些所属关系需要使用缓存redis存放所以这里使用反射的方式执行校验具体要执行的方法写在业务service层中拦截器根据配置信息获取到service从request请求中获取到参数值带着参数值使用反射invoke执行它的方法方法返回的结果值与配置的权限码进行比较符合了才放行。看下workspace实体类
EqualsAndHashCode(callSuper true)
Data
NoArgsConstructor
FieldDefaults(level AccessLevel.PRIVATE)
public class WorkspaceAuthority extends AuthorityConfigOne {Integer code; //空间权限码String beanName; //bean名称配置调用service层的名称开头小写String methodName; //执行的方法名称ArrayList methodParamType; //执行的方法参数类型Integerjava.lang.Integer,Stringjava.lang.StringArrayList methodParamKey; //执行方法需要的参数名称用户id默认userId其他参数根据方法需要的参数来配置
}2num需要校验用户操作资源的数量使用sql查询的方式进行校验配置一个允许的最大数量配置sql需要参数值的key参数值从request请求中获取使用jdbcTemplate执行sql结果值与配置的阈值比较小于阈值才放行。看下num实体类
EqualsAndHashCode(callSuper true)
Data
NoArgsConstructor
FieldDefaults(level AccessLevel.PRIVATE)
public class NumAuthority extends AuthorityConfigOne {String querySql; //查询数量的sqlArrayList paramKey; //参数值集合Integer upLimit; //最大阈值
}3deadline访问接口时需要获取用户开通服务的时间是否已到期到期的话直接拦截请求。看下deadline实体类
EqualsAndHashCode(callSuper true)
Data
NoArgsConstructor
FieldDefaults(level AccessLevel.PRIVATE)
public class DeadlineAuthority extends AuthorityConfigOne {
}4disabled访问接口时需要判断是否有权访问此接口购买了初级版的服务访问中级版才有权访问的接口时需要拦截。看下disabled实体类
EqualsAndHashCode(callSuper true)
Data
NoArgsConstructor
FieldDefaults(level AccessLevel.PRIVATE)
public class DisabledAuthority extends AuthorityConfigOne {
}5disabledtype校验接口可以访问的类型从request请求中获取需要校验参数的值判断这个值是否在允许的集合里面在集合里面才放行这里需要配置通过key获取到的value值的具体类型因为判断list是否包含某个值需要是同类型的值。看下disabledtype实体类
EqualsAndHashCode(callSuper true)
Data
NoArgsConstructor
FieldDefaults(level AccessLevel.PRIVATE)
public class DisabledTypeAuthority extends AuthorityConfigOne {String checkKey; //需要校验的keyString keyValueType; //key值的类型需要设置得与allowValues的类型一致ArrayList allowValues; //允许配置的值checkKey获取到的参数值需要在allowValues集合中才能放行
}4.规则案例说明 创建一个名称为AuthorityConfig.json的配置文件放到resources配置目录下。规则案例
[{key: /data/addData,config:{publicConfig: [{type:workspace,code:4,beanName:xxxDataService,methodName:getPrivilegeByIdFromRedisOrDatabase,methodParamType:[java.lang.Integer,java.lang.String],methodParamKey:[id,userId]}],noviceConfig: [{type:num,querySql:select count(1) from table_name where xxx_id?,paramKey:[xxxId],upLimit:3}],intermeConfig: [{type:num,querySql:select count(1) from table_name where xxx_id?,paramKey:[xxxId],upLimit:5}]}},{key: /data/info,config:{publicConfig: [{type:deadline},{type:workspace,code:4,beanName:yyyDataService,methodName:getPrivilegeByYyyIdFromRedisOrDatabase,methodParamType:[java.lang.Integer,java.lang.String],methodParamKey:[yyyId,userId]}],noviceConfig: [{type:disabled}],intermeConfig: [{type:disabledtype,checkKey:yyyId,keyValueType:java.lang.Integer,allowValues:[1,2,4,5]}]}}
]规则放到json文件中使用数组的方式存储每个条目对应一个接口校验。配置的参数说明
1key需要进行校验的接口后缀
2config校验的规则信息
3publicConfig公共校验规则只要访问对应接口必须判断里面的校验数组格式可以配置多个校验类型
4noviceConfig初级版校验规则当用户购买的服务为初级版时必须判断里面的校验数组格式可以配置多个校验类型
5intermeConfig中级版校验规则当用户购买的服务为中级版时必须判断里面的校验数组格式可以配置多个校验类型
6type指明规则是哪种类型后面把规则信息反序列化时转成哪种实体类也是用这个字段标识
7其他参数其他参数根据规则类型来定某种规则类型需要哪些参数使用对应key来指定当执行校验时需要根据配置参数取到对应的值。
对上面配置案例的解释 对/data/addData、/data/info两个接口进行权限校验配置有公共规则、初级版规则、中级版规则配置。/data/addData接口访问时需要校验它的权限码是否大于等于4具体的校验方法写在业务service层此处使用反射的方式去调用对应方法执行反射需要用到方法所在的bean对象、方法名、方法参数类型、方法传递的参数值参数值需要从request请求中获取所以这里配置上取值的key初级版配置了校验数量最大值为3当请求这个接口的用户是初级版时执行查询数量的sqlsql需要的参数值从request中获取中极版配置了校验数量最大值为5。/data/info接口访问时需要校验用户购买的服务是否已到期、空间下的权限码初级版是不允许访问此接口中级版时请求的id值要在[1,2,4,5]中才放行。
小提示tip 当项目打包时若是在pom.xml中指定了导出resource的文件项需要把json文件也配置上否则导出的jar包里不包含json文件。配置导出文件的方式 buildresourcesresource!-- 指定配置文件所在的resource目录 --directorysrc/main/resources/directory!-- 指定导出时包含的文件 --includesincludeapplication.yml/includeincludeapplication-${environment}.yml/includeincludelogback-xxx.xml/includeincludeAuthorityConfig.json/include/includesfilteringtrue/filtering/resource/resources/build5.规则加载 在程序启动时读取规则配置文件使用实体类接收。因为校验的类型type是不确定的可以随意扩展我们具体使用哪个实体类来接收需要根据type来决定不同类型的type体现了java的多态性。这里使用jackson的JsonTypeInfo实现不同type使用不同的实体类接收。
1为了方便type的扩展和维护我们定义一个枚举type类。type枚举类
ToString
AllArgsConstructor
public enum AuthorityType{Workspace(workspace),Num(num),Deadline(deadline),Disabled(disabled),DisabledType(disabledtype);JsonValueGetterprivate final String value;//提供一个根据value值来获取枚举值的方法public static AuthorityType valueOfNew(Object value) {if (value ! null) {for (AuthorityType item:AuthorityType.values()) {if (item.value.equals(value)) {return item;}}}return null;}
}2定义与json文件对应的实体类接收规则信息最外层包含key、config字段定义AuthorityConfigAll类
Data
FieldDefaults(level AccessLevel.PRIVATE)
public class AuthorityConfigAll {String key;AuthorityConfigType config;
}3config里面包含着公共、初级、中级的权限划分定义AuthorityConfigType类
Data
FieldDefaults(level AccessLevel.PRIVATE)
public class AuthorityConfigType {ListAuthorityConfigOne publicConfig; //公共的权限控制ListAuthorityConfigOne noviceConfig; //初级版的权限控制ListAuthorityConfigOne intermeConfig; //中级版的权限控制
}4jackson的JsonTypeInfo根据不同的type使用不同的实体类接收定义一个抽象父类AuthorityConfigOne每种类型都继承此父类使用父类型来存放规则集合。遍历规则的时候可以根据它具体是哪种子类型来调用此种类型的校验逻辑这体现了java的多态性。AuthorityConfigOne类
Data
FieldDefaults(level AccessLevel.PRIVATE)
JsonTypeInfo(use JsonTypeInfo.Id.CUSTOM, include JsonTypeInfo.As.EXISTING_PROPERTY,visible true,property type)
JsonTypeIdResolver(AuthorityTypeIdResolver.class)
JsonIgnoreProperties(ignoreUnknown true)
NoArgsConstructor(access AccessLevel.PROTECTED)
public abstract class AuthorityConfigOne {AuthorityType type;
}JsonTypeInfo注解的property属性指定了按哪个字段来确定接收规则的实体类property属性的值需要对应上AuthorityConfigOne上的某个字段此处对应上的是type字段
JsonTypeIdResolver注解指定了序列化java对象转成字符串、反序列化字符串转成java对象时的对应关系这也是能够根据不同type使用不同实体接收的原因AuthorityTypeIdResolver.class类需要自己定义。
5AuthorityTypeIdResolver指定了序列化与反序列化为哪种类型AuthorityTypeIdResolver实体类
public class AuthorityTypeIdResolver extends TypeIdResolverBase {private JavaType superType;Overridepublic void init(JavaType bt) {superType bt;}Overridepublic String idFromValue(Object value) {return idFromValueAndType(value, value.getClass());}//序列化调用的方法Overridepublic String idFromValueAndType(Object value, Class? suggestedType) {if (!(value instanceof AuthorityConfigOne)) {return null;}AuthorityConfigOne filter (AuthorityConfigOne) value;return filter.getType().getValue();}Overridepublic JsonTypeInfo.Id getMechanism() {return JsonTypeInfo.Id.NAME;}//反序列化时根据指定的property字段值匹配按哪种实体类来接收Overridepublic JavaType typeFromId(DatabindContext context, String id) throws IOException {AuthorityType authorityType AuthorityType.valueOfNew(id);if (authorityType null) {throw new IOException(String.format(id:%s not filter type, id));}final Class? extends AuthorityConfigOne authorityClassType;switch (authorityType) {case Workspace:authorityClassType WorkspaceAuthority.class;break;case Num:authorityClassType NumAuthority.class;break;case Deadline:authorityClassType DeadlineAuthority.class;break;case Disabled:authorityClassType DisabledAuthority.class;break;case DisabledType:authorityClassType DisabledTypeAuthority.class;break;default:throw new IOException(String.format(not supported filterType:%s, authorityType));}return context.constructSpecializedType(superType, authorityClassType);}
}idFromValueAndType()方法是序列化时确定type的值typeFromId()方法是反序列化时根据指定的property字段值匹配按哪种实体类来接收。这样对实体类进行序列化后再反序列化时才能找到具体的接收实体。
6程序启动加载规则使用jackson下的ObjectMapper把文件流按类型引用转成对应的类型这里使用配置类记录转好的规则集合这样后面拦截器直接注入这个配置类就能获取到规则集合。使用spring的注解PostConstruct初始化加载在程序启动时会执行bean中被PostConstruct修饰的方法。AuthorityInit初始化类
Configuration
Data
public class AuthorityInit {//转成的类型引用private static final TypeReferenceListAuthorityConfigAll AUTHORITY_LIST_TYPE new TypeReferenceListAuthorityConfigAll() {};//记录规则信息key为接口名这样判断某个接口是否有配置校验规则可以在时间复杂度为O(1)下完成private MapString,AuthorityConfigType authorityMap new HashMapString,AuthorityConfigType();//程序启动时会执行bean下被此注解修饰的方法PostConstructpublic void init() throws IOException {InputStream inputStream null;try {//读取权限配置文件inputStream ClassLoader.getSystemResourceAsStream(AuthorityConfig.json);//使用jackson下的ObjectMapper类读取文件流ObjectMapper objectMapper new ObjectMapper();//把读取到的文件流按某种类型来接收ListAuthorityConfigAll list objectMapper.readValue(inputStream, AUTHORITY_LIST_TYPE);if(null ! list list.size() 0) {//把list转成maplist每条记录的key字段值作为map的key值config字段值作为map的value值authorityMap list.stream().collect(Collectors.toMap(AuthorityConfigAll::getKey,AuthorityConfigAll::getConfig));}} catch (Exception e){e.printStackTrace();} finally {//关闭文件流if(null ! inputStream) {inputStream.close();}}}
}从json文件中读取到文件流按类型引用把json文件反序列化到实体类中获取到的list集合再转成map类型存放规则集合。程序启动后map存放的记录截图 从截图中可以看出每个接口是一条map记录key为接口名value为规则集合分为公共、初级、中级规则集合具体的规则已经根据type用不同的实体接收。
三、拦截器定义 需要定义拦截器来拦截请求拦截器可以配置哪些请求要拦截哪些请求加白放行。自定义拦截器只需要实现HandlerInterceptor接口即可把自定义拦截器添加到管理所有拦截器的InterceptorRegistry拦截器注册类中。不管用户定义了多少个拦截器都是由InterceptorRegistry类统一管理。把自定义拦截器添加到InterceptorRegistry中的方式为创建一个配置类类实现WebMvcConfigurer接口重写它的addInterceptors添加拦截器方法在方法中把自定义拦截器以bean的方式加入进去。当请求进来时InterceptorRegistry会遍历注册到它下面的拦截器根据配置的拦截规则依次执行拦截器的三个默认方法preHandle()、postHandle()、afterCompletion()preHandle是业务Controller层处理之前执行可以用于校验、检查等操作postHandle是Controller层处理完在进行视图渲染之前执行afterCompletion是视图渲染结束之后调用一般用于销毁资源。
1.自定义拦截器 自定义拦截器重写preHandle方法此方法作为权限校验的入口点。自定义拦截器AuthorityHandlerInterceptor类
Slf4j
public class AuthorityHandlerInterceptor implements HandlerInterceptor {//业务controller层响应之前调用Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//只针对于方法进行处理if (!(handler instanceof HandlerMethod)) {return true;}if(!(request instanceof HttpServletRequest)){return true;}return true;}
}2.注册拦截器 把自定义拦截器注册到InterceptorRegistry类中进行管理。AuthorityHandlerConfig类
Configuration
public class AuthorityHandlerConfig implements WebMvcConfigurer {//自定义拦截器注册为beanBeanpublic AuthorityHandlerInterceptor getAuthorityHandlerInterceptor(){return new AuthorityHandlerInterceptor();}//添加自定义拦截器Overridepublic void addInterceptors(NotNull InterceptorRegistry registry) { registry.addInterceptor(getAuthorityHandlerInterceptor()).order(Ordered.HIGHEST_PRECEDENCE);}
}四、获取请求参数 我们执行校验时需要获取参数值例如获取操作资源的id、获取当前用户id等把获取到的参数值作为执行校验的参数。 获取请求参数需要考虑接口的请求方式为get还是post、还需要考虑上传的文件流、动态参数作为接口后缀的情况像http://api/getUser/{id}后面的id值是接口的一部分有些post请求参数可能会放到url后像http://api/xxx?id1。
1.获取get提交方式参数 get方式提交参数都是跟在url后面可以从HttpServletRequest中获取。获取get方式参数的方式 private MapString, Object getParamMaps(HttpServletRequest request) throws IOException {//存放参数值的集合MapString, Object paramsMaps new TreeMap();//获取url后面跟的参数MapString, String[] parameterMap request.getParameterMap();if(null ! parameterMap !parameterMap.isEmpty() parameterMap.size() 0) {SetMap.EntryString, String[] entries parameterMap.entrySet();IteratorMap.EntryString, String[] iterator entries.iterator();while (iterator.hasNext()) {Map.EntryString, String[] next iterator.next();paramsMaps.putIfAbsent(next.getKey(), next.getValue()[0]);}}return paramsMaps;}2.获取post提交方式参数 post方式提交参数需要从HttpServletRequest的输入流中获取但是获取输入流的方法request.getInputStream()只能调用一次拦截器中调用后Controller层就获取不到这些参数了所以需要重写getInputStream()方法不管调用多少次getInputStream()都能获取到参数。
1定义RequestWrapper类 RequestWrapper类默认构造函数调用request.getInputStream()获取到参数值把参数值记录在一个内部变量中让此类继承HttpServletRequestWrapper这样就可以让过滤器链chain向下传递请求时传递RequestWrapper类。过滤器链chain向下传递请求的方法 void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;HttpServletRequestWrapper类的继承关系 所以当请求为post方式时我们创建一个RequestWrapper类并把此RequestWrapper类作为过滤器chain链向下传递的request。重写的getInputStream()方法是根据RequestWrapper类内部变量值生成的输入流内部变量在创建RequestWrapper类时已经接收了请求参数值这样无论调用多少次getInputStream()都能获取到参数值。当这样处理后后面Controller层获取参数时执行的getInputStream()也是RequestWrapper类重写的方法因为过滤器链向下传递的ServletRequest的具体类是自定义的RequestWrapper类。RequestWrapper类
public class RequestWrapper extends HttpServletRequestWrapper {//内部变量记录请求参数private String body;public RequestWrapper(HttpServletRequest request) throws IOException {//把request设置到父类中super(request);//获取请求输入流的方法request.getInputStream()只能调用一次在此处获取后把值设置到变量body中//后面controller层需要从HttpServletRequestWrapper类获取输入流的方法在此类进行重写把body的值写入到输入流中这样从controller层调用时就能获取到输入流StringBuilder stringBuilder new StringBuilder();InputStream inputStream null;BufferedReader bufferedReader null;try {inputStream request.getInputStream();if (inputStream ! null) {bufferedReader new BufferedReader(new InputStreamReader(inputStream,UTF-8));char[] charBuffer new char[128];int bytesRead -1;while ((bytesRead bufferedReader.read(charBuffer)) 0) {stringBuilder.append(charBuffer, 0, bytesRead);}} else {stringBuilder.append();}} catch (IOException ex) {throw ex;} finally {if (inputStream ! null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}if (bufferedReader ! null) {try {bufferedReader.close();} catch (IOException ex) {throw ex;}}}body stringBuilder.toString();}/*** 重写父类HttpServletRequestWrapper的getInputStream方法从body中获取请求参数这个会在controller层进行参数获取时调用*/Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream byteArrayInputStream new ByteArrayInputStream(body.getBytes(UTF-8));ServletInputStream servletInputStream new ServletInputStream() {Overridepublic boolean isFinished() {return false;}Overridepublic boolean isReady() {return false;}Overridepublic void setReadListener(ReadListener readListener) {}Overridepublic int read() throws IOException {return byteArrayInputStream.read();}};return servletInputStream;}/*** 重写父类HttpServletRequestWrapper获取字符流的方式这个会在controller层进行参数获取时调用*/Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(this.getInputStream(),UTF-8));}/*** 直接返回获取 body*/public String getBody() {return this.body;}
}2定义过滤器 当为post请求时需要重新设置过滤器链chain向下传递的ServletRequest若是get请求不用处理直接传递接收到的ServletRequest。过滤器负责ServletRequest的传递拦截器不负责ServletRequest的传递先执行过滤器再执行拦截器。自定义过滤器需要实现Filter重写doFilter方法自定义过滤器HttpServletRequestFilter类
public class HttpServletRequestFilter implements Filter {Overridepublic void destroy() {}//过滤器负责request的传递拦截器不负责request的传递Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse response,FilterChain chain) throws IOException, ServletException {ServletRequest requestWrapper null;if(servletRequest instanceof HttpServletRequest){HttpServletRequest request (HttpServletRequest) servletRequest;String methodType request.getMethod();if(post.equalsIgnoreCase(methodType)){//当为post方式时需要使用request.getInputStream()获取参数此方法只能使用一次所以创建一个方法来接收参数body//并重写getInputStream方法后面controller层需要从HttpServletRequestWrapper类获取输入流的方法在此自定义类进行重写把body的值写入到输入流中这样从controller层调用时就能获取到输入流requestWrapper new RequestWrapper(request);}}// 在chain.doFiler方法中传递新的request对象if (requestWrapper null) {chain.doFilter(servletRequest, response);} else {chain.doFilter(requestWrapper, response);}}Overridepublic void init(FilterConfig arg0) throws ServletException {}
}3注册过滤器 自定义的过滤器需要注册到配置中使用bean管理过滤器注册FilterRegistration类
Configuration
public class FilterRegistration {Beanpublic FilterRegistrationBean httpServletRequestReplacedRegistration() {FilterRegistrationBean registration new FilterRegistrationBean();//添加自定义过滤器registration.setFilter(new HttpServletRequestFilter());registration.addUrlPatterns(/*);registration.addInitParameter(paramName, paramValue);registration.setName(httpServletRequestFilter);registration.setOrder(1);return registration;}
}4获取post提交方式参数 需要使用request.getInputStream()方法获取到输入流此时的request已经在过滤器中变更为自定义的RequestWrapper所以此处调用的是RequestWrapper类的getInputStream()方法。获取参数的方法 private MapString, Object getParamMaps(HttpServletRequest request) throws IOException {String methodType request.getMethod();MapString, Object paramsMaps new TreeMap();//post方式时单独处理if(post.equalsIgnoreCase(methodType)){try {String body getParameBody(request);TreeMap paramsMapsTemp JSONObject.parseObject(body, TreeMap.class);if(null ! paramsMapsTemp) {paramsMaps paramsMapsTemp;}} catch (Exception e) {e.printStackTrace();}}return paramsMaps;}/*** Description: 获取请求参数的body值*/public String getParameBody(HttpServletRequest request) throws IOException {StringBuilder stringBuilder new StringBuilder();InputStream inputStream null;BufferedReader bufferedReader null;try {//此处request.getInputStream()方法调用到的是自定义类RequestWrapper重写的方法getInputStream()//重写的getInputStream方法是使用过滤器检测到是post方法时创建的RequestWrapper每次获取都是拿接收到的body参数组织的inputStream,所以可以重复调用//controller层调用的时候也是调用到RequestWrapper重写的方法getInputStreaminputStream request.getInputStream();if (inputStream ! null) {bufferedReader new BufferedReader(new InputStreamReader(inputStream,UTF-8));char[] charBuffer new char[128];int bytesRead -1;while ((bytesRead bufferedReader.read(charBuffer)) 0) {stringBuilder.append(charBuffer, 0, bytesRead);}} else {stringBuilder.append();}} catch (IOException ex) {throw ex;} finally {if (inputStream ! null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}if (bufferedReader ! null) {try {bufferedReader.close();} catch (IOException ex) {throw ex;}}}return stringBuilder.toString();}3.上传文件参数处理 上传文件都是用post方式提交经过上面post方式对参数处理后在Controller层获取到的文件流为空所以需要对post方式上传文件特殊处理。在过滤器中判断是上传文件时请求的contentType包含multipart/form-data字符使用MultipartResolver对文件流处理一下。过滤器中doFilter方法 //过滤器负责request的传递拦截器不负责request的传递Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse response,FilterChain chain) throws IOException, ServletException {ServletRequest requestWrapper null;if(servletRequest instanceof HttpServletRequest){HttpServletRequest request (HttpServletRequest) servletRequest;String contentType request.getContentType();String method multipart/form-data;if (contentType ! null contentType.contains(method)) {//处理文件流上传的方式把请求处理成MultipartHttpServletRequest传递下去//实现request的转换MultipartResolver resolver new CommonsMultipartResolver(request.getSession().getServletContext());MultipartHttpServletRequest multipartRequest resolver.resolveMultipart(request);// 将转化后的 request 放入过滤链中request multipartRequest;requestWrapper new RequestWrapper(request);} else {String methodType request.getMethod();if(post.equalsIgnoreCase(methodType)){//当为post方式时需要使用request.getInputStream()获取参数此方法只能使用一次所以创建一个方法来接收参数body//并重写getInputStream方法后面controller层需要从HttpServletRequestWrapper类获取输入流的方法在此自定义类进行重写把body的值写入到输入流中这样从controller层调用时就能获取到输入流requestWrapper new RequestWrapper(request);}}}// 在chain.doFiler方法中传递新的request对象if (requestWrapper null) {chain.doFilter(servletRequest, response);} else {chain.doFilter(requestWrapper, response);}}当使用MultipartResolver处理MultipartFile文件时它需要依赖commons-fileupload包在项目pom.xml中引入相关依赖 dependencygroupIdcommons-fileupload/groupIdartifactIdcommons-fileupload/artifactIdversion1.3.3/version/dependency4.获取动态接口参数 当接口定义为/xxx/{id}id作为动态参数拼接接口名例如下面这样的接口 RequestMapping(value {/xxx/{id}}, method RequestMethod.GET)public Object useShare(PathVariable String id) {return xxx;}获取到参数的key为PathVariable指定的名称。获取动态参数具体值的方式 private MapString, Object getParamMaps(HttpServletRequest request) throws IOException {MapString, Object paramsMaps new TreeMap();//获取动态参数PathVariableMapString, String pathVars (MapString, String) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);if(null ! pathVars !pathVars.isEmpty() pathVars.size() 0) {SetMap.EntryString, String entries pathVars.entrySet();IteratorMap.EntryString, String iterator entries.iterator();while (iterator.hasNext()) {Map.EntryString, String next iterator.next();paramsMaps.putIfAbsent(next.getKey(), next.getValue());}}return paramsMaps;}5.获取系统固定参数 有一些参数是根据token获取的值例如用户id用户id在规则校验中用得特别频繁所以按固定参数的方式获取约定好用户id的key后面校验时直接使用。完整的获取请求参数的方法 private MapString, Object getParamMaps(HttpServletRequest request) throws IOException {String methodType request.getMethod();MapString, Object paramsMaps new TreeMap();if(post.equalsIgnoreCase(methodType)){try {String body getParameBody(request);TreeMap paramsMapsTemp JSONObject.parseObject(body, TreeMap.class);if(null ! paramsMapsTemp) {paramsMaps paramsMapsTemp;}} catch (Exception e) {e.printStackTrace();}}MapString, String[] parameterMap request.getParameterMap();if(null ! parameterMap !parameterMap.isEmpty() parameterMap.size() 0) {SetMap.EntryString, String[] entries parameterMap.entrySet();IteratorMap.EntryString, String[] iterator entries.iterator();while (iterator.hasNext()) {Map.EntryString, String[] next iterator.next();paramsMaps.putIfAbsent(next.getKey(), next.getValue()[0]);}}//获取动态参数PathVariableMapString, String pathVars (MapString, String) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);if(null ! pathVars !pathVars.isEmpty() pathVars.size() 0) {SetMap.EntryString, String entries pathVars.entrySet();IteratorMap.EntryString, String iterator entries.iterator();while (iterator.hasNext()) {Map.EntryString, String next iterator.next();paramsMaps.putIfAbsent(next.getKey(), next.getValue());}}//获取用户id这是sa-token框架的获取方式paramsMaps.put(userId, StpUtil.getLoginId());return paramsMaps;}五、拦截请求 拦截请求的入手点为拦截器只针对于方法调用进行拦截非方法的直接放行例如加载静态资源。经过上面的步骤校验规则已经定义并加载到内存中请求参数也获取到map中接下来要对请求进行拦截获取接口配置的校验规则集合。
1.获取校验规则 在拦截器中注入程序启动时加载规则信息的配置类AuthorityInit通过AuthorityInit可以获取到记录规则集合的map。 AutowiredAuthorityInit authorityInit; //注入配置类authorityInit.getAuthorityMap();//获取规则配置信息map集合2.固定接口地址匹配 获取到请求的接口地址判断此接口是否配置了校验规则规则的校验信息已经使用map存放key为接口名value为AuthorityConfigType包含公共、初级版、中级版规则集合使用map.containsKey即可判断是否包含不包含的直接放行包含则遍历规则执行校验。 可以使用这样的方式获取请求接口地址 String servletPath request.getServletPath();当一个接口请求地址是这样http://ipport/api/xxx/getInfo获取到的servletPath为/xxx/getInfo所以校验配置规则的key也是接口的后缀。判断固定接口是否有配置校验规则
//获取请求接口地址
String servletPath request.getServletPath();
//判断接口是否配置了校验规则
if(authorityInit.getAuthorityMap().containsKey(servletPath)){//校验规则}3.动态接口地址匹配 当接口为动态参数的方式时获取到的servletPath是一个动态的例如/xxx/{id}接口当参数为1时获取到的是/xxx/1参数为2时获取到的是/xxx/2这时候就需要使用匹配的方式比对。针对于动态参数的接口配置规则的key使用*代替动态的部分像/xxx{id}这个接口配置的key为:
{key: /xxx/*,config:{publicConfig: [],noviceConfig: [],intermeConfig: []}},可以使用获取动态参数值的方式去获取参数当获取到的动态参数值不为空则表示是一个动态接口地址需要使用匹配的方式判断包含关系若是动态参数值为空说明是一个固定接口地址使用map的包含判断。
动态参数的匹配使用AntPathMatcher路径匹配类匹配获取到的servletPath与key关系key的集合可以过滤一下只包含*号的记录当匹配了则获取配置的校验规则集合。 //获取请求接口地址String servletPath request.getServletPath();//获取动态参数请求接口的方式MapString, String pathVars (MapString, String) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);if(null ! pathVars !pathVars.isEmpty() pathVars.size() 0) { //包含动态参数使用正则进行判断MapString,AuthorityConfigType authorityMap authorityInit.getAuthorityMap();SetString keySet authorityMap.keySet();//获取到key包含*的记录ListString collect keySet.stream().filter(x - x.indexOf(*) ! -1).collect(Collectors.toList());if(null ! collect collect.size() 0){AntPathMatcher pathMatcher new AntPathMatcher();//url匹配工具类for(String key : collect) {if(pathMatcher.match(key,servletPath)){ //地址匹配break;}}}} 当接口地址匹配后需要获取此接口配置的校验规则集合并把这些规则集合传递到一个执行校验的service中。 此处创建一个名为CheckAuthorityService的service类并注入到拦截器中。完整的拦截器代码
Slf4j
public class AuthorityHandlerInterceptor implements HandlerInterceptor {AutowiredAuthorityInit authorityInit; //注入配置类AutowiredCheckAuthorityService checkAuthorityService; //注入处理校验的service类//业务controller层响应之前调用Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//只针对于方法进行处理if (!(handler instanceof HandlerMethod)) {return true;}if(!(request instanceof HttpServletRequest)){return true;}//获取请求接口地址String servletPath request.getServletPath();//获取动态参数请求接口的方式MapString, String pathVars (MapString, String) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);if(null ! pathVars !pathVars.isEmpty() pathVars.size() 0) { //包含动态参数使用正则进行判断MapString,AuthorityConfigType authorityMap authorityInit.getAuthorityMap();SetString keySet authorityMap.keySet();//获取到key包含*的记录ListString collect keySet.stream().filter(x - x.indexOf(*) ! -1).collect(Collectors.toList());if(null ! collect collect.size() 0){AntPathMatcher pathMatcher new AntPathMatcher(); //url匹配工具类for(String key : collect) {if(pathMatcher.match(key,servletPath)){ //地址匹配checkAuthorityService.checkAuthority(request,authorityInit.getAuthorityMap().get(key));break;}}}} else { //固定接口地址使用map的包含判断if(authorityInit.getAuthorityMap().containsKey(servletPath)){//校验规则checkAuthorityService.checkAuthority(request,authorityInit.getAuthorityMap().get(servletPath));}}return true;}
}六、执行校验 经过上面的步骤已经获取到要校验的规则集合CheckAuthorityService类是处理校验逻辑的根据需求分析需要执行sql查询数据库所以注入JdbcTemplate需要使用反射执行业务方法所以注入ApplicationContext程序上下文来获取bean对象。获取请求参数值的方法上面已经分析直接把方法写到CheckAuthorityService类中调用。
1.执行校验入口 执行入口就是CheckAuthorityService类的checkAuthority()方法在此方法中获取到此次请求的参数值、公共规则集合、根据用户开通的版本情况获取对应的规则集合遍历执行规则校验。看下checkAuthority()方法 public void checkAuthority(HttpServletRequest request, AuthorityConfigType authorityConfigType) throws Exception {//获取请求参数MapString, Object paramsMaps getParamMaps(request);//配置的权限拦截不为空if(null ! authorityConfigType) {//获取公共权限进行处理ListAuthorityConfigOne publicConfig authorityConfigType.getPublicConfig();//配置的规则不为空则处理if(null ! publicConfig publicConfig.size() 0) {checkAuthorityConfigOne(publicConfig,paramsMaps);}//------获取用户的权限版本int versionNum getUserVersionNum();if(versionNum 0) { //初级版权限ListAuthorityConfigOne noviceConfig authorityConfigType.getnoviceConfig();if(null ! noviceConfig noviceConfig.size() 0) {checkAuthorityConfigOne(noviceConfig,paramsMaps);}} else if (versionNum 1) {//中级版权限ListAuthorityConfigOne intermeConfig authorityConfigType.getintermeConfig();if(null ! intermeConfig intermeConfig.size() 0) {checkAuthorityConfigOne(intermeConfig,paramsMaps);}}}}获取用户开通的权限版本可以使用反射去执行查询方法也可以使用JdbcTemplate执行sql的方式去查询反射的方式可以使用缓冲redis记录用户的版本情况。这里使用sql的方式 private int getUserVersionNum() {String querySql select version_num from xxx_user where user_id ? ;//执行sql查询return jdbcTemplate.queryForObject(querySql, Integer.class, new Object[]{StpUtil.getLoginId()});}遍历规则集合根据规则是哪种实体类型调用它对应的处理逻辑遍历处理规则的方法checkAuthorityConfigOne()
private void checkAuthorityConfigOne(ListAuthorityConfigOne authorityConfigOneList, MapString, Object paramsMaps) throws Exception {for(AuthorityConfigOne authorityConfigOne : authorityConfigOneList){if(authorityConfigOne instanceof WorkspaceAuthority) {//校验workspace类型checkWorkspace(paramsMaps,(WorkspaceAuthority)authorityConfigOne);} else if(authorityConfigOne instanceof NumAuthority){//校验num类型checkNum(paramsMaps,(NumAuthority)authorityConfigOne);} else if(authorityConfigOne instanceof DeadlineAuthority){//验证deadline会员截止时间checkDeadline(paramsMaps,(DeadlineAuthority)authorityConfigOne);} else if(authorityConfigOne instanceof DisabledAuthority){//验证disabled接口是否可以访问checkDisabled(paramsMaps,(DisabledAuthority)authorityConfigOne);} else if(authorityConfigOne instanceof DisabledTypeAuthority){//验证disabledtype接口可以访问的类型checkDisabledType(paramsMaps,(DisabledTypeAuthority)authorityConfigOne);}}}2.反射执行校验 workspace类型校验需要使用反射机制从spring程序上下文获取到业务service的bean对象执行service下定义的方法执行方法需要先获取到此方法获取方法的时候要传递方法的参数类型执行方法时要带有参数值参数值从请求的参数map里获取执行完业务方法后返回值与配置的阈值进行比较。看下workspace类型校验的方法checkWorkspace() private void checkWorkspace(MapString, Object paramsMaps, WorkspaceAuthority workspaceAuthority) throws Exception {//从spring容器中根据bean名称获取beanObject bean applicationContext.getBean(workspaceAuthority.getBeanName());//根据class获取方法时需要设置方法接收的参数类型Class[] parameterTypes new Class[workspaceAuthority.getMethodParamType().size()];//方法参数的值Object[] methodParam new Object[workspaceAuthority.getMethodParamKey().size()];for(int i 0;i workspaceAuthority.getMethodParamType().size();i) {//根据全限定类名创建classparameterTypes[i] Class.forName(workspaceAuthority.getMethodParamType().get(i).toString());//根据配置的参数key从请求中获取参数值Object parameValue paramsMaps.getOrDefault(workspaceAuthority.getMethodParamKey().get(i),null);if(null parameValue){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 获取不到参数workspaceAuthority.getMethodParamKey().get(i)的值请确保参数的准确性);}//设置上参数值参数值的类型需要根据它的类型进行转一下参数接收过来默认是字符串methodParam[i] getMethodParamWidthType(workspaceAuthority.getMethodParamType().get(i).toString(),parameValue);}//根据方法名和参数类型获取方法Method method bean.getClass().getMethod(workspaceAuthority.getMethodName(),parameterTypes);//使用反射执行方法接收值Object value method.invoke(bean,methodParam);//值进行比较if(null ! value){if(Integer.parseInt(value.toString()) workspaceAuthority.getCode()){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 您没有权限操作此记录请确保参数的准确性);}} else {throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 程序错误请稍后重试);}}从请求参数里面获取到的参数值类型为Object反射执行时需要转成对应的参数类型例如Integer类型的参数参数值需要转成Integer。写一个根据类型转成对应值的方法 private Object getMethodParamWidthType(String type, Object parameValue) {switch (type) {case java.lang.Integer :return Integer.parseInt(parameValue.toString());default:return parameValue.toString();}}3.执行sql校验数量 num类型需要根据配置的sql以及sql需要的参数key从请求参数map中获取到参数key对应的值把参数值作为sql执行的参数传递进行执行sql获取到sql的结果值与配置的阈值进行比较。看下校验数量的方法checkNum() private void checkNum(MapString, Object paramsMaps, NumAuthority numAuthority) {//获取需要执行的sqlString querySql numAuthority.getQuerySql();//构造参数集合Object[] paramKey new Object[numAuthority.getParamKey().size()];//变量参数集合设置进数组中for(int i 0;i numAuthority.getParamKey().size();i) {//从请求参数中获取参数的值Object parameValue paramsMaps.getOrDefault(numAuthority.getParamKey().get(i),null);if(null parameValue){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 获取不到参数numAuthority.getParamKey().get(i)的值请确保参数的准确性);}paramKey[i] parameValue;}//执行sql查询Integer num jdbcTemplate.queryForObject(querySql, Integer.class, paramKey);//判断数量是否大于配置的最大数量if(num numAuthority.getUpLimit()){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 已经达到您的最大数量numAuthority.getUpLimit());}}4.校验服务截止时间 deadline类型需要获取用户开通服务的截止时间拿到截止时间与当前时间做差差值小于0表示用户服务时间已到期。获取用户服务截止时间有用缓存redis的话可以使用反射获取也可以用sql执行获取此处用sql查询获取。看下校验服务截止时间的方法checkDeadline() private static final DateTimeFormatter dateTimeFormatter DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss); //定义日期格式private void checkDeadline(MapString, Object paramsMaps, DeadlineAuthority deadlineAuthority) {//获取用户的会员截止时间与当前时间做比对String dataLineStr getUserDeadLine();LocalDateTime deadLine LocalDateTime.parse(dataLineStr,dateTimeFormatter);Duration duration Duration.between(LocalDateTime.now(),deadLine);if(duration.toMillis() 0){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 您的会员时间已到期请您续期再访问);}}获取用户开通服务截止时间的方法getUserDeadLine() private String getUserDeadLine() {String querySql select dead_line from xxx_user where user_id ? ;//执行sql查询return jdbcTemplate.queryForObject(querySql, String.class, new Object[]{StpUtil.getLoginId()});}5.禁用接口校验 disabled类型是禁用接口有配置这个类型直接拦截接口。看下禁用接口校验的方法checkDisabled()
private void checkDisabled(MapString, Object paramsMaps, DisabledAuthority disabledAuthority) {throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 您没有权限操作此资源);}6.允许操作类型校验 disabledtype类型是配置白名单的方式进行校验用户允许操作的类型配置在集合里面配置一个需要校验的key根据key从请求参数里面获取值看值是否在允许的集合里面在才放行。看下校验允许操作类型校验的方法checkDisabledType() private void checkDisabledType(MapString, Object paramsMaps, DisabledTypeAuthority disabledTypeAuthority) {String checkKey disabledTypeAuthority.getCheckKey();Object parameValue paramsMaps.getOrDefault(checkKey,null);if(null parameValue){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 获取不到参数checkKey的值请确保参数的准确性);}ArrayList allowValues disabledTypeAuthority.getAllowValues();if(null allowValues || allowValues.size() 0) {throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 您没有权限操作此种类型的资源);}//设置上参数值参数值的类型需要根据它的类型进行转一下参数接收过来默认是字符串parameValue getMethodParamWidthType(disabledTypeAuthority.getKeyValueType(),parameValue);if(!allowValues.contains(parameValue)){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 您没有权限操作此种类型的资源);}}list中是否包含某个元素的判断需要把元素的类型转成与list元素一致再进行比较所以使用了getMethodParamWidthType()方法把元素转成需要的类型值。
完整的校验service类CheckAuthorityService
Service
public class CheckAuthorityService {Autowiredprivate ApplicationContext applicationContext;Autowiredprivate JdbcTemplate jdbcTemplate;private static final DateTimeFormatter dateTimeFormatter DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss);/*** Description: 校验权限规则*/public void checkAuthority(HttpServletRequest request, AuthorityConfigType authorityConfigType) throws Exception {//获取请求参数MapString, Object paramsMaps getParamMaps(request);//配置的权限拦截不为空if(null ! authorityConfigType) {//获取公共权限进行处理ListAuthorityConfigOne publicConfig authorityConfigType.getPublicConfig();//配置的规则不为空则处理if(null ! publicConfig publicConfig.size() 0) {checkAuthorityConfigOne(publicConfig,paramsMaps);}//------获取用户的权限版本int versionNum getUserVersionNum();if(versionNum 0) { //个人版权限ListAuthorityConfigOne noviceConfig authorityConfigType.getnoviceConfig();if(null ! noviceConfig noviceConfig.size() 0) {checkAuthorityConfigOne(noviceConfig,paramsMaps);}} else if (versionNum 1) {//创作版权限ListAuthorityConfigOne intermeConfig authorityConfigType.getintermeConfig();if(null ! intermeConfig intermeConfig.size() 0) {checkAuthorityConfigOne(intermeConfig,paramsMaps);}}}}/*** Description: 获取用户的权限版本*/private int getUserVersionNum() {String querySql select version_num from xxx_user where user_id ? ;//执行sql查询return jdbcTemplate.queryForObject(querySql, Integer.class, new Object[]{StpUtil.getLoginId()});}/*** Description: 校验一类权限*/private void checkAuthorityConfigOne(ListAuthorityConfigOne authorityConfigOneList, MapString, Object paramsMaps) throws Exception {for(AuthorityConfigOne authorityConfigOne : authorityConfigOneList){if(authorityConfigOne instanceof WorkspaceAuthority) {//校验workspace类型checkWorkspace(paramsMaps,(WorkspaceAuthority)authorityConfigOne);} else if(authorityConfigOne instanceof NumAuthority){//校验num类型checkNum(paramsMaps,(NumAuthority)authorityConfigOne);} else if(authorityConfigOne instanceof DeadlineAuthority){//验证会员截止时间checkDeadline(paramsMaps,(DeadlineAuthority)authorityConfigOne);} else if(authorityConfigOne instanceof DisabledAuthority){//验证接口是否可以访问checkDisabled(paramsMaps,(DisabledAuthority)authorityConfigOne);} else if(authorityConfigOne instanceof DisabledTypeAuthority){//验证接口可以访问的类型checkDisabledType(paramsMaps,(DisabledTypeAuthority)authorityConfigOne);}}}/*** Description: 验证接口可以访问的类型*/private void checkDisabledType(MapString, Object paramsMaps, DisabledTypeAuthority disabledTypeAuthority) {String checkKey disabledTypeAuthority.getCheckKey();Object parameValue paramsMaps.getOrDefault(checkKey,null);if(null parameValue){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 获取不到参数checkKey的值请确保参数的准确性);}ArrayList allowValues disabledTypeAuthority.getAllowValues();if(null allowValues || allowValues.size() 0) {throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 您没有权限操作此种类型的资源);}//设置上参数值参数值的类型需要根据它的类型进行转一下参数接收过来默认是字符串parameValue getMethodParamWidthType(disabledTypeAuthority.getKeyValueType(),parameValue);if(!allowValues.contains(parameValue)){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 您没有权限操作此种类型的资源);}}/*** Description: 验证接口是否可以访问配置了这个类型的都不允许访问接口*/private void checkDisabled(MapString, Object paramsMaps, DisabledAuthority disabledAuthority) {throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 您没有权限操作此资源);}/*** Description: 验证会员截止时间有此配置则验证当前时间与用户的过期时间*/private void checkDeadline(MapString, Object paramsMaps, DeadlineAuthority deadlineAuthority) {//获取用户的会员截止时间与当前时间做比对String dataLineStr getUserDeadLine();LocalDateTime deadLine LocalDateTime.parse(dataLineStr,dateTimeFormatter);Duration duration Duration.between(LocalDateTime.now(),deadLine);if(duration.toMillis() 0){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 您的会员时间已到期请您续期再访问);}}/*** Description: 获取用户的会员截止时间*/private String getUserDeadLine() {String querySql select create_time from doravis_sys_user where id ? ;//执行sql查询return jdbcTemplate.queryForObject(querySql, String.class, new Object[]{StpUtil.getLoginId()});}/*** Description: 检查数量*/private void checkNum(MapString, Object paramsMaps, NumAuthority numAuthority) {//获取需要执行的sqlString querySql numAuthority.getQuerySql();//构造参数集合Object[] paramKey new Object[numAuthority.getParamKey().size()];//变量参数集合设置进数组中for(int i 0;i numAuthority.getParamKey().size();i) {//从请求参数中获取参数的值Object parameValue paramsMaps.getOrDefault(numAuthority.getParamKey().get(i),null);if(null parameValue){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 获取不到参数numAuthority.getParamKey().get(i)的值请确保参数的准确性);}paramKey[i] parameValue;}//执行sql查询Integer num jdbcTemplate.queryForObject(querySql, Integer.class, paramKey);//判断数量是否大于配置的最大数量if(num numAuthority.getUpLimit()){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 已经达到您的最大数量numAuthority.getUpLimit());}}/*** Description: 校验workspace*/private void checkWorkspace(MapString, Object paramsMaps, WorkspaceAuthority workspaceAuthority) throws Exception {//从spring容器中根据bean名称获取beanObject bean applicationContext.getBean(workspaceAuthority.getBeanName());//根据class获取方法时需要设置方法接收的参数类型Class[] parameterTypes new Class[workspaceAuthority.getMethodParamType().size()];//方法参数的值Object[] methodParam new Object[workspaceAuthority.getMethodParamKey().size()];for(int i 0;i workspaceAuthority.getMethodParamType().size();i) {//根据全限定类名创建classparameterTypes[i] Class.forName(workspaceAuthority.getMethodParamType().get(i).toString());//根据配置的参数key从请求中获取参数值Object parameValue paramsMaps.getOrDefault(workspaceAuthority.getMethodParamKey().get(i),null);if(null parameValue){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 获取不到参数workspaceAuthority.getMethodParamKey().get(i)的值请确保参数的准确性);}//设置上参数值参数值的类型需要根据它的类型进行转一下参数接收过来默认是字符串methodParam[i] getMethodParamWidthType(workspaceAuthority.getMethodParamType().get(i).toString(),parameValue);}//根据方法名和参数类型获取方法Method method bean.getClass().getMethod(workspaceAuthority.getMethodName(),parameterTypes);//使用反射执行方法接收值Object value method.invoke(bean,methodParam);//值进行比较if(null ! value){if(Integer.parseInt(value.toString()) workspaceAuthority.getcode()){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 您没有权限操作此记录请确保参数的准确性);}} else {throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, 程序错误请稍后重试);}}/*** Description: 获取请求参数*/private MapString, Object getParamMaps(HttpServletRequest request) throws IOException {String methodType request.getMethod();MapString, Object paramsMaps new TreeMap();if(post.equalsIgnoreCase(methodType)){try {String body getParameBody(request);TreeMap paramsMapsTemp JSONObject.parseObject(body, TreeMap.class);if(null ! paramsMapsTemp) {paramsMaps paramsMapsTemp;}} catch (Exception e) {e.printStackTrace();}}MapString, String[] parameterMap request.getParameterMap();if(null ! parameterMap !parameterMap.isEmpty() parameterMap.size() 0) {SetMap.EntryString, String[] entries parameterMap.entrySet();IteratorMap.EntryString, String[] iterator entries.iterator();while (iterator.hasNext()) {Map.EntryString, String[] next iterator.next();paramsMaps.putIfAbsent(next.getKey(), next.getValue()[0]);}}//获取动态参数PathVariableMapString, String pathVars (MapString, String) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);if(null ! pathVars !pathVars.isEmpty() pathVars.size() 0) {SetMap.EntryString, String entries pathVars.entrySet();IteratorMap.EntryString, String iterator entries.iterator();while (iterator.hasNext()) {Map.EntryString, String next iterator.next();paramsMaps.putIfAbsent(next.getKey(), next.getValue());}}paramsMaps.put(userId, StpUtil.getLoginId());return paramsMaps;}/*** Description: 获取请求参数的body值*/public String getParameBody(HttpServletRequest request) throws IOException {StringBuilder stringBuilder new StringBuilder();InputStream inputStream null;BufferedReader bufferedReader null;try {//此处request.getInputStream()方法调用到的是自定义类RequestWrapper重写的方法getInputStream()//重写的getInputStream方法是使用过滤器检测到是post方法时创建的RequestWrapper每次获取都是拿接收到的body参数组织的inputStream,所以可以重复调用//controller层调用的时候也是调用到RequestWrapper重写的方法getInputStreaminputStream request.getInputStream();if (inputStream ! null) {bufferedReader new BufferedReader(new InputStreamReader(inputStream,UTF-8));char[] charBuffer new char[128];int bytesRead -1;while ((bytesRead bufferedReader.read(charBuffer)) 0) {stringBuilder.append(charBuffer, 0, bytesRead);}} else {stringBuilder.append();}} catch (IOException ex) {throw ex;} finally {if (inputStream ! null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}if (bufferedReader ! null) {try {bufferedReader.close();} catch (IOException ex) {throw ex;}}}return stringBuilder.toString();}/*** Description: 获取带类型的方法参数*/private Object getMethodParamWidthType(String type, Object parameValue) {switch (type) {case java.lang.Integer :return Integer.parseInt(parameValue.toString());default:return parameValue.toString();}}
}