Fork me on Gitee

SpringBoot项目通用的配置

SpringBoot项目通用的配置

一、定义自定义异常类

因为后台Java项目是Web工程,所以有异常消息,我们要在原有异常消息的基础之上,封装状态码所以需要我们自己创建一个异常类。

自定义异常类继承的父类,我没有选择Exception。因为Exception类型的异常,我们必须要手动显式处理,要么上抛,要么捕获。我希望我定义的异常采用既可以采用显式处理,也可以隐式处理,所以我选择继承RuntimeException这个父类。RuntimeException类型的异常可以被虚拟机隐式处理,这样就省去了我们很多手动处理异常的麻烦。

  1. 创建exception

  2. 创建EmosException类

@Data
@AllArgsConstructor
public class EmosException extends RuntimeException {
	/**
	 * msg
	 */
	private String msg;
	/**
	 * code
	 */
	private int code = 500;

	public EmosException(String msg){
		super(msg);
		this.msg = msg;
	}


	public EmosException(String msg,Throwable e){
		super(msg,e);
		this.msg = msg;
	}

	public EmosException(String msg,int code,Throwable e){
		super(msg,e);
		this.msg = msg;
		this.code = code;
	}

}

二、自定义全局异常类

因为客户端提交的消息不正确,后端系统可能向客户端返回了大量的异常内容。这里对返回的异常内容做一下精简。

config包中创建ExceptionAdvice类。

@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {

    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public String exceptionHandler(Exception e){
        log.error("执行异常",e);
        if(e instanceof MethodArgumentNotValidException){
            MethodArgumentNotValidException exception = (MethodArgumentNotValidException) e;
            return exception.getBindingResult().getFieldError().getDefaultMessage();
        }else if(e instanceof EmosException){
            EmosException exception = (EmosException) e;
            return exception.getMsg();
        }else if(e instanceof UnauthenticatedException){
            return "你不具备相关权限";
        }else {
            return "后端执行异常";
        }

    }
}

三、 通用Web返回类

虽然SpringMVC的Controller可以自动把对象转换成JSON返回给客户端,但是我们需要制定一个统一的标准,保证所有Controller返回的数据格式一致。最简便的办法就是定义封装类,来统一封装返回给客户端的数据。

修改pom.xml文件,添加依赖库。Apachehttpcomponents库里面的HttpStatus类封装了很多状态码,所以我们在Web返回对象中封装状态码,可以用到这些状态码。

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    <version>4.4.13</version>
</dependency>

先创建common.util包,然后创建R类。

import org.apache.http.HttpStatus;

import java.util.HashMap;
import java.util.Map;

public class R extends HashMap<String, Object> {
	public R(){
		put("code", HttpStatus.SC_OK);
		put("msg","success");
	}
	@Override
	public R put(String key,Object vale){
		super.put(key,vale);
		return this;
	}

	public static R ok(){
		return new R();
	}

	public static R ok(String msg){
		R r = new R();
		r.put("msg",msg);
		return r;
	}

	public static R ok(Map<String,Object> map){
		R r = new R();
		r.putAll(map);
		return r;
	}

	public static R error(int code,String msg){
		R r = new R();
		r.put("code",code);
		r.put("msg",msg);
		return r;
	}

	public static R error(String msg){
		return error(HttpStatus.SC_INTERNAL_SERVER_ERROR,msg);
	}

	public static R error(){
		return error(HttpStatus.SC_INTERNAL_SERVER_ERROR,"未知异常,请联系管理员");
	}


}


四、接入Swagger

开发前后端分离架构的项目,往往调试后端Web接口需要用到POSTMAN工具。虽然POSTMAN工具的功能非常强大,但是请求参数很多的情况下,我们手写这些参数和数据还是非常麻烦的。因此我们需要一个调试后端Web接口更加简便的方法。恰好Swagger提供了REST API调用方式,我们不需要借助任何工具的情况下,访问Swagger页面,就可以对Web接口进行调用和调试,这种调试方式的效率要远超POSTMAN软件。

4.1 添加依赖库

