java开发,根据代码自动生成api接口文档工具,支持RESTful风格
基本信息
演示
数据模拟mock
在线预览地址
http://lovepeng.gitee.io/apidoc
这个工具是一个典型的前后端分离开发的项目,想了解前后端分离开发的同学也可以下载本项目学习。
项目后端使用java代码,前端使用angular开发。Java开发时,使用注解把文档相关信息标注在类的方法上,通过工具自动扫描代码的注解,生成json数据,发给前端,前端angular解析生成页面
本项目自带一个spring-boot框架为基础的demo(这里使用spring-boot做演示的demo仅仅是为了方便,本质上只要是java写的项目都可以用该工具),前端用angular做了一个比较漂亮的界面(最终前端界面都编译成了html,如果你前端不熟悉,可以跳过,不用管他),这里使用angular开发仅仅是我比较喜欢,你可以用任何你喜欢的的前端框架或者仅仅使用html写一个漂亮的界面就可以。
后端项目开源地址:https://github.com/liepeng328/api-doc
前端开源地址:https://github.com/liepeng328/api-doc-angular
功能目录对应关系
请求参数和响应参数对应
当成一个工具类用就可以了,下载本项目,拷贝包com.apidoc下的代码到你的系统,
然后拷贝前端html页面,在static.apidoc文件下,到你的资源文件下。即可使用
使用时,后台提供两个接口,目录文档接口和某个功能的详细接口
//生成目录接口ApiDoc apiDoc=new GeneratorApiDoc() .setInfo(//设置文档基本信息 new ApiDocInfo() .setTitle("某莫系统后台管理文档") .setVersion("1.0") .setDescription("") ) .generator(packageName);//指定生成哪个包下controller的文档 System.err.println(JsonUtil.toString(detail));//详细功能接口ApiDocAction detail=new GeneratorApiDoc() //设置数据库连接信息,可忽略 .setDriver(driver) .setUrl(url) .setUserName(userName) .setPassword(password) .setDataBaseName(dataBaseName) .getApiOfMethod(methodUUID); System.err.println(JsonUtil.toString(detail));
一个详细例子如下代码,这里是springboot/springmvc的controller示例(展示两个文档,前端接口和后台接口)参考代码这个类 UserController.java
共有6个注解,标注出整个文档信息(我为什么讲那么详细,那么啰嗦,而且我没有把这个项目打成jar包直接给别人使用,就是因为文档生成最大可能是需要特殊定制,确保你拿到该代码可以个性化定制功能,随意修改)。
Api 标注文档的功能模块
ApiAction 标注一个功能
ApiReqAparams 请求参数
ApiResqAparams 响应参数
ApiParam 参数,用以组成请求参数和响应参数
Table 用以标注实体类(比如bean)和数据库表的关系,自动从数据库读取相关信息,不用写大量的 ApiReqAparams和ApiResqAparams
详细介绍如下
Api:写在类上,表明一个功能模块。
属性:
name 模块名称
mapping url映射
ApiAction: 写在方法上,表明一个功能点
属性:
name 方法的功能名称
mapping url映射
description 描述
method 请求方式(get,post,put,delete)
ApiReqParams: 请求参数
属性:
type:参数类型
header 在请求头
url 在url后拼接
form 表单数据
json json格式
ApiParam :参数列表
value : class类,增加该类可自动读取数据库信息,避免写多个属性
remove: 配合value使用,去除class类中无用的属性,比如id
dataType: 数据类型(字符串string,数字number,文件file,日期date,对象object,数组array,布尔类型boolean)
descrption:描述
defaultValue: 默认值
required:是否必须
object:从属于哪个对象(因为请求参数或者响应参数可能是对象中嵌套对象的,这里为了更好的表示这种层级关系,增加两个属性,object和belongTo,构建一个树结构,表示对象之间无限、互相嵌套)
belognTo : 对应object 默认值为"0",字符串0
ApiRespParams: 响应参数
属性:
ApiParam: 该参数等同于请求参数中的ApiParam,参考如上描述
配置jdk8以上版本,下载代码,运行ApidocApplication类main方法即可。
然后访问地址 http://localhost:8080/index.html
该项目为maven项目,引用工具请查看 pom.xml
感谢 spring-boot
感谢@路晓磊 的工具类hutool https://gitee.com/loolly/hutool
感谢阿里fastjson
译:h4d35
预估稿费:120RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
前言
本篇文章主要介绍了在一次漏洞悬赏项目中如何利用配置错误挖到一个认证绕过漏洞。
从JS文件中发现认证绕过漏洞
本文内容源自一个私有漏洞赏金计划。在这个漏洞计划中,接受的漏洞范围限于目标网站少数几个公开的功能。基于前期发现的问题(当我被邀请进这个计划时,其他人一共提交了5个漏洞),似乎很难再挖到新的漏洞。同时,在赏金详情中提到了这样一句话:
如果你成功进入管理页面,请立即报告,请勿在/admin中进行进一步的测试。
然而,目标网站中存在一个仅限于未认证和未经授权的用户访问的管理页面。当我们访问/login或/admin时会跳转到https://bountysite.com/admin/dashboard?redirect=/。
对登录页面进行暴力破解也许是一个可行方案,但是我并不喜欢这种方式。看一下网页源码,没什么有用的内容。于是我开始查看目标网站的结构。似乎目标网站的JS文件都放在少数几个文件夹中,如/lib、/js、/application等。
有意思!
祭出神器BurpSuite,使用Intruder跑一下看能否在上述文件夹中找到任何可访问的JS文件。将攻击点设置为https://bountysite.com/admin/dashboard/js/*attack*.js。注意,不要忘记.js扩展名,这样如果文件能够访问则返回200响应。确实有意思!因为我找到了一些可访问的JS文件,其中一个文件是/login.js。
访问这个JS文件https://bountysite.com/admin/dashboard/js/login.js,请求被重定向至管理页面:) 。但是,我并没有查看该文件的权限,只能看到部分接口信息。
但是我并没有就此止步。这看起来很奇怪,为什么我访问一个.js文件却被作为HTML加载了呢?经过一番探查,终于发现,我能够访问管理页面的原因在于*login*。是的,只要在请求路径/dashboard/后的字符串中含有*login*(除了'login',这只会使我回到登录页面),请求就会跳转到这个管理接口,但是却没有正确的授权。
我继续对这个受限的管理接口进行了进一步的测试。再一次查看了页面源码,试着搞清楚网站结构。在这个管理接口中,有其他一些JS文件能够帮助我理解管理员是如何执行操作的。一些管理操作需要一个有效的令牌。我试着使用从一个JS文件中泄露的令牌执行相关管理操作,然并卵。请求还是被重定向到了登录页面。我发现另外一个真实存在的路径中也部署了一些内容,那就是/dashboard/controllers/*.php。
再一次祭出BurpSuite,使用Intruder检查一下是否存在可以从此处访问的其他任何路径。第二次Intruder的结果是,我发现几乎不存在其他无需授权即可访问的路径。这是基于服务器返回的500或者200响应得出的结论。
回到我在上一步侦察中了解到的网站结构中,我发现这些路径是在/controllers中定义的,通过/dashboard/*here*/进行访问。但是直接访问这些路径会跳转到登录页面,似乎网站对Session检查得还挺严格。此时我又累又困,几乎都打算放弃了,但是我想最后再试一把。如果我利用与访问管理页面相同的方法去执行这些管理操作会怎么样呢?很有趣,高潮来了:) 我能够做到这一点。
通过访问/dashboard/photography/loginx,请求跳转到了Admin Photography页面,并且拥有完整的权限!
从这里开始,我能够执行和访问/dashboard/*路径下的所有操作和目录,这些地方充满了诸如SQL注入、XSS、文件上传、公开重定向等漏洞。但是,我没有继续深入测试,因为这些都不在赏金计划之内,根据计划要求,一旦突破管理授权限制,应立即报告问题。此外,根据管理页面显示的调试错误信息可知,我之所以能够访问到管理页面,是因为应用程序在/dashboard/controllers/*文件中存在错误配置。期望达到的效果是:只要请求链接中出现*login*,就重定向至主登录页面,然而,实际情况并不如人所愿。
后记
总之,这是有趣的一天!我拿到了这个漏洞赏金计划最大金额的奖励。
录
前言
一、接口用户上下文的构建、使用、清除
??1. 利用Filter拦截到每一个请求
??2. 获取当前请求的线程
??3. 用户上下文生命周期管理
??4. 用户上下文的使用
??5. 用户上下文的删除
二. 用户登录&认证
作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。
考虑到文字太过寡淡,我先上一张图
在Spring Boot中,默认情况下,每个请求到达时都会分配一个单独的线程来处理,而且请求的发起人也不一定都是同一个人,所以一个请求对应一个用户上下文,并且要求线程隔离,即不同线程的用户上下文互不影响,最后用户上下文还需要随着线程的结束而删除。
本文我会从用户上下文如何构建、如何使用、如何删除这三个方面解释接口用户上下文的设计与实现。
由于接口散落在各个Controller中,且绝大部分接口都是需要这个用户上下文的(注:也不排除不需要用户上下文的接口存在),所以这里需要统一入口进行创建、销毁。看起来可以使用AOP的方式来实现,
不过这里有一个更合适的方案,利用SpringBoot自带的Filter【javax.servlet.Filter】来实现。
实现起来非常简单,我这边自定义了一个WebFilter,代码如下:
WebFilter.java
package com.summo.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.summo.context.GlobalUserContext;
import com.summo.context.UserContext;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class WebFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
try {
//获取本次接口的唯一码
String token=java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
MDC.put("requestId", token);
//获取请求头
HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest;
HttpServletResponse httpServletResponse=(HttpServletResponse)servletResponse;
log.info("当前请求链接为:[{}]", httpServletRequest.getRequestURL());
//设置用户上下文
UserContext userContext=new UserContext();
userContext.setUserId(1L);
GlobalUserContext.setUserContext(userContext);
//执行doFilter,这行一定要加,否则程序会中断掉
filterChain.doFilter(httpServletRequest, httpServletResponse);
} catch (Exception e) {
log.error("do doFilter exception", e);
} finally {
GlobalUserContext.clear();
MDC.remove("requestId");
}
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
这段代码的核心方法是:public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
我们可以在这个方法里面获取到ServletRequest和ServletResponse,这两个类能获取到代表着我们可以操作整个请求过程,这里如何确定当前请求的用户?下面有一张流程图供大家参考:
还有一种做法是使用JWT来当做用户token,因为JWT本身就可以存储一些信息,所以我们就不需要去缓存用户信息了,直接解析JWT即可,这种做法在分布式应用中很常见。
上面已经获取到用户信息了,现在需要将用户信息放入用户上下文中,但由于请求的发起人不一定都是同一个人,所以一个请求对应着一个用户上下文,也即一个线程设置一个上下文。那么这里就需要获取到当前线程才能设置上下文。
获取当前线程有很多办法,这里推荐使用阿里巴巴开源的TTL框架(TransmittableThreadLocal)来实现,功能强大且用法简单。
引入方法如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.11.1</version>
</dependency>
使用方法如下:
private static final TransmittableThreadLocal<UserContext> USER_HOLDER=new TransmittableThreadLocal<>();
直接new一个对象就行,而且支持泛型。
对于用户上下文的生命周期管理需要定义3个方法:
以上方法均为静态方法。
下面是一个简单的例子:
GlobalUserContext.java
package com.summo.context;
import com.alibaba.ttl.TransmittableThreadLocal;
public class GlobalUserContext {
private static final TransmittableThreadLocal<UserContext> USER_HOLDER=new TransmittableThreadLocal<>();
/**
* 设置上下文用户信息
*
* @param user 用户信息
*/
public static void setUserContext(UserContext user) {
USER_HOLDER.set(user);
}
/**
* 获取上下文用户信息
*/
public static UserContext getUserContext() {
return USER_HOLDER.get();
}
/**
* 清除上下文用户信息
*/
public static void clear() {
USER_HOLDER.remove();
}
}
UserContext.java
package com.summo.context;
import lombok.Data;
@Data
public class UserContext {
/**
* 用户ID
*/
private Long userId;
}
调用方式如下:
设置上下文用户信息:GlobalUserContext.setUserContext(userContext);
获取上下文用户信息:GlobalUserContext.getUserContext();
清除上下文用户信息:GlobalUserContext.clear();
获取用户上下文很方便,调用GlobalUserContext.getUserContext();就行了,这里我主要讲一下用户上下文的使用场景。
可以将用户的身份认证信息(如用户名、密码、权限等)保存在用户上下文中,在需要进行鉴权的地方进行验证。
正如《优化接口设计的思路》系列:第三篇—在用户使用系统过程中留下痕迹 的方法三.
举个例子,比如有些业务需要获取当前登录用户的信息、当前登录用户的收藏、当前登录用户的浏览记录,这样的接口总不能在接口上传一个userId吧?真要这样干了,非得给安全骂死。。。
利用用户上下文的话,接口就可以不用传递任何参数获取到当前用户的userId,实现你的需求啦。
在分布式系统中,可以将用户上下文信息传递给其他服务,以保持用户的一致性和连贯性。
可以将用户上下文中的信息用于系统的监控和统计,如请求的处理时间、请求的次数等。
删除很简单,调用GlobalUserContext.clear();即可,详情可见WebFilter.java内容。
上面主要是说怎么获取到接口请求的用户以及怎么设置用户上下文,但没说用户身份是什么时候确认的以及怎么确认的,这里说一下常见做法。
想要确认用户信息就不得不提到用户登录&认证这套东西了,登录的方式非常多,简单的有账号密码登录、手机验证码登录,复杂的就是单点登录、三方授权登录如微信扫码、支付宝扫码等。虽然方式多,但是结果都一样的:确认当前用户身份。
当前用户身份确认好之后,系统一般会根据当前用户信息生成一个唯一的并带有时效性的token,放入下一次请求的cookie中。等到下一次请求来的时候,我们就可以从cookie中获取这个token,利用这个token获取这个用户的信息。
由于用户认证情况太多,这里我就不贴代码了,上面是账号密码登录用户认证的的时序图,供大家参考。
作者:sum墨
原文链接:https://www.cnblogs.com/wlovet/p/17671747.html
*请认真填写需求信息,我们会在24小时内与您取得联系。