# jforum.orize **Repository Path**: subpu/jforum.orize ## Basic Information - **Project Name**: jforum.orize - **Description**: 请求地址验证. - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2021-08-08 - **Last Updated**: 2021-09-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # jforum.orize #### 介绍 在请求时通过过滤器来验证请求的地址是否符合资源定义中的规则. 若符合再进行用户角色匹配, 目前支持:全匹配(一个用户有多个角色), 单匹配(一个用户只有一个角色) #### 安装教程 ##### 1. 项目基于jdk8开发. 低于这个版本无法运行。 ##### 2. 项目依赖(Maven) ``` org.reflections reflections 0.9.12 org.eclipse.persistence org.eclipse.persistence.moxy 2.7.1 javax.xml.bind jaxb-api 2.2.11 net.sf.saxon Saxon-HE 10.5 ``` 请保证使用Orize的项目中具有这些jar包。 ##### 3. SpringBoot使用超容易:[jforum.orize.starter](https://gitee.com/subpu/jforum.orize.starter) 基于SpringBoot(2.3.3. 版本无所谓了)的自动装配,示例项目: [jforum2的分支:boot-orize](https://gitee.com/subpu/jforum2/tree/boot-orize/) ##### 4. 非SpringBoot的SpringMVC 示例项目: [jforum的分支:orize](https://gitee.com/subpu/jforum/tree/orize/) #### 使用说明 ##### 1. 定义需要验证的资源. 目前支持:xml,json,注解。资源定义(xml/json)文件需要放在WEB-INF的目录下 ###### xml结构如下: ``` /orders/list get VIEW Member do ... ``` ###### json结构如下: ``` { "resources" : { "stamp" : "20210805", "item" : [ { "path" : "/orders/list", "method" : "get", "action" : "VIEW", "roles" : "Member", "spot" : "do" }, ... ] } } ``` ###### 注解: 需要在负责完成请求的方法上增加注解: Orize,例: ``` @GetMapping(path="/{path}.xhtml") @Orize(roles={"GUEST", "ADMIN", "MEMBER"}, method="GET", action={OrizeAction.VIEW}) public String boardHome(){} ``` 注意:当一个方法同时负责新增和编辑操作时注解需要如下使用 ``` @PostMapping(path = "/edit") @Orize(roles={"GUEST", "ADMIN", "MEMBER"}, method="POST", action={OrizeAction.ADD, OrizeAction.EDIT}, spot="action") public String editBoardPage(){} ``` 上面的示例表示新增时地址如下: ``` /edit?action=add ``` 编辑时地址如下: ``` /edit?action=edit ``` 以此来达到区分同一个请求路径表示哪个具体的操作. 若一个地址只完成一个操作不需要使用spot查询参数Key,若spot的值等于do可以忽略(默认值) 扫描注解(Orize)资源定义需要实现: OrizeAnnotationScanner, 以下示例为SpringMVC的实现 ``` import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import java.lang.reflect.Method; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class OrizeAnnotationSpringScanner extends OrizeAnnotationScanner { @Override protected List parsePath(Method method) { Stream _methodPath = Stream.empty(), _classPath = Stream.empty(); GetMapping getAnno = method.getAnnotation(GetMapping.class); if(null != getAnno){ String[] getMethodPath = getAnno.path(); String[] getMethodValue = getAnno.value(); _methodPath = Stream.concat(Stream.of(getMethodPath), Stream.of(getMethodValue)); } PostMapping postAnno = method.getAnnotation(PostMapping.class); if(null != postAnno){ String[] postMethodPath = postAnno.path(); String[] postMethodValue = postAnno.value(); _methodPath = Stream.concat(Stream.of(postMethodPath), Stream.of(postMethodValue)); } // Class aClass = method.getDeclaringClass(); RequestMapping _crm = aClass.getAnnotation(RequestMapping.class); if(null != _crm){ String[] classPathPrefix = _crm.path(); String[] classValPrefix = _crm.value(); _classPath = Stream.concat(Stream.of(classPathPrefix), Stream.of(classValPrefix)); } return OrizeAnnotationScanner.cartesian( _methodPath.collect(Collectors.toList()), _classPath.collect(Collectors.toList()), OrizeAnnotationScanner.mergePathFun); } } ``` ##### 2. 实现用户信息查询接口: OrizeMemberQuery 示例: ``` /** * OrizeMemberQuery的实现 */ public class OrizeMemberQueryImpl implements OrizeMemberQuery { @Override public OrizeMember query(HttpServletRequest request) { //ETC } } ``` 强烈不建议每次都从数据库(关系型数据库)中获取 ##### 3. 配置过滤器 (web.xml)示例如下: ``` OrizeAuthServletFilter com.apobates.forum.orize.servlet.OrizeAuthServletFilter loader xml path resouces.xml queryClass x.y.z.OrizeMemberQueryImpl roleMatch any ignoreDomain cdn.subpu.com ignorePath /static/** ignoreExt js,css,png,svg,gif,jpg OrizeAuthServletFilter /* ``` init-param说明: loader: 支持:xml,json,anno; 根据值不同使用不同的资源加载器 path:资源所在的位置,需要放到WEB-INF目录下 queryClass:OrizeMemberQuery实现类的类全名, 实现类中需要有无参的构造器 roleMatch:用户角色的匹配规则, any(单角色匹配),all(多角色匹配) ignoreDomain: 忽略的域名, 只要请求来自这些域名不进行验证. 多个之间用逗号分隔, 可选项(没有可以不设置此参数) 支持以下几种模式: ``` *.a.com, a.com, cdn.a.com ``` ignorePath: 忽略的请求路径, 只要请求路径符合定义不进行验证. 多个之间用逗号分隔, 可选项(没有可以不设置此参数) 支持以下几种模式: ``` /static, /static/*, /static/*/*.css, /static/** ``` ignoreExt: 忽略的请求文件扩展名, 只要请求文件符合定义不进行验证. 多个之间用逗号分隔, 可选项(没有可以不设置此参数) #### 其它说明 ##### 1. SpringMVC不建议使用OrizeAuthServletFilter. 项目提供了一个包装类: com.apobates.forum.member.strategy.spring.OrizeAuthHelper 使用示例如下: ``` /** * 实现Orize过滤器 */ public class OrizeAuthSpringInterceptorImpl extends HandlerInterceptorAdapter { @Autowired private OrizeAuthHelper orizeAuthHelper; @Autowired private OrizeMemberRolePredicate memberRolePredicate; @Value("${site.orize.log}") private String orizeResultLog; /** * 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器,自定义Controller * 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应; * * @param request * @param response * @param handler * @return * @throws java.lang.Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ImmutablePair ips = orizeAuthHelper.verify(memberRolePredicate, request); if(!ips.getLeft()){ String redirectPath = request.getContextPath() + orizeResultLog; // 验证失败时的提示地址; FlashMap flashMap = new FlashMap(); flashMap.put("errors", ips.getRight().get("message")); flashMap.put("method", ips.getRight().get("reqMethod")); flashMap.put("path", ips.getRight().get("reqPath")); flashMap.setTargetRequestPath(redirectPath); FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request); flashMapManager.saveOutputFlashMap(flashMap, request, response); response.sendRedirect(redirectPath); return false; } return true; } //ETC } ``` SpringMVC的忽略参数设置: ``` //不需要验证的配置 @Bean(name="cusIgnoreConfig") public OrizeAuthIgnoreConfig getIgnoreConfig(){ return OrizeAuthIgnoreConfig .defaultInstance() .setIgnoreMediaType("js", "css", "png", "svg", "gif", "jpg"); } //资源验证助手类 @Bean(name="orizeAuthHelper") public OrizeAuthHelper getAuthHelper(@NotNull OrizeMemberQuery memberQuery, @Nullable OrizeAuthIgnoreConfig cusIgnoreConfig, ServletContext sc){ return OrizeAuthHelper .defaultInstance("anno", sc) .setMemberQuery(memberQuery) .setIgnoreConfig(cusIgnoreConfig) .build(); //手动生成资源定义 //return OrizeAuthHelper.defaultInstance("xml", sc).setNotAnnoResourcePath("resources.xml").setMemberQuery(memberQuery).setIgnoreConfig(cusIgnoreConfig).build(); } ``` ##### 2. 项目不提供角色定义。 用户角色的验证规则只提供抽像的实现: any(OrizeMemberRoleAnyContainsPredicate),all(OrizeMemberRoleContainsAllPredicate),若这两个抽像不满足您的需求可以自行实现: OrizeMemberRolePredicater ``` /** * 用户角色验证谓词表达式 */ public interface OrizeMemberRolePredicate { /** * 返回验证OrizeMember的谓词表达式 * * @return 第一个参数: OrizeMember 用户信息, 第二个参数: 资源中定义的角色要求 */ BiPredicate getPredicate(); } ``` ##### 3. 关于资源定义中的路径包含占位符 项目暂时只支持全模式匹配, 不支持通配符匹配. 例: ``` //资源定义: /board/volumes/{path}.xhtml //匹配请求地址 /board/volumes/20210810.xhtml //不匹配请求地址 /board/2020/20210810.xhtml ``` 为了支持占位符Orize注解增加两个新方法 ``` /** * 注解.用于收集资源的定义 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Orize { /** * 路径中是否存在占位符 * 例: * /{id}.xhtml 存在 * /edit 不存在 * @return true存在/false不存在 */ boolean slot()default false; /** * 若存在占位符,占位的名称是什么 * 例:/{id}.xhtml, slotKeys={"id"} * @return */ String[] slotKeys()default {"*"}; //ETC } ``` SpringMVC控制器方法示例: ``` @GetMapping(path="/{path}.xhtml") @Orize(roles={"NO","BM","MASTER","ADMIN"}, method="get", action={OrizeAction.VIEW}, slot = true, slotKeys = {"path"}) public String volumeHome(){} ``` ##### 4. 关于xml和json文件的生成 在项目中提供了一个示例: com.apobates.forum.orize.servlet.OrizeResourceGenTest.