pom.xml文件中添加Swagger依赖库,这里我们使用的是Swagger2版本,在UI方面,比Swagger1版本要好看很多。

 <!-- swagger 2-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

4.2 创建Swagger配置类

config包中创建SwaggerConfig类。

@Configuration
@EnableSwagger2
public class SwaggerConfig {

	@Bean
	public Docket createRestApi() {
		Docket docket = new Docket(DocumentationType.SWAGGER_2);
		// ApiInfoBuilder 用于在Swagger界面上添加各种信息
		ApiInfoBuilder builder = new ApiInfoBuilder();
		builder.title("EMOS在线办公系统");
		builder.version("V0.0.1");
		ApiInfo apiInfo = builder.build();
		// 设置页面显示信息(desc描述,作者,lic均可设置)
		docket.apiInfo(apiInfo);
		// ApiSelectorBuilder 用来设置哪些类中的方法会生成到REST API中
		ApiSelectorBuilder selectorBuilder = docket.select();
		//所有包下的类
		selectorBuilder.paths(PathSelectors.any());
		//使用@ApiOperation的方法会被提取到REST API中
		selectorBuilder.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class));
		docket = selectorBuilder.build();

		/*
		 * 下面的语句是开启对JWT的支持,当用户用Swagger调用受JWT认证保护的方法,
		 * 必须要先提交参数(例如令牌)
		 */
		//存储用户必须提交的参数
		List<ApiKey> apikey = Lists.newArrayList();
		//规定用户需要输入什么参数
		apikey.add(new ApiKey("token", "token", "header"));
		docket.securitySchemes(apikey);
		//如果用户JWT认证通过,则在Swagger中全局有效
		AuthorizationScope scope = new AuthorizationScope("global", "accessEverything");
		AuthorizationScope[] scopeArray = {scope};
		//存储令牌和作用域
		SecurityReference reference = new SecurityReference("token", scopeArray);
		List<SecurityReference> refList = Lists.newArrayList();
		refList.add(reference);
		SecurityContext context = SecurityContext.builder().securityReferences(refList).build();
		List<SecurityContext> cxtList = Lists.newArrayList();
		cxtList.add(context);
		docket.securityContexts(cxtList);
		return docket;
	}

}

4.3测试Web接口

打开浏览器,访问http://127.0.0.1:8080/emos-wx-api/swagger-ui.html

image-20240224234121155

五、抵御Xss攻击

5.1 XSS攻击的危害

XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上也可以包括Java、 VBScript、ActiveX、 Flash 或者甚至是普通的HTML。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。

例如用户在发帖或者注册的时候,在文本框中输入<script>alert('xss')</script>这段代码如果不经过转义处理,而直接保存到数据库。将来视图层渲染HTML的时候,把这段代码输出到页面上,那么<script> 标签的内容就会被执行。

通常情况下,我们登陆到某个网站。如果网站使用 HttpSession保存登陆凭证,那么 SessionId 会以 cookie 的形式保存在浏览器上。如果黑客在这个网页发帖的时候,填写的 JavaScript 代码是用来获取 cookie 内容的,并且把cookie 内容通过Ajax发送给黑客自己的电脑。于是只要有人在这个网站上浏览黑客发的帖子,那么视图层渲染HTML页面,就会执行注入的XSS脚本,于是你的Cookie信息就泄露了。黑客在自己的电脑上构建出Cookie,就可以冒充已经登陆的用户。

即便很多网站使用了JWT,登陆凭证( Token令牌 )是存储在浏览器上面的。所以用XSS脚本可以轻松的从Storage中提取出Token,黑客依然可以轻松的冒充已经登陆的用户。

所以避免XSS攻击最有效的办法就是对用户输入的数据进行转义,然后存储到数据库里面。等到视图层渲染HTML页面的时候。转义后的文字是不会被当做JavaScript执行的,这就可以抵御XSS攻击。

5.2 导入依赖库

因为Hutool工具包带有XSS转义的工具类,所以我们要导入Hutool,然后利用Servlet规范提供的请求包装类,定义数据转义功能。

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.4.0</version>
</dependency>

