初步完善完毕

This commit is contained in:
dushitaoyuan
2019-08-31 17:58:49 +08:00
parent 2ab558fc3d
commit 4d886bf129
15 changed files with 156 additions and 71 deletions

View File

@@ -1,4 +1,4 @@
#web 安全常见漏洞
# web 安全常见漏洞
1. sql 注入 </br>
@@ -24,7 +24,7 @@
7.文件未授权访问
解决方案:上传的文件,展示时后端返回签名的文件,访问时,走一次后端,方便做权限验证
解决方案:上传的文件,展示时后端返回签名的文件,访问时,走一次后端,方便做权限验证,参见,/api/upload,/api/file
8.文件不安全类型上传
@@ -35,3 +35,4 @@
密码加密传输,参见login.html

View File

@@ -81,7 +81,11 @@
<artifactId>hutool-core</artifactId>
<version>4.0.3</version>
</dependency>
<!-- <dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>-->
</dependencies>
<build>

View File

@@ -28,7 +28,12 @@ public class GlobalConfig {
private String allowUploadExt;
private String tokenKey;
private Long expireSeconds;
private String fileStorageDir;
private String systemFileFormat;
public String getConfig(String configKey) {
return environment.getProperty(configKey);
}
}

View File

@@ -10,6 +10,7 @@ import com.taoyuanx.securitydemo.interceptor.RefererHandlerIntercepter;
import com.taoyuanx.securitydemo.interceptor.SimpleAuthHandlerIntercepter;
import com.taoyuanx.securitydemo.security.blacklist.BlackListIpCheck;
import com.taoyuanx.securitydemo.security.blacklist.DefaultBlackListIpCheck;
import com.taoyuanx.securitydemo.utils.FileHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -21,9 +22,12 @@ import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.nio.charset.Charset;
@@ -35,7 +39,6 @@ import java.util.List;
* @desc mvc配置
* @date 2019/8/29
*/
@EnableWebMvc
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@@ -44,16 +47,16 @@ public class MvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
if (StringUtils.isEmpty(globalConfig.getSystemDomain())) {
registry.addMapping("/**");
} else {
registry.addMapping("/**").allowedOrigins(globalConfig.getSystemDomain());
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/public/**").addResourceLocations("classpath:/public/");
registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/");
}
/**
* 拦截器
*
@@ -65,9 +68,10 @@ public class MvcConfig implements WebMvcConfigurer {
* 匹配路径按需设定
*/
registry.addInterceptor(refererHandlerIntercepter()).addPathPatterns("/**")
.excludePathPatterns("/static/**").excludePathPatterns("/public/**");
.excludePathPatterns("/**/*.css", "/**/*.html", "/**/*.js");
registry.addInterceptor(simpleAuthHandlerIntercepter()).addPathPatterns("/**")
.excludePathPatterns("/static/**").excludePathPatterns("/public/**");
.excludePathPatterns("/**/*.css", "/**/*.html", "/**/*.js", "/**/*.png")
;
}
/**
@@ -92,10 +96,12 @@ public class MvcConfig implements WebMvcConfigurer {
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON);
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastMediaTypes.add(MediaType.TEXT_PLAIN);
fastJsonConfig.setCharset(Charset.forName("UTF-8"));
fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
converters.add(fastJsonHttpMessageConverter);
converters.add(0,fastJsonHttpMessageConverter);
}
@@ -147,4 +153,10 @@ public class MvcConfig implements WebMvcConfigurer {
return blackListIpCheck;
}
@Bean
public FileHandler fileHandler(){
FileHandler fileHandler=new FileHandler(globalConfig.getFileStorageDir(),globalConfig.getTokenKey(),false,globalConfig.getSystemFileFormat());
return fileHandler;
}
}

View File

@@ -11,4 +11,12 @@ public class SystemConstants {
public static final String TOKEN_ACCOUNTID_KEY = "a";
public static final String TOKEN_ACCOUNT_STATUS_KEY = "as";
public static final String TOKEN_COOKIE_KEY = "_t";
/**
* 文件签名相关
*/
public static final String REQUEST_PARAM_FILE_KEY = "p";
public static final String REQUEST_PARAM_TYPE_KEY = "t";
public static final String REQUEST_PARAM_TOKEN_KEY = "s";
}

