初始提交
This commit is contained in:
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**
|
||||
!**/src/test/**
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
37
Readme.md
Normal file
37
Readme.md
Normal file
@@ -0,0 +1,37 @@
|
||||
#web 安全常见漏洞
|
||||
|
||||
1. sql 注入 </br>
|
||||
|
||||
解决方案:参数检测,拦截非法入参,后端使用druid 连接池的sql防火墙
|
||||
参见:com.taoyuanx.securitydemo.web
|
||||
|
||||
2.xss攻击 </br>
|
||||
|
||||
解决方案:参数检测,拦截非法入参,转义html字符,参见com.taoyuanx.securitydemo.web
|
||||
|
||||
</br>
|
||||
|
||||
3.跨站请求
|
||||
解决方案:设置允许跨站的header信息
|
||||
|
||||
4.文件,图片等非站内请求
|
||||
|
||||
解决方案:防盗链处理(Http Rerfer头部,nginx可设置)
|
||||
|
||||
5.接口暴力访问
|
||||
|
||||
解决方案:系统限流,全局限流,单个ip限流,黑名单等,参见:com.taoyuanx.securitydemo.security.RateLimitAspect,com.taoyuanx.securitydemo.web.BlackListFilter
|
||||
|
||||
7.文件未授权访问
|
||||
|
||||
解决方案:上传的文件,展示时后端返回签名的文件,访问时,走一次后端,方便做权限验证
|
||||
|
||||
|
||||
8.文件不安全类型上传
|
||||
|
||||
解决方案:校验文件类型,校验流信息,校验文件真实类型,参见/api/upload
|
||||
|
||||
9密码泄露风险
|
||||
|
||||
密码加密传输,参见login.html
|
||||
|
||||
122
pom.xml
Normal file
122
pom.xml
Normal file
@@ -0,0 +1,122 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.7.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.taoyuanx</groupId>
|
||||
<artifactId>security-demo</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>security-demo</name>
|
||||
<description>java security demo</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<skipTests>true</skipTests>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<!– <optional>true</optional>–>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>20.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.59</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>1.10</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.8</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>4.0.3</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<!-- 加入该配置,使用maven package,依赖jar不会打包在项目最终jar文件内
|
||||
启动jar 加入参数 -Dthin.root=. -jar
|
||||
-->
|
||||
<groupId>org.springframework.boot.experimental</groupId>
|
||||
<artifactId>spring-boot-thin-layout</artifactId>
|
||||
<version>1.0.12.RELEASE</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<!-- 加入该配置,maven package执行时会在target目录整理好依赖包 -->
|
||||
<groupId>org.springframework.boot.experimental</groupId>
|
||||
<artifactId>spring-boot-thin-maven-plugin</artifactId>
|
||||
<version>1.0.12.RELEASE</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>resolve</id>
|
||||
<goals>
|
||||
<goal>resolve</goal>
|
||||
</goals>
|
||||
<inherited>false</inherited>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.taoyuanx.securitydemo;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
@SpringBootApplication
|
||||
@ServletComponentScan("com.taoyuanx.securitydemo.web")
|
||||
class SecurityDemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SecurityDemoApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
55
src/main/java/com/taoyuanx/securitydemo/common/Result.java
Normal file
55
src/main/java/com/taoyuanx/securitydemo/common/Result.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package com.taoyuanx.securitydemo.common;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一返回结果
|
||||
* <p>
|
||||
* errorCode 错误码
|
||||
* msg 错误消息
|
||||
* success 请求成功标识 1成功,0失败
|
||||
* data 结果
|
||||
* ext 扩展信息
|
||||
*/
|
||||
@Data
|
||||
public class Result implements Serializable {
|
||||
private Integer errorCode;
|
||||
private String msg;
|
||||
private Integer success;
|
||||
private Object data;
|
||||
private Object ext;
|
||||
|
||||
public static Result build() {
|
||||
return new Result();
|
||||
}
|
||||
|
||||
public Result buildCode(Integer code) {
|
||||
this.setErrorCode(code);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Result buildMsg(String msg) {
|
||||
this.setMsg(msg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Result buildSuccess(Integer success) {
|
||||
this.setSuccess(success);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Result buildData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public Result buildExt(Object ext) {
|
||||
this.ext = ext;
|
||||
return this;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.taoyuanx.securitydemo.common;
|
||||
|
||||
/**
|
||||
* 结果构造
|
||||
*/
|
||||
public class ResultBuilder {
|
||||
|
||||
public static Result success(Object data) {
|
||||
return Result.build().buildData(data).buildSuccess(ResultCode.OK.code);
|
||||
}
|
||||
|
||||
public static Result success() {
|
||||
return Result.build().buildSuccess(ResultCode.OK.code);
|
||||
}
|
||||
|
||||
public static Result success(String msg) {
|
||||
return Result.build().buildSuccess(ResultCode.OK.code).buildMsg(msg);
|
||||
}
|
||||
|
||||
|
||||
public static Result failed(String msg) {
|
||||
return Result.build().buildSuccess(ResultCode.FAIL.code).buildMsg(msg);
|
||||
}
|
||||
|
||||
|
||||
public static Result failed(Integer code, String msg) {
|
||||
return Result.build().buildSuccess(ResultCode.FAIL.code)
|
||||
.buildCode(code).buildMsg(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.taoyuanx.securitydemo.common;
|
||||
|
||||
/**
|
||||
* 响应码枚举,参考HTTP状态码的语义
|
||||
*/
|
||||
public enum ResultCode {
|
||||
OK(1, "success"),
|
||||
FAIL(0, "failed"),
|
||||
UNAUTHORIZED(401, "权限异常"),
|
||||
NOT_FOUND(404, "service not found"),
|
||||
UN_SUPPORT_MEDIATYPE(415, "不支持媒体类型"),
|
||||
PARAM_ERROR(400, "参数异常"),
|
||||
TOO_MANY_REQUESTS(429, "请求被限制"),
|
||||
INTERNAL_SERVER_ERROR(500, "服务器内部错误"),
|
||||
BUSSINESS_ERROR(1001, "业务异常");
|
||||
public int code;
|
||||
public String desc;
|
||||
private ResultCode(int code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.taoyuanx.securitydemo.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 系统全局配置
|
||||
* @date 2019/8/29
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties("application.config")
|
||||
@Configuration
|
||||
public class GlobalConfig {
|
||||
@Autowired
|
||||
Environment environment;
|
||||
|
||||
private String systemDomain;
|
||||
private List<String> refererCheckUrl;
|
||||
private List<String> refererCheckAllowDomains;
|
||||
|
||||
private List<String> blackListIp;
|
||||
private String allowUploadExt;
|
||||
private String tokenKey;
|
||||
private Long expireSeconds;
|
||||
public String getConfig(String configKey) {
|
||||
return environment.getProperty(configKey);
|
||||
}
|
||||
}
|
||||
150
src/main/java/com/taoyuanx/securitydemo/config/MvcConfig.java
Normal file
150
src/main/java/com/taoyuanx/securitydemo/config/MvcConfig.java
Normal file
@@ -0,0 +1,150 @@
|
||||
package com.taoyuanx.securitydemo.config;
|
||||
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson.support.config.FastJsonConfig;
|
||||
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
|
||||
import com.taoyuanx.securitydemo.common.Result;
|
||||
import com.taoyuanx.securitydemo.common.ResultBuilder;
|
||||
import com.taoyuanx.securitydemo.exception.SystemExceptionHandler;
|
||||
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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.MediaType;
|
||||
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.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.config.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc mvc配置
|
||||
* @date 2019/8/29
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@Configuration
|
||||
public class MvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
GlobalConfig globalConfig;
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
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/");
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截器
|
||||
*
|
||||
* @param registry
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
/**
|
||||
* 匹配路径按需设定
|
||||
*/
|
||||
registry.addInterceptor(refererHandlerIntercepter()).addPathPatterns("/**")
|
||||
.excludePathPatterns("/static/**").excludePathPatterns("/public/**");
|
||||
registry.addInterceptor(simpleAuthHandlerIntercepter()).addPathPatterns("/**")
|
||||
.excludePathPatterns("/static/**").excludePathPatterns("/public/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常处理
|
||||
*
|
||||
* @param resolvers
|
||||
*/
|
||||
@Override
|
||||
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
|
||||
resolvers.add(systemExceptionHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息转换
|
||||
*
|
||||
* @param converters
|
||||
*/
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
|
||||
FastJsonConfig fastJsonConfig = new FastJsonConfig();
|
||||
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
|
||||
List<MediaType> fastMediaTypes = new ArrayList<>();
|
||||
fastMediaTypes.add(MediaType.APPLICATION_JSON);
|
||||
fastJsonConfig.setCharset(Charset.forName("UTF-8"));
|
||||
fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
|
||||
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
|
||||
converters.add(fastJsonHttpMessageConverter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 统一结果处理
|
||||
*/
|
||||
@RestControllerAdvice(basePackages = "com.taoyuanx.securitydemo.controller")
|
||||
public static class ResponseHandler implements ResponseBodyAdvice<Object> {
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||
if (body instanceof Result) {
|
||||
return body;
|
||||
}
|
||||
if (body instanceof ResponseEntity) {
|
||||
return body;
|
||||
}
|
||||
return ResultBuilder.success(body);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public SystemExceptionHandler systemExceptionHandler() {
|
||||
return new SystemExceptionHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RefererHandlerIntercepter refererHandlerIntercepter() {
|
||||
RefererHandlerIntercepter refererHandlerIntercepter = new RefererHandlerIntercepter(globalConfig.getRefererCheckUrl(), globalConfig.getRefererCheckAllowDomains());
|
||||
return refererHandlerIntercepter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SimpleAuthHandlerIntercepter simpleAuthHandlerIntercepter() {
|
||||
SimpleAuthHandlerIntercepter refererHandlerIntercepter = new SimpleAuthHandlerIntercepter();
|
||||
return refererHandlerIntercepter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BlackListIpCheck blackListIpCheck() {
|
||||
BlackListIpCheck blackListIpCheck = new DefaultBlackListIpCheck(globalConfig.getBlackListIp());
|
||||
return blackListIpCheck;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.taoyuanx.securitydemo.constant;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 系统常量
|
||||
* @date 2019/8/29
|
||||
*/
|
||||
public class SystemConstants {
|
||||
public static final String ACCOUNT_SESSION_KEY = "s";
|
||||
|
||||
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";
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.taoyuanx.securitydemo.controller;
|
||||
|
||||
import cn.hutool.core.io.FileTypeUtil;
|
||||
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.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;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 业务api
|
||||
* @date 2019/8/29
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping(value = "api")
|
||||
public class BussinessController {
|
||||
@Autowired
|
||||
ToeknHelper toeknHelper;
|
||||
@Value("${server.servlet.session.timeout}")
|
||||
Integer sessionTimeOut;
|
||||
/**
|
||||
* 管理员访问
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("admin")
|
||||
@ResponseBody
|
||||
@RequireRole(role = {Role.ADMIN})
|
||||
public String admin() {
|
||||
return "hello admin!";
|
||||
}
|
||||
/**
|
||||
* 普通用户访问
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("commonUser")
|
||||
@ResponseBody
|
||||
@RequireRole(role = {Role.COMMONUSER})
|
||||
public String commonUser() {
|
||||
return "hello commonUser!";
|
||||
}
|
||||
|
||||
/**
|
||||
* 公开访问
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("public")
|
||||
@ResponseBody
|
||||
public String publicM() {
|
||||
return "hello anybody!";
|
||||
}
|
||||
|
||||
/**
|
||||
* 必须携带Rerferer头部
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("refererCheck")
|
||||
@ResponseBody
|
||||
public String refererCheck() {
|
||||
return "hello refererCheck!";
|
||||
}
|
||||
|
||||
/**
|
||||
* 限流: 单个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")})
|
||||
public String rateLimit() {
|
||||
return "hello rateLimit!";
|
||||
}
|
||||
|
||||
/**
|
||||
* 限流: 每秒10次,
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("rateLimit_key")
|
||||
@ResponseBody
|
||||
@RateLimit(type = RateLimitType.SERVICE_KEY,limit = 2,key = "api/rateLimit_key")
|
||||
public String rateLimitKey() {
|
||||
return "hello rateLimit!";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 黑名单测试
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("blackList")
|
||||
@ResponseBody
|
||||
public String blackList() {
|
||||
return "hello blackList!";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 登录安全控制
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("login")
|
||||
@ResponseBody
|
||||
public String 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();
|
||||
accountDTO.setAccountId(1L);
|
||||
accountDTO.setAccountStatus(Role.ADMIN.getAccountStatus());
|
||||
String token = toeknHelper.create(accountDTO);
|
||||
Cookie cookie=new Cookie(SystemConstants.TOKEN_COOKIE_KEY,token);
|
||||
cookie.setPath("/");
|
||||
cookie.setMaxAge(sessionTimeOut*2);
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
return "hello upload!";
|
||||
}
|
||||
|
||||
/**
|
||||
* 黑名单测试
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("upload")
|
||||
@ResponseBody
|
||||
public 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)){
|
||||
throw new ServiceException("文件上传失败,类型不支持");
|
||||
}
|
||||
ext=FileTypeCheckUtil.getRealType(multipartFile.getInputStream());
|
||||
if(!FileTypeCheckUtil.allow(ext)){
|
||||
throw new ServiceException("文件上传失败,类型不支持");
|
||||
}
|
||||
return "hello upload!";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.taoyuanx.securitydemo.controller;
|
||||
|
||||
import com.taoyuanx.securitydemo.exception.SystemExceptionHandler;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.servlet.error.ErrorAttributes;
|
||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 用途描述
|
||||
* @date 2019/8/30
|
||||
**/
|
||||
|
||||
@Controller
|
||||
@RequestMapping("${server.error.path:${error.path:/error}}")
|
||||
public class SystemErrorController implements ErrorController {
|
||||
private static final String PATH = "/error";
|
||||
|
||||
@Autowired
|
||||
ErrorAttributes errorAttributes;
|
||||
@Autowired
|
||||
SystemExceptionHandler systemExceptionHandler;
|
||||
|
||||
@Override
|
||||
public String getErrorPath() {
|
||||
return PATH;
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping
|
||||
public void doHandleError(HttpServletRequest request, HttpServletResponse response) {
|
||||
WebRequest webRequest = new ServletWebRequest(request, response);
|
||||
Throwable e = errorAttributes.getError(webRequest);
|
||||
systemExceptionHandler.doHandleException(request, response, null, e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
17
src/main/java/com/taoyuanx/securitydemo/dto/AccountDTO.java
Normal file
17
src/main/java/com/taoyuanx/securitydemo/dto/AccountDTO.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.taoyuanx.securitydemo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 账户实体
|
||||
* @date 2019/8/29
|
||||
*/
|
||||
@Data
|
||||
public class AccountDTO implements Serializable {
|
||||
private Long accountId;
|
||||
private Integer accountStatus;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.taoyuanx.securitydemo.exception;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 限制访问异常 http状态码 429
|
||||
* @date 2019/8/26
|
||||
*/
|
||||
public class RadioLimitException extends RuntimeException {
|
||||
public RadioLimitException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.taoyuanx.securitydemo.exception;
|
||||
|
||||
|
||||
import com.taoyuanx.securitydemo.common.ResultCode;
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*/
|
||||
public class ServiceException extends RuntimeException {
|
||||
private static final long serialVersionUID = 8793672380339632040L;
|
||||
private Integer errorCode;
|
||||
public ServiceException(String msg) {
|
||||
super(msg);
|
||||
this.errorCode= ResultCode.BUSSINESS_ERROR.code;
|
||||
}
|
||||
public ServiceException(Integer errorCode,String msg) {
|
||||
super(msg);
|
||||
this.errorCode=errorCode;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.taoyuanx.securitydemo.exception;
|
||||
|
||||
import com.alibaba.fastjson.JSONException;
|
||||
import com.taoyuanx.securitydemo.common.Result;
|
||||
import com.taoyuanx.securitydemo.common.ResultBuilder;
|
||||
import com.taoyuanx.securitydemo.common.ResultCode;
|
||||
import com.taoyuanx.securitydemo.utils.ResponseUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
public class SystemExceptionHandler implements HandlerExceptionResolver {
|
||||
public static final Logger LOG = LoggerFactory.getLogger(SystemExceptionHandler.class);
|
||||
|
||||
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||
Exception e) {
|
||||
|
||||
if (isJson(request)) {
|
||||
doHandleException(request, response, handler, e);
|
||||
return new ModelAndView();
|
||||
} else {
|
||||
//todo跳转到页面
|
||||
return new ModelAndView();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void doHandleException(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||
Throwable e) {
|
||||
Result result = null;
|
||||
HttpStatus httpStatus = HttpStatus.OK;
|
||||
if (e instanceof ValidatorException) {//参数异常
|
||||
result = ResultBuilder.failed(ResultCode.PARAM_ERROR.code, e.getMessage());
|
||||
} else if (e instanceof ServiceException) {
|
||||
result = ResultBuilder.failed(ResultCode.BUSSINESS_ERROR.code, e.getMessage());
|
||||
} else if (e instanceof UnAuthException) {
|
||||
httpStatus = HttpStatus.UNAUTHORIZED;
|
||||
result = ResultBuilder.failed(ResultCode.UNAUTHORIZED.code, e.getMessage());
|
||||
} else if (e instanceof HttpMediaTypeNotSupportedException) {
|
||||
HttpMediaTypeNotSupportedException mediaEx = (HttpMediaTypeNotSupportedException) e;
|
||||
result = ResultBuilder.failed(ResultCode.UN_SUPPORT_MEDIATYPE.code, "不支持该媒体类型:" + mediaEx.getContentType());
|
||||
} else if (e instanceof RadioLimitException) {
|
||||
httpStatus = HttpStatus.TOO_MANY_REQUESTS;
|
||||
result = ResultBuilder.failed(ResultCode.TOO_MANY_REQUESTS.code, e.getMessage());
|
||||
} else if (e instanceof JSONException) {
|
||||
result = ResultBuilder.failed(ResultCode.PARAM_ERROR.code, "参数异常,json格式非法:" + e.getMessage());
|
||||
} else if (e instanceof ServletException) {
|
||||
result = ResultBuilder.failed(e.getMessage());
|
||||
} else if (e instanceof NoHandlerFoundException) {
|
||||
httpStatus = HttpStatus.NOT_FOUND;
|
||||
result = ResultBuilder.failed(ResultCode.NOT_FOUND.code, "接口 [" + ((NoHandlerFoundException) e).getRequestURL() + "] 不存在");
|
||||
} else {
|
||||
result = ResultBuilder.failed(ResultCode.INTERNAL_SERVER_ERROR.code, "接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
|
||||
if (handler != null && handler instanceof HandlerMethod) {
|
||||
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
||||
LOG.error("接口 [{}] 出现异常,方法:{}.{},异常摘要:{}", request.getRequestURI(),
|
||||
handlerMethod.getBean().getClass().getName(),
|
||||
handlerMethod.getMethod().getName(),
|
||||
e.getMessage());
|
||||
}
|
||||
LOG.error("系统未知异常,异常信息:", e);
|
||||
}
|
||||
ResponseUtil.responseJson(response, result, httpStatus);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isJson(HttpServletRequest request) {
|
||||
String header = request.getHeader("Content-Type");
|
||||
String accept = request.getHeader("Accept");
|
||||
if ((header != null && header.contains("application/json")) || (accept != null && accept.contains("application/json"))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.taoyuanx.securitydemo.exception;
|
||||
|
||||
/**
|
||||
* 权限异常 http状态码401
|
||||
*/
|
||||
public class UnAuthException extends RuntimeException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public UnAuthException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.taoyuanx.securitydemo.exception;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 参数异常
|
||||
* @date 2019/8/29
|
||||
*/
|
||||
public class ValidatorException extends RuntimeException {
|
||||
public ValidatorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.taoyuanx.securitydemo.helper;
|
||||
|
||||
import com.taoyuanx.securitydemo.config.GlobalConfig;
|
||||
import com.taoyuanx.securitydemo.constant.SystemConstants;
|
||||
import com.taoyuanx.securitydemo.dto.AccountDTO;
|
||||
import com.taoyuanx.securitydemo.utils.SimpleTokenManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc token签发帮助类
|
||||
* @date 2019/8/30
|
||||
*/
|
||||
@Component
|
||||
public class ToeknHelper {
|
||||
@Autowired
|
||||
GlobalConfig globalConfig;
|
||||
|
||||
public String create(AccountDTO accountDTO) {
|
||||
Map<String, Object> tokenData = new HashMap<>();
|
||||
tokenData.put(SystemConstants.TOKEN_ACCOUNTID_KEY, String.valueOf(accountDTO.getAccountId()));
|
||||
tokenData.put(SystemConstants.TOKEN_ACCOUNT_STATUS_KEY, String.valueOf(accountDTO.getAccountStatus()));
|
||||
return SimpleTokenManager.createToken(globalConfig.getTokenKey(), tokenData, globalConfig.getExpireSeconds(), TimeUnit.SECONDS);
|
||||
}
|
||||
public Map<String, Object> vafy(String token) {
|
||||
return SimpleTokenManager.vafy(globalConfig.getTokenKey(),token);
|
||||
}
|
||||
|
||||
public Long getAccountId(Map<String, Object> tokenData) {
|
||||
return Long.parseLong(tokenData.get(SystemConstants.TOKEN_ACCOUNTID_KEY).toString());
|
||||
}
|
||||
|
||||
public Integer getAccountStatus(Map<String, Object> tokenData) {
|
||||
return (Integer) tokenData.get(SystemConstants.TOKEN_ACCOUNT_STATUS_KEY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.taoyuanx.securitydemo.interceptor;
|
||||
|
||||
import com.taoyuanx.securitydemo.exception.UnAuthException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* referer校验拦截器
|
||||
*/
|
||||
|
||||
public class RefererHandlerIntercepter implements HandlerInterceptor {
|
||||
Logger LOG = LoggerFactory.getLogger(RefererHandlerIntercepter.class);
|
||||
private List<String> refererCheckUrl;
|
||||
private List<String> allowDomains;
|
||||
|
||||
public RefererHandlerIntercepter(List<String> refererCheckUrl, List<String> allowDomains) {
|
||||
this.refererCheckUrl = refererCheckUrl;
|
||||
this.allowDomains = allowDomains;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
throws Exception {
|
||||
if (refererCheckUrl == null) {
|
||||
return true;
|
||||
}
|
||||
String requestUrl = request.getRequestURI();
|
||||
for (String checkUrl : refererCheckUrl) {
|
||||
if (requestUrl.contains(checkUrl)) {
|
||||
String referer = request.getHeader("Referer");
|
||||
if (referer == null) {
|
||||
LOG.debug("请求url[{}],未携带有效header:[Referer]", requestUrl);
|
||||
throw new UnAuthException("操作非法");
|
||||
}
|
||||
if (!checkDomain(referer)) {
|
||||
LOG.debug("请求url[{}],Referer->[{}]不在允许范围内", requestUrl, referer);
|
||||
throw new UnAuthException("操作非法");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private boolean checkDomain(String referer) {
|
||||
for (String domain : allowDomains) {
|
||||
if (referer.contains(domain)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.taoyuanx.securitydemo.interceptor;
|
||||
|
||||
import com.taoyuanx.securitydemo.constant.SystemConstants;
|
||||
import com.taoyuanx.securitydemo.dto.AccountDTO;
|
||||
import com.taoyuanx.securitydemo.exception.UnAuthException;
|
||||
import com.taoyuanx.securitydemo.security.RequireRole;
|
||||
import com.taoyuanx.securitydemo.security.Role;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 简单权限校验
|
||||
*/
|
||||
public class SimpleAuthHandlerIntercepter implements HandlerInterceptor {
|
||||
Logger LOG = LoggerFactory.getLogger(SimpleAuthHandlerIntercepter.class);
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
throws Exception {
|
||||
if (handler instanceof HandlerMethod) {
|
||||
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
||||
AccountDTO account = getAccount(request);
|
||||
RequireRole requireRole = getAuthority(handlerMethod, RequireRole.class);
|
||||
Integer accountStatus = account == null ? null : account.getAccountStatus();
|
||||
if (Role.hasRole(requireRole, accountStatus)) {
|
||||
return true;
|
||||
}
|
||||
LOG.debug("请求url[{}],权限不足,已拦截", request.getRequestURI());
|
||||
throw new UnAuthException("操作");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private AccountDTO getAccount(HttpServletRequest request) {
|
||||
Object account = request.getSession().getAttribute(SystemConstants.ACCOUNT_SESSION_KEY);
|
||||
if (account == null) {
|
||||
return null;
|
||||
}
|
||||
return (AccountDTO) account;
|
||||
}
|
||||
|
||||
private <T> T getAuthority(HandlerMethod handlerMethod, Class annotationType) {
|
||||
if (handlerMethod == null) {
|
||||
return null;
|
||||
}
|
||||
T methodAnno = (T) AnnotationUtils.findAnnotation(handlerMethod.getMethod(), annotationType);
|
||||
if (methodAnno == null) {
|
||||
return (T) AnnotationUtils.findAnnotation(handlerMethod.getBean().getClass(), annotationType);
|
||||
} else {
|
||||
return methodAnno;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.taoyuanx.securitydemo.interceptor;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.taoyuanx.securitydemo.constant.SystemConstants;
|
||||
import com.taoyuanx.securitydemo.exception.UnAuthException;
|
||||
import com.taoyuanx.securitydemo.helper.ToeknHelper;
|
||||
import com.taoyuanx.securitydemo.utils.CookieUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class TokenAuthHandlerIntercepter implements HandlerInterceptor {
|
||||
@Autowired
|
||||
ToeknHelper toeknHelper;
|
||||
|
||||
private List<String> publicUrl;
|
||||
|
||||
public TokenAuthHandlerIntercepter(String publicUrl) {
|
||||
this.publicUrl = Lists.newArrayList();
|
||||
Splitter.on(",").split(publicUrl).forEach(url -> {
|
||||
this.publicUrl.add(url.trim());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
throws Exception {
|
||||
if (handler instanceof HandlerMethod) {
|
||||
Long accountId = getAccountId(request);
|
||||
if (isPermit(accountId, request)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new UnAuthException("未授权操作");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Long getAccountId(HttpServletRequest request) {
|
||||
Object accountIdObj = request.getSession().getAttribute(SystemConstants.ACCOUNT_SESSION_KEY);
|
||||
if (accountIdObj == null) {
|
||||
return null;
|
||||
}
|
||||
Long accountId = (Long) accountIdObj;
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public boolean isPermit(Long accountId, HttpServletRequest request) {
|
||||
/**
|
||||
* 校验逻辑:
|
||||
* 1.登录时,校验操作token中accountId是否等于session中id
|
||||
* 2.未登录,校验url是否在公开url中
|
||||
*/
|
||||
boolean login = accountId != null;
|
||||
String url = request.getRequestURI();
|
||||
if (login) {
|
||||
if (null == accountId) {
|
||||
return false;
|
||||
}
|
||||
String token = CookieUtil.getCookie(request.getCookies(), SystemConstants.TOKEN_COOKIE_KEY);
|
||||
if (null == token || token.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
Map<String, Object> tokenData = toeknHelper.vafy(token);
|
||||
Integer tokenAccountId = (Integer) tokenData.get(SystemConstants.TOKEN_ACCOUNTID_KEY);
|
||||
if (tokenAccountId.longValue() != accountId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (null != publicUrl && publicUrl.size() > 0) {
|
||||
for (String pUrl : publicUrl) {
|
||||
if (url.contains(pUrl)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
14
src/main/java/com/taoyuanx/securitydemo/security/Rate.java
Normal file
14
src/main/java/com/taoyuanx/securitydemo/security/Rate.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.taoyuanx.securitydemo.security;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 限流复合注解
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Rate {
|
||||
RateLimit[] rate() default {};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.taoyuanx.securitydemo.security;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 限流注解
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RateLimit {
|
||||
/**
|
||||
* 限流并发 和限流的key,类型
|
||||
*/
|
||||
double limit() default 100;
|
||||
|
||||
String key() default "" ;
|
||||
|
||||
RateLimitType type() default RateLimitType.METHOD;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.taoyuanx.securitydemo.security;
|
||||
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
import com.taoyuanx.securitydemo.exception.RadioLimitException;
|
||||
import com.taoyuanx.securitydemo.utils.RequestUtil;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.Signature;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* aop限流
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class RateLimitAspect {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RateLimitAspect.class);
|
||||
|
||||
private Map<String, RateLimiter> rateHolder = Maps.newConcurrentMap();
|
||||
private static final int MAX_HOLDER_SIZE = 50000;
|
||||
|
||||
@Pointcut("execution(* com.taoyuanx.securitydemo.controller..*.*(..))&& (@annotation(RateLimit)||@annotation(Rate))")
|
||||
public void ratePointCut() {
|
||||
}
|
||||
|
||||
@Around("ratePointCut()")
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
Signature signature = joinPoint.getSignature();
|
||||
MethodSignature methodSignature = (MethodSignature) signature;
|
||||
Method currentMethod = methodSignature.getMethod();
|
||||
String className = joinPoint.getTarget().getClass().getName();
|
||||
String methodName = className + "." + methodSignature.getName();
|
||||
RateLimit rateLimit = AnnotationUtils.findAnnotation(currentMethod, RateLimit.class);
|
||||
if (rateLimit != null) {
|
||||
handleRateLimit(rateLimit, methodName);
|
||||
} else {
|
||||
Rate rate = AnnotationUtils.findAnnotation(currentMethod, Rate.class);
|
||||
if (rate != null) {
|
||||
RateLimit[] rateLimitArray = rate.rate();
|
||||
if (rateLimitArray != null) {
|
||||
for (RateLimit limit : rateLimitArray) {
|
||||
handleRateLimit(limit, methodName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
|
||||
private void handleRateLimit(RateLimit rateLimit, String methodName) throws Throwable {
|
||||
RateLimiter rateLimiter = doGetRateLimiter(rateLimit, methodName);
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
throw new RadioLimitException("请求过于频繁,请稍后再试");
|
||||
}
|
||||
}
|
||||
|
||||
private RateLimiter doGetRateLimiter(RateLimit rateLimit, String methodName) {
|
||||
RateLimitType type = rateLimit.type();
|
||||
String key = rateLimit.key();
|
||||
if (type == null) {
|
||||
key = methodName;
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("限流策略未定义,采用[]限流策略", methodName, RateLimitType.METHOD);
|
||||
}
|
||||
} else {
|
||||
switch (type) {
|
||||
case IP:
|
||||
String serviceKey = rateLimit.key();
|
||||
if (serviceKey == null || key.isEmpty()) {
|
||||
key = RequestUtil.getRemoteIp() + "_" + methodName;
|
||||
} else {
|
||||
key = RequestUtil.getRemoteIp() + "_" + serviceKey;
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("采用[{}]限流策略,限流key:{}", RateLimitType.IP, key);
|
||||
}
|
||||
break;
|
||||
case METHOD:
|
||||
key = methodName;
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("采用[{}]限流策略,限流key:{}", RateLimitType.METHOD, key);
|
||||
}
|
||||
break;
|
||||
case SERVICE_KEY:
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("采用[{}]限流策略,限流key:{}", RateLimitType.SERVICE_KEY, key);
|
||||
}
|
||||
break;
|
||||
case GLOBAL:
|
||||
key = "global";
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("采用[{}]限流策略,限流key:{}", RateLimitType.GLOBAL, key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rateHolder.containsKey(key)) {
|
||||
return rateHolder.get(key);
|
||||
}
|
||||
RateLimiter rateLimiter = RateLimiter.create(rateLimit.limit());
|
||||
rateHolder.put(key, rateLimiter);
|
||||
//超过固定阈值,清空,重构
|
||||
if (rateHolder.size() > MAX_HOLDER_SIZE) {
|
||||
rateHolder.clear();
|
||||
}
|
||||
return rateLimiter;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.taoyuanx.securitydemo.security;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 限流类型
|
||||
* @date 2019/8/27
|
||||
*/
|
||||
public enum RateLimitType {
|
||||
IP(0, "IP限流"), METHOD(1, "方法名"),
|
||||
SERVICE_KEY(3, "业务自定义key"),
|
||||
GLOBAL(4,"系统全局");
|
||||
private int code;
|
||||
private String desc;
|
||||
|
||||
private RateLimitType(int code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public static RateLimitType type(Integer code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
switch (code) {
|
||||
case 0:
|
||||
return IP;
|
||||
case 1:
|
||||
return METHOD;
|
||||
case 3:
|
||||
return SERVICE_KEY;
|
||||
|
||||
case 4:
|
||||
return GLOBAL;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.taoyuanx.securitydemo.security;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({ElementType.METHOD,ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RequireRole {
|
||||
Role[] role() ;
|
||||
}
|
||||
64
src/main/java/com/taoyuanx/securitydemo/security/Role.java
Normal file
64
src/main/java/com/taoyuanx/securitydemo/security/Role.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package com.taoyuanx.securitydemo.security;
|
||||
|
||||
/**
|
||||
* 简单权限控制,将账户账户状态和角色关联
|
||||
*/
|
||||
public enum Role {
|
||||
/**
|
||||
* 数字越小权限越大
|
||||
*/
|
||||
ADMIN(0, "超级管理员"),
|
||||
COMMONUSER(1, "普通用户"),
|
||||
USER(2, "任何已登录用户"),
|
||||
PUBLIC(3, "未登录");
|
||||
private int accountStatus;
|
||||
private String desc;
|
||||
|
||||
private Role(int accountStatus, String desc) {
|
||||
this.accountStatus = accountStatus;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public Integer getAccountStatus() {
|
||||
return accountStatus;
|
||||
}
|
||||
|
||||
public static boolean isMatch(Role[] roles, Integer accountStatus) {
|
||||
for (Role role : roles) {
|
||||
if (accountStatus <= role.getAccountStatus()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断账户是否具有某个角色
|
||||
*
|
||||
* @param requireRole
|
||||
* @param accountStatus
|
||||
* @return
|
||||
*/
|
||||
public static boolean hasRole(RequireRole requireRole, Integer accountStatus) {
|
||||
if (requireRole == null) {
|
||||
return true;
|
||||
}
|
||||
Role[] roles = requireRole.role();
|
||||
|
||||
if (roles != null) {
|
||||
if (accountStatus == null) {
|
||||
accountStatus = Role.PUBLIC.getAccountStatus();
|
||||
}
|
||||
return isMatch(roles, accountStatus);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
System.out.println(isMatch(new Role[]{Role.ADMIN}, Role.COMMONUSER.accountStatus));
|
||||
|
||||
System.out.println(isMatch(new Role[]{Role.COMMONUSER}, Role.ADMIN.accountStatus));
|
||||
System.out.println(isMatch(new Role[]{Role.PUBLIC}, Role.PUBLIC.accountStatus));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.taoyuanx.securitydemo.security.blacklist;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 黑名单判断
|
||||
* @date 2019/8/30
|
||||
*/
|
||||
public interface BlackListIpCheck {
|
||||
boolean ipInBlackList(String ip);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.taoyuanx.securitydemo.security.blacklist;
|
||||
|
||||
import com.google.common.hash.BloomFilter;
|
||||
import com.google.common.hash.Funnels;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 默认黑名单检测实现类
|
||||
* @date 2019/8/30
|
||||
*/
|
||||
public class DefaultBlackListIpCheck implements BlackListIpCheck {
|
||||
private BloomFilter<CharSequence> ipBlackList;
|
||||
public DefaultBlackListIpCheck(List<String> ipBlackList){
|
||||
if(ipBlackList==null||ipBlackList.isEmpty()){
|
||||
throw new RuntimeException("黑名单ip不可为空");
|
||||
}
|
||||
this.ipBlackList = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), ipBlackList.size());
|
||||
for(String blackListIp:ipBlackList){
|
||||
this.ipBlackList.put(blackListIp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean ipInBlackList(String ip) {
|
||||
return ipBlackList.mightContain(ip);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.taoyuanx.securitydemo.utils;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
public class CookieUtil {
|
||||
//判断cookie是否存在
|
||||
public static String getCookie(Cookie[] cookies, String cookieName){
|
||||
if (cookies != null && cookies.length > 0) {
|
||||
for (Cookie cookie : cookies) {
|
||||
if (cookie.getName().equals(cookieName)) {
|
||||
return cookie.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.taoyuanx.securitydemo.utils;
|
||||
|
||||
import cn.hutool.core.io.FileTypeUtil;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.taoyuanx.securitydemo.config.GlobalConfig;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 文件类型检测工具
|
||||
* @date 2019/8/30
|
||||
*/
|
||||
public class FileTypeCheckUtil {
|
||||
private static GlobalConfig globalConfig = SpringContextUtil.getBean(GlobalConfig.class);
|
||||
|
||||
private static Set<String> ALLOWEXT = null;
|
||||
|
||||
static {
|
||||
ALLOWEXT = Sets.newHashSet();
|
||||
Splitter.on(",").split(globalConfig.getAllowUploadExt()).forEach(ext -> {
|
||||
ALLOWEXT.add(ext.trim().toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean allow(String fileExt) {
|
||||
if (ALLOWEXT == null || ALLOWEXT.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return ALLOWEXT.contains(fileExt);
|
||||
}
|
||||
|
||||
public static String getType(String fileName) {
|
||||
return fileName.substring(fileName.lastIndexOf("."));
|
||||
}
|
||||
|
||||
public static String getRealType(InputStream inputStream) {
|
||||
return FileTypeUtil.getType(inputStream).toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.taoyuanx.securitydemo.utils;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 密码工具类
|
||||
* @date 2019/8/30
|
||||
*/
|
||||
public class PasswordUtil {
|
||||
public static String passwordEncode(String passwordHash) {
|
||||
/**
|
||||
* 处理密码hash
|
||||
*/
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* 明文处理
|
||||
*
|
||||
* @param passwordPlain
|
||||
* @return
|
||||
*/
|
||||
public static String passwordHanlePlain(String passwordPlain) {
|
||||
/**
|
||||
* 前后端约定好hash算法,涉及密码相关,hash传送
|
||||
*/
|
||||
return Base64.encodeBase64String(DigestUtils.getSha256Digest().digest(passwordPlain.getBytes()));
|
||||
}
|
||||
|
||||
public static boolean isPasswordEqual(String passwordHash, String dbPasswordHash) {
|
||||
if (passwordHash != null) {
|
||||
return false;
|
||||
}
|
||||
return passwordHash.equals(dbPasswordHash);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.taoyuanx.securitydemo.utils;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 请求参数过滤
|
||||
* @date 2019/8/29
|
||||
*/
|
||||
public class RequestParamFilterUtil {
|
||||
/**
|
||||
* 处理xss
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static String[] xssFilter(String[] values) {
|
||||
if(values==null){
|
||||
return null;
|
||||
}
|
||||
String[] newValues = new String[values.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
newValues[i] = xssFilter(values[i]);
|
||||
}
|
||||
return newValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理xss
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static String xssFilter(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return HtmlUtils.htmlEscape(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* sql注入风险检测
|
||||
*/
|
||||
private static List<String> SQL_KEY_WORDS = Splitter.on(",").splitToList("'|and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|; |or|-|+|,");
|
||||
public static boolean isSqlInject(String... params) {
|
||||
if (null == params) {
|
||||
return false;
|
||||
}
|
||||
for (String param : params) {
|
||||
for (int i = 0, len = SQL_KEY_WORDS.size(); i < len; i++) {
|
||||
if (param.contains(" " + SQL_KEY_WORDS.get(i) + " ")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.taoyuanx.securitydemo.utils;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 请求工具类
|
||||
* @date 2019/8/27
|
||||
*/
|
||||
public class RequestUtil {
|
||||
/**
|
||||
* 获取请求ip
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getRemoteIp() {
|
||||
HttpServletRequest request =
|
||||
((ServletRequestAttributes) RequestContextHolder.
|
||||
getRequestAttributes()).getRequest();
|
||||
return request.getRemoteHost();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断是否为本站请求
|
||||
*
|
||||
* @param referer
|
||||
* @param systemDomain
|
||||
* @return
|
||||
*/
|
||||
public static boolean isLoalSiteRequest(String referer, String systemDomain) {
|
||||
if (StringUtils.isEmpty(systemDomain)) {
|
||||
return true;
|
||||
}
|
||||
if (StringUtils.isEmpty(referer)) {
|
||||
return false;
|
||||
}
|
||||
return referer.contains(systemDomain);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.taoyuanx.securitydemo.utils;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 自定义结果返回
|
||||
* @date 2019/8/30
|
||||
*/
|
||||
public class ResponseUtil {
|
||||
public static void responseJson(HttpServletResponse response, Object result, HttpStatus httpStatus) {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setHeader("Content-type", "application/json;charset=UTF-8");
|
||||
response.setStatus(httpStatus.value());
|
||||
try {
|
||||
response.getWriter().write(JSON.toJSONString(result));
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.taoyuanx.securitydemo.utils;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.taoyuanx.securitydemo.exception.UnAuthException;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.codec.binary.StringUtils;
|
||||
import org.apache.commons.codec.digest.HmacUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author 都市桃源
|
||||
* time:2018 下午4:25:55
|
||||
* usefor: hmac token算法 单向 简单实现
|
||||
*/
|
||||
public class SimpleTokenManager {
|
||||
private static final String END_KEY = "end";
|
||||
|
||||
public static String createToken(String hmacKey, Map<String, Object> data, Long expire, TimeUnit timeUnit) {
|
||||
Long end = System.currentTimeMillis() + timeUnit.toMillis(expire);
|
||||
if (data == null || data.isEmpty()) {
|
||||
data = new HashMap<>(1);
|
||||
}
|
||||
data.put(END_KEY, end);
|
||||
byte[] signData = JSON.toJSONBytes(data);
|
||||
String token = Base64.encodeBase64URLSafeString(HmacUtils.getHmacMd5(hmacKey.getBytes()).doFinal(signData));
|
||||
token += "." + Base64.encodeBase64URLSafeString(signData);
|
||||
return token;
|
||||
}
|
||||
|
||||
public static Map<String, Object> vafy(String hmacKey, String token) throws SecurityException {
|
||||
try {
|
||||
if (null == token || "".equals(token)) {
|
||||
return null;
|
||||
}
|
||||
String[] split = token.split("\\.");
|
||||
if (split.length > 2) {
|
||||
throw new UnAuthException("token格式非法");
|
||||
}
|
||||
byte[] signData = Base64.decodeBase64(split[1].getBytes());
|
||||
JSONObject signObj = JSON.parseObject(StringUtils.newString(signData, "UTF-8"));
|
||||
Long end = signObj.getLong(END_KEY);
|
||||
if (end < System.currentTimeMillis()) {
|
||||
throw new UnAuthException("token过期");
|
||||
}
|
||||
String tokenC = Base64.encodeBase64URLSafeString(HmacUtils.getHmacMd5(hmacKey.getBytes()).doFinal(signData));
|
||||
if (tokenC.equals(split[0])) {
|
||||
return signObj;
|
||||
}
|
||||
throw new UnAuthException("token校验失败");
|
||||
} catch (Exception e) {
|
||||
if (e instanceof UnAuthException) {
|
||||
throw e;
|
||||
} else {
|
||||
throw new UnAuthException("token校验失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.taoyuanx.securitydemo.utils;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
@Component
|
||||
public class SpringContextUtil implements ApplicationContextAware {
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
public static Object getBean(String name){
|
||||
return getApplicationContext().getBean(name);
|
||||
}
|
||||
|
||||
public static <T> T getBean(Class<T> clazz){
|
||||
return getApplicationContext().getBean(clazz);
|
||||
}
|
||||
|
||||
public static <T> T getBean(String name,Class<T> clazz){
|
||||
return getApplicationContext().getBean(name, clazz);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.taoyuanx.securitydemo.web;
|
||||
|
||||
import com.taoyuanx.securitydemo.exception.UnAuthException;
|
||||
import com.taoyuanx.securitydemo.security.blacklist.BlackListIpCheck;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.annotation.WebFilter;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 黑名单过滤器
|
||||
*/
|
||||
/*@WebFilter(urlPatterns = "/api/*")
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)*/
|
||||
public class BlackListFilter implements Filter {
|
||||
|
||||
@Autowired
|
||||
BlackListIpCheck blackListIpCheck;
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException, IOException {
|
||||
String clientIp = servletRequest.getRemoteAddr();
|
||||
if (blackListIpCheck.ipInBlackList(clientIp)) {
|
||||
throw new UnAuthException("ip已在黑名单,不可访问");
|
||||
}
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
38
src/main/java/com/taoyuanx/securitydemo/web/SqlFilter.java
Normal file
38
src/main/java/com/taoyuanx/securitydemo/web/SqlFilter.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package com.taoyuanx.securitydemo.web;
|
||||
|
||||
import com.taoyuanx.securitydemo.exception.UnAuthException;
|
||||
import com.taoyuanx.securitydemo.utils.RequestParamFilterUtil;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.annotation.WebFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* xss过滤器,
|
||||
* 注意测试,不要拦截所有请求,有风险再拦截,容易误杀正常请求
|
||||
*/
|
||||
@WebFilter(urlPatterns = "/api/*")
|
||||
public class SqlFilter implements Filter {
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException, IOException {
|
||||
Map<String, String[]> parameterMap = servletRequest.getParameterMap();
|
||||
if (parameterMap != null) {
|
||||
for (String key : parameterMap.keySet()) {
|
||||
if (RequestParamFilterUtil.isSqlInject(parameterMap.get(key))) {
|
||||
throw new UnAuthException("权限异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
29
src/main/java/com/taoyuanx/securitydemo/web/XssFilter.java
Normal file
29
src/main/java/com/taoyuanx/securitydemo/web/XssFilter.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package com.taoyuanx.securitydemo.web;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.annotation.WebFilter;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* xss过滤器
|
||||
*/
|
||||
@WebFilter(urlPatterns = "/api/*")
|
||||
public class XssFilter implements Filter {
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException, IOException {
|
||||
//使用包装器
|
||||
XssRequestWrapper xssRequestWrapper = new XssRequestWrapper((HttpServletRequest) servletRequest);
|
||||
filterChain.doFilter(xssRequestWrapper, servletResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.taoyuanx.securitydemo.web;
|
||||
|
||||
import com.taoyuanx.securitydemo.utils.RequestParamFilterUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* xss request包装类
|
||||
*/
|
||||
public class XssRequestWrapper extends HttpServletRequestWrapper {
|
||||
public XssRequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对数组参数进行特殊字符过滤
|
||||
*/
|
||||
@Override
|
||||
public String[] getParameterValues(String name) {
|
||||
return RequestParamFilterUtil.xssFilter(super.getParameterValues(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
return RequestParamFilterUtil.xssFilter(super.getParameter(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String[]> getParameterMap() {
|
||||
Map<String, String[]> parameterMap = super.getParameterMap();
|
||||
if (parameterMap == null || parameterMap.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<String, String[]> newParameterMap = new HashMap<>(parameterMap.size());
|
||||
for (String key : parameterMap.keySet()) {
|
||||
newParameterMap.put(key, RequestParamFilterUtil.xssFilter(parameterMap.get(key)));
|
||||
}
|
||||
return newParameterMap;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
31
src/main/resources/application.properties
Normal file
31
src/main/resources/application.properties
Normal file
@@ -0,0 +1,31 @@
|
||||
server.port=8080
|
||||
#数据库连接池
|
||||
#spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
|
||||
#spring.datasource.username:root
|
||||
#spring.datasource.password:root
|
||||
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
|
||||
#spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
|
||||
#spring.datasource.initialSize=10
|
||||
#spring.datasource.minIdle=10
|
||||
#spring.datasource.maxActive=100
|
||||
#spring.datasource.maxWait=60000
|
||||
#spring.datasource.timeBetweenEvictionRunsMillis=60000
|
||||
#spring.datasource.filters=wall
|
||||
|
||||
|
||||
server.servlet.session.timeout=1800
|
||||
|
||||
#自定义配置
|
||||
#系统域名
|
||||
application.config.systemDoamin=taoyuanx.com
|
||||
#referer校验配置
|
||||
application.config.referer-check-url[0]=refererCheck
|
||||
application.config.referer-check-allow-domains=taoyuanx.com
|
||||
#黑名单
|
||||
application.config.blackListIp[0]=127.0.0.1
|
||||
application.config.blackListIp[1]=0:0:0:0:0:0:0:1
|
||||
#允许上传文件后缀,逗号分隔
|
||||
application.config.allowUploadExt=bmp,jpg,png,tif,gif,pcx,tga,exif,fpx,svg,psd,cdr,pcd,dxf,ufo,eps,ai,raw,WMF,webp
|
||||
#系统颁发token秘钥hmac算法的key
|
||||
application.config.token-key=123456
|
||||
application.config.expire-seconds=1800
|
||||
41
src/main/resources/logback.xml
Normal file
41
src/main/resources/logback.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="false">
|
||||
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 -->
|
||||
<property name="LOG_HOME" value="./logs" />
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -
|
||||
%msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<!-- 按照每天生成日志文件 -->
|
||||
<appender name="FILE"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!--日志文件输出的文件名 -->
|
||||
<FileNamePattern>${LOG_HOME}/cert.log.%d{yyyy-MM-dd}.log
|
||||
</FileNamePattern>
|
||||
<!--日志文件保留天数 -->
|
||||
<MaxHistory>30</MaxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -
|
||||
%msg%n</pattern>
|
||||
|
||||
</encoder>
|
||||
<!--日志文件最大的大小 -->
|
||||
<triggeringPolicy
|
||||
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
|
||||
<MaxFileSize>10MB</MaxFileSize>
|
||||
</triggeringPolicy>
|
||||
</appender>
|
||||
<!-- 日志输出级别 -->
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
|
||||
</configuration>
|
||||
11
src/main/resources/public/index.html
Normal file
11
src/main/resources/public/index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>首页</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>首页</h1>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
51
src/main/resources/public/login.html
Normal file
51
src/main/resources/public/login.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>登录测试</title>
|
||||
</head>
|
||||
<body>
|
||||
<form>
|
||||
<input name="userName" type="text" value="dushitaoyuan" placeholder="请输入账户名">
|
||||
<input name="password" type="password" value="123456" placeholder="请输入密码">
|
||||
<input type="button" value="登录" onclick="upload()">
|
||||
</form>
|
||||
|
||||
|
||||
</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 type="text/javascript">
|
||||
function upload() {
|
||||
var password = $("input[name='password']").val();
|
||||
password = encodePassword(password);
|
||||
jQuery.support.cors = true;
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
url: getApiUrl("api/login"),
|
||||
data: {
|
||||
"userName": $("input[name='userName']").val(),
|
||||
"password": password
|
||||
},
|
||||
dataType: 'json',
|
||||
traditional: true,
|
||||
async: false,
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
}
|
||||
}).success(function (result) {
|
||||
if (result.success == 1) {
|
||||
alert("登录成功");
|
||||
window.location.href = getStaticUrl("public/index.html");
|
||||
} else {
|
||||
alert(result.msg);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
40
src/main/resources/public/uploadtest.html
Normal file
40
src/main/resources/public/uploadtest.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>文件上传测试</title>
|
||||
</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"/>
|
||||
|
||||
<script src="/static/js/common/system.js"/>
|
||||
<script type="text/javascript">
|
||||
function upload() {
|
||||
var formData = new FormData();
|
||||
formData.append('file', $('input[name=file]')[0].files[0]);
|
||||
jQuery.support.cors = true;
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
url: getApiUrl("api/upload"),
|
||||
data: formData,
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
cache: false,
|
||||
processData: false,
|
||||
contentType: false
|
||||
}).success(function (result) {
|
||||
if (result.status == 1) {
|
||||
alert("上传成功");
|
||||
} else {
|
||||
alert(result.msg);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
src/main/resources/static/js/ajaxhook.min.js
vendored
Normal file
1
src/main/resources/static/js/ajaxhook.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(t){function n(e){if(r[e])return r[e].exports;var i=r[e]={exports:{},id:e,loaded:!1};return t[e].call(i.exports,i,i.exports,n),i.loaded=!0,i.exports}var r={};return n.m=t,n.c=r,n.p="",n(0)}([function(t,n,r){r(1)(window)},function(t,n){t.exports=function(t){var n="RealXMLHttpRequest";t.hookAjax=function(t){function r(n){return function(){var r=this.hasOwnProperty(n+"_")?this[n+"_"]:this.xhr[n],e=(t[n]||{}).getter;return e&&e(r,this)||r}}function e(n){return function(r){var e=this.xhr,i=this,o=t[n];if("function"==typeof o)e[n]=function(){t[n](i)||r.apply(e,arguments)};else{var u=(o||{}).setter;r=u&&u(r,i)||r;try{e[n]=r}catch(t){this[n+"_"]=r}}}}function i(n){return function(){var r=[].slice.call(arguments);if(!t[n]||!t[n].call(this,r,this.xhr))return this.xhr[n].apply(this.xhr,r)}}return window[n]=window[n]||XMLHttpRequest,XMLHttpRequest=function(){var t=new window[n];for(var o in t){var u="";try{u=typeof t[o]}catch(t){}"function"===u?this[o]=i(o):Object.defineProperty(this,o,{get:r(o),set:e(o),enumerable:!0})}this.xhr=t},window[n]},t.unHookAjax=function(){window[n]&&(XMLHttpRequest=window[n]),window[n]=void 0},t.default=t}}]);
|
||||
43
src/main/resources/static/js/common/system.js
Normal file
43
src/main/resources/static/js/common/system.js
Normal file
@@ -0,0 +1,43 @@
|
||||
//全局定义变量和全局函数
|
||||
|
||||
var apiBaseUrl = "http://localhost:8080/"
|
||||
var staticBaseUrl = "http://localhost:8080/"
|
||||
|
||||
function getApiUrl(path) {
|
||||
return apiBaseUrl + path;
|
||||
}
|
||||
|
||||
function getStaticUrl(path) {
|
||||
return staticBaseUrl + path;
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
hookAjax({
|
||||
//拦截回调
|
||||
onreadystatechange: function (xhr) {
|
||||
try {
|
||||
//权限异常,跳转到登录
|
||||
if (xhr.status == 401) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
//限流异常
|
||||
if (xhr.status == 429) {
|
||||
var result = JSON.parse(xhr.responseText);
|
||||
alert(result.msg);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* 加密密码
|
||||
*/
|
||||
function encodePassword(password) {
|
||||
return CryptoJS.SHA256(password).toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
13
src/main/resources/static/js/crypt/core-min.js
vendored
Normal file
13
src/main/resources/static/js/crypt/core-min.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
CryptoJS v3.1.2
|
||||
code.google.com/p/crypto-js
|
||||
(c) 2009-2013 by Jeff Mott. All rights reserved.
|
||||
code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
var CryptoJS=CryptoJS||function(h,r){var k={},l=k.lib={},n=function(){},f=l.Base={extend:function(a){n.prototype=this;var b=new n;a&&b.mixIn(a);b.hasOwnProperty("init")||(b.init=function(){b.$super.init.apply(this,arguments)});b.init.prototype=b;b.$super=this;return b},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var b in a)a.hasOwnProperty(b)&&(this[b]=a[b]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
|
||||
j=l.WordArray=f.extend({init:function(a,b){a=this.words=a||[];this.sigBytes=b!=r?b:4*a.length},toString:function(a){return(a||s).stringify(this)},concat:function(a){var b=this.words,d=a.words,c=this.sigBytes;a=a.sigBytes;this.clamp();if(c%4)for(var e=0;e<a;e++)b[c+e>>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((c+e)%4);else if(65535<d.length)for(e=0;e<a;e+=4)b[c+e>>>2]=d[e>>>2];else b.push.apply(b,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,b=this.sigBytes;a[b>>>2]&=4294967295<<
|
||||
32-8*(b%4);a.length=h.ceil(b/4)},clone:function(){var a=f.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var b=[],d=0;d<a;d+=4)b.push(4294967296*h.random()|0);return new j.init(b,a)}}),m=k.enc={},s=m.Hex={stringify:function(a){var b=a.words;a=a.sigBytes;for(var d=[],c=0;c<a;c++){var e=b[c>>>2]>>>24-8*(c%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c<b;c+=2)d[c>>>3]|=parseInt(a.substr(c,
|
||||
2),16)<<24-4*(c%8);return new j.init(d,b/2)}},p=m.Latin1={stringify:function(a){var b=a.words;a=a.sigBytes;for(var d=[],c=0;c<a;c++)d.push(String.fromCharCode(b[c>>>2]>>>24-8*(c%4)&255));return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c<b;c++)d[c>>>2]|=(a.charCodeAt(c)&255)<<24-8*(c%4);return new j.init(d,b)}},t=m.Utf8={stringify:function(a){try{return decodeURIComponent(escape(p.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return p.parse(unescape(encodeURIComponent(a)))}},
|
||||
q=l.BufferedBlockAlgorithm=f.extend({reset:function(){this._data=new j.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=t.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var b=this._data,d=b.words,c=b.sigBytes,e=this.blockSize,f=c/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;c=h.min(4*a,c);if(a){for(var g=0;g<a;g+=e)this._doProcessBlock(d,g);g=d.splice(0,a);b.sigBytes-=c}return new j.init(g,c)},clone:function(){var a=f.clone.call(this);
|
||||
a._data=this._data.clone();return a},_minBufferSize:0});l.Hasher=q.extend({cfg:f.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){q.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(b,d){return(new a.init(d)).finalize(b)}},_createHmacHelper:function(a){return function(b,d){return(new u.HMAC.init(a,
|
||||
d)).finalize(b)}}});var u=k.algo={};return k}(Math);
|
||||
8
src/main/resources/static/js/crypt/enc-base64-min.js
vendored
Normal file
8
src/main/resources/static/js/crypt/enc-base64-min.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
CryptoJS v3.1.2
|
||||
code.google.com/p/crypto-js
|
||||
(c) 2009-2013 by Jeff Mott. All rights reserved.
|
||||
code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
(function(){var h=CryptoJS,j=h.lib.WordArray;h.enc.Base64={stringify:function(b){var e=b.words,f=b.sigBytes,c=this._map;b.clamp();b=[];for(var a=0;a<f;a+=3)for(var d=(e[a>>>2]>>>24-8*(a%4)&255)<<16|(e[a+1>>>2]>>>24-8*((a+1)%4)&255)<<8|e[a+2>>>2]>>>24-8*((a+2)%4)&255,g=0;4>g&&a+0.75*g<f;g++)b.push(c.charAt(d>>>6*(3-g)&63));if(e=c.charAt(64))for(;b.length%4;)b.push(e);return b.join("")},parse:function(b){var e=b.length,f=this._map,c=f.charAt(64);c&&(c=b.indexOf(c),-1!=c&&(e=c));for(var c=[],a=0,d=0;d<
|
||||
e;d++)if(d%4){var g=f.indexOf(b.charAt(d-1))<<2*(d%4),h=f.indexOf(b.charAt(d))>>>6-2*(d%4);c[a>>>2]|=(g|h)<<24-8*(a%4);a++}return j.create(c,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();
|
||||
19
src/main/resources/static/js/crypt/md5.js
Normal file
19
src/main/resources/static/js/crypt/md5.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
CryptoJS v3.1.2
|
||||
code.google.com/p/crypto-js
|
||||
(c) 2009-2013 by Jeff Mott. All rights reserved.
|
||||
code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
var CryptoJS=CryptoJS||function(s,p){var m={},l=m.lib={},n=function(){},r=l.Base={extend:function(b){n.prototype=this;var h=new n;b&&h.mixIn(b);h.hasOwnProperty("init")||(h.init=function(){h.$super.init.apply(this,arguments)});h.init.prototype=h;h.$super=this;return h},create:function(){var b=this.extend();b.init.apply(b,arguments);return b},init:function(){},mixIn:function(b){for(var h in b)b.hasOwnProperty(h)&&(this[h]=b[h]);b.hasOwnProperty("toString")&&(this.toString=b.toString)},clone:function(){return this.init.prototype.extend(this)}},
|
||||
q=l.WordArray=r.extend({init:function(b,h){b=this.words=b||[];this.sigBytes=h!=p?h:4*b.length},toString:function(b){return(b||t).stringify(this)},concat:function(b){var h=this.words,a=b.words,j=this.sigBytes;b=b.sigBytes;this.clamp();if(j%4)for(var g=0;g<b;g++)h[j+g>>>2]|=(a[g>>>2]>>>24-8*(g%4)&255)<<24-8*((j+g)%4);else if(65535<a.length)for(g=0;g<b;g+=4)h[j+g>>>2]=a[g>>>2];else h.push.apply(h,a);this.sigBytes+=b;return this},clamp:function(){var b=this.words,h=this.sigBytes;b[h>>>2]&=4294967295<<
|
||||
32-8*(h%4);b.length=s.ceil(h/4)},clone:function(){var b=r.clone.call(this);b.words=this.words.slice(0);return b},random:function(b){for(var h=[],a=0;a<b;a+=4)h.push(4294967296*s.random()|0);return new q.init(h,b)}}),v=m.enc={},t=v.Hex={stringify:function(b){var a=b.words;b=b.sigBytes;for(var g=[],j=0;j<b;j++){var k=a[j>>>2]>>>24-8*(j%4)&255;g.push((k>>>4).toString(16));g.push((k&15).toString(16))}return g.join("")},parse:function(b){for(var a=b.length,g=[],j=0;j<a;j+=2)g[j>>>3]|=parseInt(b.substr(j,
|
||||
2),16)<<24-4*(j%8);return new q.init(g,a/2)}},a=v.Latin1={stringify:function(b){var a=b.words;b=b.sigBytes;for(var g=[],j=0;j<b;j++)g.push(String.fromCharCode(a[j>>>2]>>>24-8*(j%4)&255));return g.join("")},parse:function(b){for(var a=b.length,g=[],j=0;j<a;j++)g[j>>>2]|=(b.charCodeAt(j)&255)<<24-8*(j%4);return new q.init(g,a)}},u=v.Utf8={stringify:function(b){try{return decodeURIComponent(escape(a.stringify(b)))}catch(g){throw Error("Malformed UTF-8 data");}},parse:function(b){return a.parse(unescape(encodeURIComponent(b)))}},
|
||||
g=l.BufferedBlockAlgorithm=r.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(b){"string"==typeof b&&(b=u.parse(b));this._data.concat(b);this._nDataBytes+=b.sigBytes},_process:function(b){var a=this._data,g=a.words,j=a.sigBytes,k=this.blockSize,m=j/(4*k),m=b?s.ceil(m):s.max((m|0)-this._minBufferSize,0);b=m*k;j=s.min(4*b,j);if(b){for(var l=0;l<b;l+=k)this._doProcessBlock(g,l);l=g.splice(0,b);a.sigBytes-=j}return new q.init(l,j)},clone:function(){var b=r.clone.call(this);
|
||||
b._data=this._data.clone();return b},_minBufferSize:0});l.Hasher=g.extend({cfg:r.extend(),init:function(b){this.cfg=this.cfg.extend(b);this.reset()},reset:function(){g.reset.call(this);this._doReset()},update:function(b){this._append(b);this._process();return this},finalize:function(b){b&&this._append(b);return this._doFinalize()},blockSize:16,_createHelper:function(b){return function(a,g){return(new b.init(g)).finalize(a)}},_createHmacHelper:function(b){return function(a,g){return(new k.HMAC.init(b,
|
||||
g)).finalize(a)}}});var k=m.algo={};return m}(Math);
|
||||
(function(s){function p(a,k,b,h,l,j,m){a=a+(k&b|~k&h)+l+m;return(a<<j|a>>>32-j)+k}function m(a,k,b,h,l,j,m){a=a+(k&h|b&~h)+l+m;return(a<<j|a>>>32-j)+k}function l(a,k,b,h,l,j,m){a=a+(k^b^h)+l+m;return(a<<j|a>>>32-j)+k}function n(a,k,b,h,l,j,m){a=a+(b^(k|~h))+l+m;return(a<<j|a>>>32-j)+k}for(var r=CryptoJS,q=r.lib,v=q.WordArray,t=q.Hasher,q=r.algo,a=[],u=0;64>u;u++)a[u]=4294967296*s.abs(s.sin(u+1))|0;q=q.MD5=t.extend({_doReset:function(){this._hash=new v.init([1732584193,4023233417,2562383102,271733878])},
|
||||
_doProcessBlock:function(g,k){for(var b=0;16>b;b++){var h=k+b,w=g[h];g[h]=(w<<8|w>>>24)&16711935|(w<<24|w>>>8)&4278255360}var b=this._hash.words,h=g[k+0],w=g[k+1],j=g[k+2],q=g[k+3],r=g[k+4],s=g[k+5],t=g[k+6],u=g[k+7],v=g[k+8],x=g[k+9],y=g[k+10],z=g[k+11],A=g[k+12],B=g[k+13],C=g[k+14],D=g[k+15],c=b[0],d=b[1],e=b[2],f=b[3],c=p(c,d,e,f,h,7,a[0]),f=p(f,c,d,e,w,12,a[1]),e=p(e,f,c,d,j,17,a[2]),d=p(d,e,f,c,q,22,a[3]),c=p(c,d,e,f,r,7,a[4]),f=p(f,c,d,e,s,12,a[5]),e=p(e,f,c,d,t,17,a[6]),d=p(d,e,f,c,u,22,a[7]),
|
||||
c=p(c,d,e,f,v,7,a[8]),f=p(f,c,d,e,x,12,a[9]),e=p(e,f,c,d,y,17,a[10]),d=p(d,e,f,c,z,22,a[11]),c=p(c,d,e,f,A,7,a[12]),f=p(f,c,d,e,B,12,a[13]),e=p(e,f,c,d,C,17,a[14]),d=p(d,e,f,c,D,22,a[15]),c=m(c,d,e,f,w,5,a[16]),f=m(f,c,d,e,t,9,a[17]),e=m(e,f,c,d,z,14,a[18]),d=m(d,e,f,c,h,20,a[19]),c=m(c,d,e,f,s,5,a[20]),f=m(f,c,d,e,y,9,a[21]),e=m(e,f,c,d,D,14,a[22]),d=m(d,e,f,c,r,20,a[23]),c=m(c,d,e,f,x,5,a[24]),f=m(f,c,d,e,C,9,a[25]),e=m(e,f,c,d,q,14,a[26]),d=m(d,e,f,c,v,20,a[27]),c=m(c,d,e,f,B,5,a[28]),f=m(f,c,
|
||||
d,e,j,9,a[29]),e=m(e,f,c,d,u,14,a[30]),d=m(d,e,f,c,A,20,a[31]),c=l(c,d,e,f,s,4,a[32]),f=l(f,c,d,e,v,11,a[33]),e=l(e,f,c,d,z,16,a[34]),d=l(d,e,f,c,C,23,a[35]),c=l(c,d,e,f,w,4,a[36]),f=l(f,c,d,e,r,11,a[37]),e=l(e,f,c,d,u,16,a[38]),d=l(d,e,f,c,y,23,a[39]),c=l(c,d,e,f,B,4,a[40]),f=l(f,c,d,e,h,11,a[41]),e=l(e,f,c,d,q,16,a[42]),d=l(d,e,f,c,t,23,a[43]),c=l(c,d,e,f,x,4,a[44]),f=l(f,c,d,e,A,11,a[45]),e=l(e,f,c,d,D,16,a[46]),d=l(d,e,f,c,j,23,a[47]),c=n(c,d,e,f,h,6,a[48]),f=n(f,c,d,e,u,10,a[49]),e=n(e,f,c,d,
|
||||
C,15,a[50]),d=n(d,e,f,c,s,21,a[51]),c=n(c,d,e,f,A,6,a[52]),f=n(f,c,d,e,q,10,a[53]),e=n(e,f,c,d,y,15,a[54]),d=n(d,e,f,c,w,21,a[55]),c=n(c,d,e,f,v,6,a[56]),f=n(f,c,d,e,D,10,a[57]),e=n(e,f,c,d,t,15,a[58]),d=n(d,e,f,c,B,21,a[59]),c=n(c,d,e,f,r,6,a[60]),f=n(f,c,d,e,z,10,a[61]),e=n(e,f,c,d,j,15,a[62]),d=n(d,e,f,c,x,21,a[63]);b[0]=b[0]+c|0;b[1]=b[1]+d|0;b[2]=b[2]+e|0;b[3]=b[3]+f|0},_doFinalize:function(){var a=this._data,k=a.words,b=8*this._nDataBytes,h=8*a.sigBytes;k[h>>>5]|=128<<24-h%32;var l=s.floor(b/
|
||||
4294967296);k[(h+64>>>9<<4)+15]=(l<<8|l>>>24)&16711935|(l<<24|l>>>8)&4278255360;k[(h+64>>>9<<4)+14]=(b<<8|b>>>24)&16711935|(b<<24|b>>>8)&4278255360;a.sigBytes=4*(k.length+1);this._process();a=this._hash;k=a.words;for(b=0;4>b;b++)h=k[b],k[b]=(h<<8|h>>>24)&16711935|(h<<24|h>>>8)&4278255360;return a},clone:function(){var a=t.clone.call(this);a._hash=this._hash.clone();return a}});r.MD5=t._createHelper(q);r.HmacMD5=t._createHmacHelper(q)})(Math);
|
||||
9
src/main/resources/static/js/crypt/sha256-min.js
vendored
Normal file
9
src/main/resources/static/js/crypt/sha256-min.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
CryptoJS v3.1.2
|
||||
code.google.com/p/crypto-js
|
||||
(c) 2009-2013 by Jeff Mott. All rights reserved.
|
||||
code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
(function(k){for(var g=CryptoJS,h=g.lib,v=h.WordArray,j=h.Hasher,h=g.algo,s=[],t=[],u=function(q){return 4294967296*(q-(q|0))|0},l=2,b=0;64>b;){var d;a:{d=l;for(var w=k.sqrt(d),r=2;r<=w;r++)if(!(d%r)){d=!1;break a}d=!0}d&&(8>b&&(s[b]=u(k.pow(l,0.5))),t[b]=u(k.pow(l,1/3)),b++);l++}var n=[],h=h.SHA256=j.extend({_doReset:function(){this._hash=new v.init(s.slice(0))},_doProcessBlock:function(q,h){for(var a=this._hash.words,c=a[0],d=a[1],b=a[2],k=a[3],f=a[4],g=a[5],j=a[6],l=a[7],e=0;64>e;e++){if(16>e)n[e]=
|
||||
q[h+e]|0;else{var m=n[e-15],p=n[e-2];n[e]=((m<<25|m>>>7)^(m<<14|m>>>18)^m>>>3)+n[e-7]+((p<<15|p>>>17)^(p<<13|p>>>19)^p>>>10)+n[e-16]}m=l+((f<<26|f>>>6)^(f<<21|f>>>11)^(f<<7|f>>>25))+(f&g^~f&j)+t[e]+n[e];p=((c<<30|c>>>2)^(c<<19|c>>>13)^(c<<10|c>>>22))+(c&d^c&b^d&b);l=j;j=g;g=f;f=k+m|0;k=b;b=d;d=c;c=m+p|0}a[0]=a[0]+c|0;a[1]=a[1]+d|0;a[2]=a[2]+b|0;a[3]=a[3]+k|0;a[4]=a[4]+f|0;a[5]=a[5]+g|0;a[6]=a[6]+j|0;a[7]=a[7]+l|0},_doFinalize:function(){var d=this._data,b=d.words,a=8*this._nDataBytes,c=8*d.sigBytes;
|
||||
b[c>>>5]|=128<<24-c%32;b[(c+64>>>9<<4)+14]=k.floor(a/4294967296);b[(c+64>>>9<<4)+15]=a;d.sigBytes=4*b.length;this._process();return this._hash},clone:function(){var b=j.clone.call(this);b._hash=this._hash.clone();return b}});g.SHA256=j._createHelper(h);g.HmacSHA256=j._createHmacHelper(h)})(Math);
|
||||
4
src/main/resources/static/js/jquery-2.1.1.min.js
vendored
Normal file
4
src/main/resources/static/js/jquery-2.1.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
32
src/test/java/com/taoyuanx/securitydemo/BoomFilterTest.java
Normal file
32
src/test/java/com/taoyuanx/securitydemo/BoomFilterTest.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package com.taoyuanx.securitydemo;
|
||||
|
||||
import com.google.common.hash.BloomFilter;
|
||||
import com.google.common.hash.Funnels;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author dushitaoyuan
|
||||
* @desc 布隆过滤测试
|
||||
* @date 2019/8/30
|
||||
*/
|
||||
public class BoomFilterTest {
|
||||
|
||||
@Test
|
||||
public void failedCountTest() {
|
||||
int batch = 10000000;
|
||||
BloomFilter bloomFilter = BloomFilter.create(Funnels.integerFunnel(), batch);
|
||||
|
||||
for (int i = 0; i < batch; i++) {
|
||||
bloomFilter.put(i);
|
||||
}
|
||||
int count = 0;
|
||||
for (int i = 0; i < batch; i++) {
|
||||
if (!bloomFilter.mightContain(i)) {
|
||||
System.out.println("failed\t" + i);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
System.out.println("失败次数:" + count);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.taoyuanx.securitydemo;
|
||||
|
||||
import com.taoyuanx.securitydemo.controller.BussinessController;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class SecurityDemoApplicationTests {
|
||||
@Autowired
|
||||
BussinessController bussinessController;
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
bussinessController.admin();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user