spring boot(学习笔记第五课)
- 自定义错误页,CORS(跨域支持)
学习内容:
- 自定义错误页
- CORS(跨域支持)
1. 自定义错误页
- 简单配置错误页面。
通常如果访问没有定义的url
,spring boot
会返回默认的页面。
这个页面通常不是想要的页面,需要进行自己的定制开发。
- 首先学习下
spring boot
的错误页面处理。spring boot
使用BasicErrorController
来进行处理。在IntelliJ IDEA
中双击shift
键,查看BasicErrorController
的源代码。MediaType == text/html
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); }
MediaType == text/html以外
@RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity<>(status); } Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity<>(body, status); }
- 进一步查看
resolveErrorView
。
注意到返回错误页面的处理的时候,spring boot
的处理是ModelAndView modelAndView = resolveErrorView(request, response, status, model);
,也就是说,这里会ErrorViewResolver
进行错误页面的生成处理。进一步,查看DefaultErrorViewResolver
的错误页面处理。
这里知道,默认的错误页面是从static { Map<Series, String> views = new EnumMap<>(Series.class); views.put(Series.CLIENT_ERROR, "4xx"); views.put(Series.SERVER_ERROR, "5xx"); SERIES_VIEWS = Collections.unmodifiableMap(views); } @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName; TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); if (provider != null) { return new ModelAndView(errorViewName, model); } return resolveResource(errorViewName, model); }
/error/4xx
和/error/5xx
进行查找的,实际上,spring boot
的规则有两种:- 可以使用4xx.html/5xx.html进行定制,那么这种粒度较粗,不用的
error code
使用同样的页面。 - 可以使用404.html/500.html进行定制,那么这种粒度较粗,不用的
error code
使用同样的页面。<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org/"> <head> <meta charset="utf-8"> <title>Title</title> </head> <body> <div>404</div> </body> </html>
- 可以使用4xx.html/5xx.html进行定制,那么这种粒度较粗,不用的
- 使用静态资源来定义
classpath:/static/error/404.html
和classpath:/static/error/500.html
之后访问未定义的资源。
- 静态资源表达的页面没有
thymeleaf
页面灵活,尝试用thymeleaf进行展示。在/resources/templates/error/4xx.html
和/resources/templates/error/5xx.html
。
加入<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org/"> <head> <meta charset="utf-8"> <title>Title</title> </head> <body> <table border="1"> <tr> <td>timestamp</td> <td th:text="${timestamp}"></td> </tr> <tr> <td>status</td> <td th:text="${status}"></td> </tr> <tr> <td>error</td> <td th:text="${error}"></td> </tr> <tr> <td>message</td> <td th:text="${message}"></td> </tr> <tr> <td>path</td> <td th:text="${path}"></td> </tr> </table> </body> </html>
thymeleaf
依赖,点击pom.xml,并maven
->重新加载项目
。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
- 查看404的错误页面。
还是不想期待的返回一个详细的thymeleaf
页面,而是原来的静态页面/resources/static/error/404.html
页面。由此得出结论,同样的有4xx.html和404.html,优先找到最匹配的html作为view
。
- 复杂配置(自定义error数据)
- 查看
BasicErrorController
的代码
看到spring boot
通过调用getErrorAttributes
这个方法,将Error
发生时候的message
和status
一起数据都放在了这里,在结果画面表示的时候,从这个ErrorAttributes
中取得,这样都会最终调用DefaultErrorAttributes
这个类。
查看ErrorMvcAutoConfiguration
,会看到下面的代码,如果没有定义实现ErrorAttributes
接口的类,那么spring boot
将生成默认的DefaultErrorAttributes
。@Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); }
- 通过继承
DefaultErrorAttributes
,进行特殊的属性的追加。public Map<String,Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions includeStackTrace){ Map<String,Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace); errorAttributes.put("custommsg","出错了!"); errorAttributes.remove("error"); return errorAttributes; }
- 更改
thymeleaf
的templates/error/404.html
的页面代码,使用custommsg
来取得message
。<tr> <td>message</td> <td th:text="${custommsg}"></td> </tr>
- 最后显示
not found
的页面。
- 自定义
error
视图
- 查看
ErrorMvcAutoConfiguration
的代码
可以看到默认的,如果没有实现出@Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean(ErrorViewResolver.class) DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resources); }
ErrorViewResolver
接口的类,那么就会使用默认的DefaultErrorViewResolver
,来根据上面的处理来查找error
的处理view
。 - 实现出自己的
ErrorViewResolver
接口的类。@Component public class CustomErrorViewResolver implements ErrorViewResolver { @Override public ModelAndView resolveErrorView(HttpServletRequest httpServletRequest, HttpStatus httpStatus, Map<String,Object> model){ ModelAndView mv = new ModelAndView("errorPage"); model.put("error","错误信息!"); mv.addAllObjects(model); return mv } ```
- 自定义
error view
,生成文件/resources/templates/errorPage.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org/"> <head> <meta charset="utf-8"> <title>CustomErrorViewResolver></title> </head> <body> <table border="1"> <tr> <td>timestamp</td> <td th:text="${timestamp}"></td> </tr> <tr> <td>status</td> <td th:text="${status}"></td> </tr> <tr> <td>error</td> <td th:text="${error}"></td> </tr> <tr> <td>message</td> <td th:text="${custommsg}"></td> </tr> <tr> <td>path</td> <td th:text="${path}"></td> </tr> </table> </body> </html>
- 完全自定义
controller
还可以完全通过自定义BasicErrorController
,完全控制错误页面的表示。
public class CustomBasicErrorController extends BasicErrorController {
@Autowired
public CustomBasicErrorController(ErrorAttributes errorAttributes,
ServerProperties serverProperties,
List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes,
serverProperties.getError(),
errorViewResolvers);
}
@Override
public ModelAndView errorHtml(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
HttpStatus status = getStatus(httpServletRequest);
Map<String, Object> model = getErrorAttributes(httpServletRequest,
ErrorAttributeOptions.defaults());
model.put("custommsg", "出错了!!");
model.put("error", "采用了完全自定义错误controller!");
return new ModelAndView("errorPage", model);
}
@Override
public ResponseEntity<Map<String,Object>>error(HttpServletRequest httpServletRequest){
Map<String,Object> model = getErrorAttributes(httpServletRequest,
ErrorAttributeOptions.defaults());
model.put("custommsg","出错了!!");
HttpStatus status = getStatus(httpServletRequest);
return new ResponseEntity<Map<String, Object>>(model,status);
}
}
之后访问,让错误页面表示出来。
2. CORS(跨域支持)
- 了解什么是
CSRF
CSRF
(Cross-site request forgery,跨站请求伪造)也被称为One Click Attack或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。
CSRF的攻击原理:
可以看出,黑客系统可以通过银行系统对于用户浏览器的信任(合法的cookie情报),当而皇之的对银行系统进行非法的get
或者post
请求。
因而现代浏览器的一个策略就是禁止跨域请求。
- CORS(
Cross-Origin Resource Sharing
跨域支持)
对于在一个域内的spring boot
的应用程序,通过浏览器的ajax
等手段访问其他域,是非常正常的情况,所以衍生出了CORS(跨域支持)的资源共享技术。
JavaEE中的JSONP能够使用非官方的方法解决这个跨域共享问题,但是只能支持Get
请求,这是一个巨大的缺陷。
接下来说明一下,服务器端是如何支持CORS的。
3. spring boot
中进行配置CORS(Cross-Origin Resource Sharing
跨域支持)
- 在
/resources/static/
中增加jquery.js。
>>>jquery的官网 - 在/resources/static/中增加index.html进行访问。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>CORS(跨域支持)</title> <script src="jquery.js"></script> </head> <body> <div id="contentDiv"></div> <div id="deleteResult"></div> <input type="button" value="提交数据" onclick="getData()"><br> <input type="button" value="删除数据" onclick="deleteData()"><br> <script> function deleteData(){ $.ajax({ url:'https://localhost:8080/book/99, type:'delete', success: function(msg){ $("#deleteResult").html(msg); } }) } </script> </body> </html>
- 在
controller
里面打开CORS(跨域支持)@DeleteMapping("/book/{id}") @CrossOrigin(value = "https://localhost:8081" ,maxAge = 1800,allowedHeaders = "*") @ResponseBody public String book(@PathVariable Long id){ return String.valueOf(id); }
- 将资源共享的server设定成8080端口,采用
java -jar
的方式启动。 - 将请求资源的server使用
IntelliJ IDEA
进行启动,端口改修成9000。 - 在浏览其中访问
https://localhost:9000/index.html
,点击删除数据
按钮,成功访问了8080端口的另一个域,实现了CORS(跨域支持)