View File

@@ -1,15 +1,17 @@
package com.taoyuanx.securitydemo.controller;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.date.DateUtil;
import com.taoyuanx.securitydemo.common.Result;
import com.taoyuanx.securitydemo.common.ResultBuilder;
import com.taoyuanx.securitydemo.config.GlobalConfig;
import com.taoyuanx.securitydemo.constant.SystemConstants;
import com.taoyuanx.securitydemo.dto.AccountDTO;
import com.taoyuanx.securitydemo.exception.ServiceException;
import com.taoyuanx.securitydemo.helper.ToeknHelper;
import com.taoyuanx.securitydemo.security.*;
import com.taoyuanx.securitydemo.utils.FileHandler;
import com.taoyuanx.securitydemo.utils.FileTypeCheckUtil;
import com.taoyuanx.securitydemo.utils.PasswordUtil;
import com.taoyuanx.securitydemo.utils.SimpleTokenManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
@@ -17,10 +19,13 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author dushitaoyuan
@@ -34,8 +39,15 @@ public class BussinessController {
ToeknHelper toeknHelper;
@Value("${server.servlet.session.timeout}")
Integer sessionTimeOut;
@Autowired
GlobalConfig globalConfig;
@Autowired
FileHandler fileHandler;
/**
* 管理员访问
*
* @return
*/
@GetMapping("admin")
@@ -44,8 +56,10 @@ public class BussinessController {
public String admin() {
return "hello admin!";
}
/**
* 普通用户访问
*
* @return
*/
@GetMapping("commonUser")
@@ -57,6 +71,7 @@ public class BussinessController {
/**
* 公开访问
*
* @return
*/
@GetMapping("public")
@@ -67,6 +82,7 @@ public class BussinessController {
/**
* 必须携带Rerferer头部
*
* @return
*/
@GetMapping("refererCheck")
@@ -78,23 +94,25 @@ public class BussinessController {
/**
* 限流: 单个ip每秒一次,
* 自定义key 每秒10次
*
* @return
*/
@GetMapping("rateLimit")
@ResponseBody
@Rate(rate = {@RateLimit(type = RateLimitType.IP,limit = 1),
@RateLimit(type = RateLimitType.SERVICE_KEY,limit = 10,key = "api/rateLimit")})
@Rate(rate = {@RateLimit(type = RateLimitType.IP, limit = 1),
@RateLimit(type = RateLimitType.SERVICE_KEY, limit = 10, key = "api/rateLimit")})
public String rateLimit() {
return "hello rateLimit!";
}
/**
* 限流: 每秒10次,
*
* @return
*/
@GetMapping("rateLimit_key")
@ResponseBody
@RateLimit(type = RateLimitType.SERVICE_KEY,limit = 2,key = "api/rateLimit_key")
@RateLimit(type = RateLimitType.SERVICE_KEY, limit = 2, key = "api/rateLimit_key")
public String rateLimitKey() {
return "hello rateLimit!";
}
@@ -102,6 +120,7 @@ public class BussinessController {
/**
* 黑名单测试
*
* @return
*/
@GetMapping("blackList")
@@ -111,48 +130,66 @@ public class BussinessController {
}
/**
* 登录安全控制
*
* @return
*/
@PostMapping("login")
@ResponseBody
public String login(String userName, String password, HttpServletResponse response) throws Exception {
public Result login(String userName, String password, HttpServletResponse response) throws Exception {
/**
* select from db
*/
if(userName.equals("dushitaoyuan")&& PasswordUtil.isPasswordEqual(password,PasswordUtil.passwordHanlePlain("123456"))){
AccountDTO accountDTO=new AccountDTO();
if (userName.equals("dushitaoyuan") && PasswordUtil.isPasswordEqual(password, PasswordUtil.passwordHanlePlain("123456"))) {
AccountDTO accountDTO = new AccountDTO();
accountDTO.setAccountId(1L);
accountDTO.setAccountStatus(Role.ADMIN.getAccountStatus());
String token = toeknHelper.create(accountDTO);
Cookie cookie=new Cookie(SystemConstants.TOKEN_COOKIE_KEY,token);
Cookie cookie = new Cookie(SystemConstants.TOKEN_COOKIE_KEY, token);
cookie.setPath("/");
cookie.setMaxAge(sessionTimeOut*2);
cookie.setMaxAge(sessionTimeOut * 2);
response.addCookie(cookie);
return ResultBuilder.success();
}
return "hello upload!";
throw new ServiceException("登陆异常");
}
/**
* 黑名单测试
*
* @return
*/
@GetMapping("upload")
@PostMapping("upload")
@ResponseBody
public String upload(@RequestParam("file") MultipartFile multipartFile,@CookieValue(name=SystemConstants.TOKEN_COOKIE_KEY) String token) throws Exception {
public Map<String, String> upload(@RequestParam("file") MultipartFile multipartFile, @CookieValue(name = SystemConstants.TOKEN_COOKIE_KEY) String token) throws Exception {
toeknHelper.vafy(token);
String ext=FileTypeCheckUtil.getType(multipartFile.getOriginalFilename());
if(!FileTypeCheckUtil.allow(ext)){
String ext = FileTypeCheckUtil.getType(multipartFile.getOriginalFilename());
if (!FileTypeCheckUtil.allow(ext)) {
throw new ServiceException("文件上传失败,类型不支持");
}
ext=FileTypeCheckUtil.getRealType(multipartFile.getInputStream());
if(!FileTypeCheckUtil.allow(ext)){
ext = FileTypeCheckUtil.getRealType(multipartFile.getInputStream());
if (!FileTypeCheckUtil.allow(ext)) {
throw new ServiceException("文件上传失败,类型不支持");
}
return "hello upload!";
/**
* 文件签名后返回前端,5分钟过期
*/
String fileId = DateUtil.format(new Date(), "yyyy-mm-dd") + "/" + multipartFile.getOriginalFilename();
File file = new File(globalConfig.getFileStorageDir(), fileId);
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
multipartFile.transferTo(file);
Map<String, String> result = new HashMap<>();
result.put("path", fileId);
result.put("url", fileHandler.signFileUrl(fileId, FileHandler.LOOK, 5L, TimeUnit.MINUTES));
return result;
}
@GetMapping("file")
public void upload(HttpServletRequest request, HttpServletResponse response) throws Exception {
fileHandler.handleFile(response, request);
}
}

View File

@@ -1,6 +1,8 @@
package com.taoyuanx.securitydemo.controller;
import com.taoyuanx.securitydemo.common.ResultBuilder;
import com.taoyuanx.securitydemo.exception.SystemExceptionHandler;
import com.taoyuanx.securitydemo.utils.ResponseUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
@@ -11,6 +13,7 @@ import org.springframework.web.context.request.WebRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
@@ -39,7 +42,15 @@ public class SystemErrorController implements ErrorController {
public void doHandleError(HttpServletRequest request, HttpServletResponse response) {
WebRequest webRequest = new ServletWebRequest(request, response);
Throwable e = errorAttributes.getError(webRequest);
if (e != null) {
systemExceptionHandler.doHandleException(request, response, null, e);
} else {
Map<String, Object> errorAttributes = this.errorAttributes.getErrorAttributes(webRequest, false);
String errorMsg = String.format("%s %s", errorAttributes.get("path"), errorAttributes.get("error"));
Integer status = (Integer) errorAttributes.get("status");
ResponseUtil.responseJson(response, ResultBuilder.failed(errorMsg), status);
}
}

View File

@@ -8,6 +8,7 @@ import com.taoyuanx.securitydemo.utils.ResponseUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
@@ -31,8 +32,9 @@ public class SystemExceptionHandler implements HandlerExceptionResolver {
doHandleException(request, response, handler, e);
return new ModelAndView();
} else {
ModelAndView modelAndView=new ModelAndView();
//todo跳转到页面
return new ModelAndView();
return modelAndView;
}
}
@@ -78,9 +80,9 @@ public class SystemExceptionHandler implements HandlerExceptionResolver {
public static boolean isJson(HttpServletRequest request) {
String header = request.getHeader("Content-Type");
String contentType = request.getHeader("Content-Type");
String accept = request.getHeader("Accept");
if ((header != null && header.contains("application/json")) || (accept != null && accept.contains("application/json"))) {
if ((accept != null && accept.contains("json"))||(contentType != null && contentType.contains("json"))) {
return true;
} else {
return false;

View File

@@ -33,7 +33,7 @@ public class FileTypeCheckUtil {
}
public static String getType(String fileName) {
return fileName.substring(fileName.lastIndexOf("."));
return fileName.substring(fileName.lastIndexOf(".")+1);
}
public static String getRealType(InputStream inputStream) {

View File

@@ -30,7 +30,7 @@ public class PasswordUtil {
}
public static boolean isPasswordEqual(String passwordHash, String dbPasswordHash) {
if (passwordHash != null) {
if (passwordHash == null) {
return false;
}
return passwordHash.equals(dbPasswordHash);

View File

@@ -22,6 +22,15 @@ public class ResponseUtil {
throw new RuntimeException(ex);
}
}
public static void responseJson(HttpServletResponse response, Object result, Integer httpStatus) {
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setStatus(httpStatus);
try {
response.getWriter().write(JSON.toJSONString(result));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}

View File

@@ -17,7 +17,7 @@ server.servlet.session.timeout=1800
#自定义配置
#系统域名
application.config.systemDoamin=taoyuanx.com
application.config.systemDoamin=
#referer校验配置
application.config.referer-check-url[0]=refererCheck
application.config.referer-check-allow-domains=taoyuanx.com
@@ -29,3 +29,7 @@ application.config.allowUploadExt=bmp,jpg,png,tif,gif,pcx,tga,exif,fpx,svg,psd,c
#系统颁发token秘钥hmac算法的key
application.config.token-key=123456
application.config.expire-seconds=1800
#文件存储地址
application.config.file-storage-dir=G:/temp
#系统文件访问地址,参见fileHandler
application.config.systemFileFormat=http://localhost:${server.port}/api/file

View File

@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
</body>
</html>

View File

@@ -13,12 +13,12 @@
</body>
<script src="/static/js/crypt/core-min.js"></script>
<script src="/static/js/crypt/enc-base64-min.js"></script>
<script src="/static/js/crypt/sha256-min.js"></script>
<script src="/static/js/jquery-2.1.1.min.js"></script>
<script src="/static/js/ajaxhook.min.js"></script>
<script src="/static/js/common/system.js"></script>
<script src="/js/crypt/core-min.js"></script>
<script src="/js/crypt/enc-base64-min.js"></script>
<script src="/js/crypt/sha256-min.js"></script>
<script src="/js/jquery-2.1.1.min.js"></script>
<script src="/js/ajaxhook.min.js"></script>
<script src="/js/common/system.js"></script>
<script type="text/javascript">
function upload() {
var password = $("input[name='password']").val();
@@ -40,7 +40,7 @@
}).success(function (result) {
if (result.success == 1) {
alert("登录成功");
window.location.href = getStaticUrl("public/index.html");
window.location.href = getStaticUrl("upload.html");
} else {
alert(result.msg);
}

View File

@@ -6,11 +6,13 @@
</head>
<body>
<input name="file" type="file" data-allow="jpg,jpeg">
<input type="button" value="上传">
<script src="/static/js/jquery-2.1.1.min.js"/>
<script src="/static/js/ajaxhook.min.js"/>
<input type="button" value="上传" onclick="upload()">
<img src="" style="display: none" id="showImg">
</body>
<script src="/js/jquery-2.1.1.min.js"></script>
<script src="/js/ajaxhook.min.js"></script>
<script src="/static/js/common/system.js"/>
<script src="/js/common/system.js"></script>
<script type="text/javascript">
function upload() {
var formData = new FormData();
@@ -23,12 +25,14 @@
xhrFields: {
withCredentials: true
},
cache: false,
processData: false,
contentType: false
contentType: false,
headers:{"Accept":"application/json"}
}).success(function (result) {
if (result.status == 1) {
if (result.success == 1) {
alert("上传成功");
$("#showImg").attr("src",result.data.url);
$("#showImg").show();
} else {
alert(result.msg);
}
@@ -36,5 +40,4 @@
});
}
</script>
</body>
</html>