5.3 定义请求包装类

我们平时写Web项目遇到的HttpServletRequest,它其实是个接口。如果我们想要重新定义请求类,扩展这个接口是最不应该的。因为 HttpServletRequest 接口中抽象方法太多了,我们逐一实现起来太耗费时间。所以我们应该挑选一个简单一点的自定义请求类的方式。那就是继承HttpservletRequestWrapper父类。

JavaEE只是一个标准,具体的实现由各家应用服务器厂商来完成。比如说Tomcat 在实现Servlet规范的时候,就自定义了HttpServletRequest 接口的实现类。同时JavaEE规范还定义了HttpSerrvletRequestWrapper,这个类是请求类的包装类,用上了装饰器模式。不得不说这里用到的设计模式真的非常棒,无论各家应用服务器厂商怎么去实现 HttpservletRequest 接口,用户想要自定义请求,只需要继承HttpServletRequestwrapper,对应覆盖某个方法即可,然后把请求传入请求包装类,装饰器模式就会替代请求对象中对应的某个方法。用户的代码和服务器厂商的代码完全解耦,我们不用关心HttpServletRequest接口是怎么实现的,借助于包装类我们可以随意修改请求中的方法。

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HtmlUtil;
import cn.hutool.json.JSONUtil;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

	public XssHttpServletRequestWrapper(HttpServletRequest request) {
		super(request);
	}

	@Override
	public String getParameter(String name) {
		String value = super.getParameter(name);
		if (!StrUtil.hasEmpty(value)) {
			value = HtmlUtil.filter(value);
		}
		return value;
	}

	@Override
	public String[] getParameterValues(String name) {
		String[] values = super.getParameterValues(name);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				String value = values[i];
				if (!StrUtil.hasEmpty(value)) {
					value = HtmlUtil.filter(value);
				}
				values[i] = value;
			}
		}
		return values;
	}

	@Override
	public Map<String, String[]> getParameterMap() {
		Map<String, String[]> parameters = super.getParameterMap();
		Map<String, String[]> map = new LinkedHashMap<>();
		if (parameters != null) {
			for (String key : parameters.keySet()) {
				String[] values = parameters.get(key);
				for (int i = 0; i < values.length; i++) {
					String value = values[i];
					if (!StrUtil.hasEmpty(value)) {
						value = HtmlUtil.filter(value);
					}
					values[i] = value;
				}
				map.put(key, values);
			}
		}
		return map;
	}

	@Override
	public String getHeader(String name) {
		String value = super.getHeader(name);
		if (!StrUtil.hasEmpty(value)) {
			value = HtmlUtil.filter(value);
		}
		return value;
	}

	@Override
	public ServletInputStream getInputStream() throws IOException {
		InputStream in = super.getInputStream();
		StringBuffer body = new StringBuffer();
		InputStreamReader reader = new InputStreamReader(in, Charset.forName("UTF-8"));
		BufferedReader buffer = new BufferedReader(reader);
		String line = buffer.readLine();

		while (line!=null) {
			body.append(line);
			line = buffer.readLine();
		}

		buffer.close();
		reader.close();
		in.close();

		Map<String,Object> map = JSONUtil.parseObj(body.toString());
		Map<String, Object> resultMap = new HashMap(map.size());
		for (String key : map.keySet()) {
			Object val = map.get(key);
			if (map.get(key) instanceof String) {
				resultMap.put(key, HtmlUtil.filter(val.toString()));
			} else {
				resultMap.put(key, val);
			}
		}

		String str = JSONUtil.toJsonStr(resultMap);
		final ByteArrayInputStream bain = new ByteArrayInputStream(str.getBytes());

		return new ServletInputStream() {
			@Override
			public int read() throws IOException {
				return bain.read();
			}
			@Override
			public boolean isFinished() {
				return false;
			}
			@Override
			public boolean isReady() {
				return false;
			}
			@Override
			public void setReadListener(ReadListener listener) {
			}
		};


	}
}
posted @ 2024-02-24 23:56  shine-rainbow  阅读(75)  评论(0编辑  收藏  举报