log中文乱码 springboot_springboot + shiro 权限注解、统一异常处理、请求乱码解决
springboot + shiro 權(quán)限注解、統(tǒng)一異常處理、請(qǐng)求亂碼解決
前篇
后臺(tái)權(quán)限管理系統(tǒng)
20200808新版本更新
版本升級(jí)及內(nèi)容優(yōu)化版本,改動(dòng)內(nèi)容:
版本更新,springboot從1.5+升級(jí)到2.1+;
權(quán)限緩存使用redis;
驗(yàn)證碼使用redis;
權(quán)限驗(yàn)證完善。
相關(guān):
基于前篇,新增功能:
新增shiro權(quán)限注解;
請(qǐng)求亂碼問(wèn)題解決;
統(tǒng)一異常處理。
源碼已集成到項(xiàng)目中:
shiro注解的使用
shiro權(quán)限注解
Shiro 提供了相應(yīng)的注解用于權(quán)限控制,如果使用這些注解就需要使用AOP 的功能來(lái)進(jìn)行判斷,如Spring AOP;Shiro 提供了Spring AOP 集成用于權(quán)限注解的解析和驗(yàn)證。
@RequiresAuthentication
表示當(dāng)前Subject已經(jīng)通過(guò)login 進(jìn)行了身份驗(yàn)證;即Subject.isAuthenticated()返回true。
@RequiresUser
表示當(dāng)前Subject已經(jīng)身份驗(yàn)證或者通過(guò)記住我登錄的。
@RequiresGuest
表示當(dāng)前Subject沒(méi)有身份驗(yàn)證或通過(guò)記住我登錄過(guò),即是游客身份。
@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)
@RequiresRoles(value={“admin”})
@RequiresRoles({“admin“})
表示當(dāng)前Subject需要角色admin 和user。
@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR)
表示當(dāng)前Subject需要權(quán)限user:a或user:b。
Shiro的認(rèn)證注解處理是有內(nèi)定的處理順序的,如果有多個(gè)注解的話,前面的通過(guò)了會(huì)繼續(xù)檢查后面的,若不通過(guò)則直接返回,處理順序依次為(與實(shí)際聲明順序無(wú)關(guān)):
RequiresRoles
RequiresPermissions
RequiresAuthentication
RequiresUser
RequiresGuest
以上注解既可以用在controller中,也可以用在service中使用;
建議將shiro注解放在controller中,因?yàn)槿绻鹲ervice層使用了spring的事物注解,那么shiro注解將無(wú)效。
shiro權(quán)限注解springAOP配置
shiro權(quán)限注解要生效,必須配置springAOP通過(guò)設(shè)置shiro的SecurityManager進(jìn)行權(quán)限驗(yàn)證。
/**
*
* @描述:開(kāi)啟Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP掃描使用Shiro注解的類(lèi),并在必要時(shí)進(jìn)行安全邏輯驗(yàn)證
* 配置以下兩個(gè)bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可實(shí)現(xiàn)此功能
* Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor(保證實(shí)現(xiàn)了Shiro內(nèi)部lifecycle函數(shù)的bean執(zhí)行) has run
* 不使用注解的話,可以注釋掉這兩個(gè)配置
* @創(chuàng)建人:wyait
* @創(chuàng)建時(shí)間:2018年5月21日 下午6:07:56
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
springboot異常處理原理
場(chǎng)景:當(dāng)用戶(hù)正常訪問(wèn)網(wǎng)站時(shí),因?yàn)槟撤N原因后端出現(xiàn)exception的時(shí)候,直接暴露異常信息或頁(yè)面顯示給用戶(hù);
這種操作體驗(yàn)不是我們想要的。所以要對(duì)異常進(jìn)行統(tǒng)一管理,能提高用戶(hù)體驗(yàn)的同時(shí),后臺(tái)能詳細(xì)定位到異常的問(wèn)題點(diǎn)。
springboot異常概況
Spring Boot提供了默認(rèn)的統(tǒng)一錯(cuò)誤頁(yè)面,這是Spring MVC沒(méi)有提供的。在理解了Spring Boot提供的錯(cuò)誤處理相關(guān)內(nèi)容之后,我們可以方便的定義自己的錯(cuò)誤返回的格式和內(nèi)容。
編寫(xiě)by zero異常
在home頁(yè)面,手動(dòng)創(chuàng)建兩個(gè)異常:普通異常和異步異常!
前端頁(yè)面:
普通請(qǐng)求異常:
點(diǎn)擊
ajax異步請(qǐng)求異常:
點(diǎn)擊
...
//js代碼
function ajaxError(){
$.get("/error/ajaxError",function(data){
layer.alert(data);
});
}
后端代碼:
/**
*
* @描述:普通請(qǐng)求異常
* @創(chuàng)建人:wyait
* @創(chuàng)建時(shí)間:2018年5月24日 下午5:30:50
*/
@RequestMapping("getError")
public void toError(){
System.out.println(1/0);
}
/**
*
* @描述:異步異常
* @創(chuàng)建人:wyait
* @創(chuàng)建時(shí)間:2018年5月24日 下午5:30:39
*/
@RequestMapping("ajaxError")
@ResponseBody
public String ajaxError(){
System.out.println(1/0);
return "異步請(qǐng)求成功!";
}
異常效果
普通異常:
console錯(cuò)誤信息:
[2018-05-25 09:30:04.669][http-nio-8077-exec-8][ERROR][org.apache.juli.logging.DirectJDKLog][181]:Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause
java.lang.ArithmeticException: / by zero
at com.wyait.manage.web.error.IndexErrorController.toError(IndexErrorController.java:18) ~[classes/:?]
...
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_131]
...
[2018-05-25 09:30:04.676][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.handler.AbstractHandlerMethodMapping][317]:Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)]
[2018-05-25 09:30:04.676][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.handler.AbstractHandlerMethodMapping][317]:Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)]
[2018-05-25 09:30:04.676][http-nio-8077-exec-8][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'basicErrorController'
[2018-05-25 09:30:04.676][http-nio-8077-exec-8][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'basicErrorController'
...
[2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.view.ContentNegotiatingViewResolver][263]:Requested media types are [text/html, text/html;q=0.8] based on Accept header types and producible media types [text/html])
[2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.view.ContentNegotiatingViewResolver][263]:Requested media types are [text/html, text/html;q=0.8] based on Accept header types and producible media types [text/html])
[2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'error'
[2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'error'
[2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.view.ContentNegotiatingViewResolver][338]:Returning [org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView@6ffd99fb] based on requested media type 'text/html'
[2018-05-25 09:30:04.686][http-nio-8077-exec-8][DEBUG][org.springframework.web.servlet.view.ContentNegotiatingViewResolver][338]:Returning [org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView@6ffd99fb] based on requested media type 'text/html'
...
通過(guò)日志可知,springboot返回的錯(cuò)誤頁(yè)面,是通過(guò):org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml處理返回ModelAndView。
異步異常:
console日志信息:
[2018-05-25 09:31:19.958][http-nio-8077-exec-6][ERROR][org.apache.juli.logging.DirectJDKLog][181]:Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause
java.lang.ArithmeticException: / by zero
at com.wyait.manage.web.error.IndexErrorController.ajaxError(IndexErrorController.java:29) ~[classes/:?]
...
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_131]
...
[2018-05-25 09:31:19.960][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.handler.AbstractHandlerMethodMapping][317]:Returning handler method [public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)]
[2018-05-25 09:31:19.960][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.handler.AbstractHandlerMethodMapping][317]:Returning handler method [public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)]
[2018-05-25 09:31:19.960][http-nio-8077-exec-6][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'basicErrorController'
[2018-05-25 09:31:19.960][http-nio-8077-exec-6][DEBUG][org.springframework.beans.factory.support.AbstractBeanFactory][251]:Returning cached instance of singleton bean 'basicErrorController'
...
[2018-05-25 09:31:19.961][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor][234]:Written [{timestamp=Fri May 25 09:31:19 CST 2018, status=500, error=Internal Server Error, exception=java.lang.ArithmeticException, message=/ by zero, path=/error/ajaxError}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@2729eae5]
[2018-05-25 09:31:19.961][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor][234]:Written [{timestamp=Fri May 25 09:31:19 CST 2018, status=500, error=Internal Server Error, exception=java.lang.ArithmeticException, message=/ by zero, path=/error/ajaxError}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@2729eae5]
[2018-05-25 09:31:19.961][http-nio-8077-exec-6][DEBUG][org.springframework.web.servlet.DispatcherServlet][1048]:Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
...
通過(guò)日志可知,springboot返回的錯(cuò)誤信息,是通過(guò):org.springframework.boot.autoconfigure.web.BasicErrorController.error處理返回ResponseEntity。
異常都是通過(guò)org.springframework.boot.autoconfigure.web.BasicErrorController控制處理的。
springboot異常處理解析
查看org.springframework.boot.autoconfigure.web包下面的類(lèi),跟蹤springboot對(duì)error異常處理機(jī)制。自動(dòng)配置通過(guò)一個(gè)MVC error控制器處理錯(cuò)誤
通過(guò)spring-boot-autoconfigure引入
查看springboot 處理error的類(lèi)
springboot的自動(dòng)配置,在web中處理error相關(guān)的自動(dòng)配置類(lèi):ErrorMvcAutoConfiguration。查看與處理error相關(guān)的類(lèi):
ErrorMvcAutoConfiguration.class
ErrorAttibutes.class
ErrorController.class
ErrorProperties.class
ErrorViewResolver.class
...
ErrorAutoConfiguration類(lèi)源碼//TODO
ErrorAutoConfiguration注冊(cè)的bean
//4個(gè)BEAN
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
this.errorViewResolvers);
}
@Bean
public ErrorPageCustomizer errorPageCustomizer() {
return new ErrorPageCustomizer(this.serverProperties);
}
@Bean
public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {
return new PreserveErrorControllerTargetClassPostProcessor();
}
DefaultErrorAttributes類(lèi)
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes
implements ErrorAttributes, HandlerExceptionResolver, Ordered {
...
}
ErrorAttributes:
public interface ErrorAttributes {
Map getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace);
Throwable getError(RequestAttributes requestAttributes);
}
HandlerExceptionResolver:
public interface HandlerExceptionResolver {
/**
* Try to resolve the given exception that got thrown during handler execution,
* returning a {@link ModelAndView} that represents a specific error page if appropriate.
*/
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}
DefaultErrorAttributes類(lèi):
實(shí)現(xiàn)了ErrorAttributes接口,當(dāng)處理/error錯(cuò)誤頁(yè)面時(shí),可以在該bean中讀取錯(cuò)誤信息響應(yīng)返回;
實(shí)現(xiàn)了HandlerExceptionResolver接口。
debug跟蹤源碼:即DispatcherServlet在doDispatch過(guò)程中有異常拋出時(shí):
一. 先由HandlerExceptionResolver.resolveException解析異常并保存在request中;
二. 再DefaultErrorAttributes.getErrorAttributes處理;DefaultErrorAttributes在處理過(guò)程中,從request中獲取錯(cuò)誤信息,將錯(cuò)誤信息保存到RequestAttributes中;
三. 最后在獲取錯(cuò)誤信息getError(RequestAttributes)時(shí),從RequestAttributes中取到錯(cuò)誤信息。
BasicErrorController類(lèi)
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
private final ErrorProperties errorProperties;
...
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}
@RequestMapping
@ResponseBody
public ResponseEntity> error(HttpServletRequest request) {
Map body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity>(body, status);
}
...
}
resolveErrorView方法(查找=error/“錯(cuò)誤狀態(tài)碼”;的資源):
如果不是異常請(qǐng)求,會(huì)執(zhí)行resolveErrorView方法;該方法會(huì)先在默認(rèn)或配置的靜態(tài)資源路徑下查找error/HttpStatus(錯(cuò)誤狀態(tài)碼)的資源文件,如果沒(méi)有;使用默認(rèn)的error頁(yè)面。
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
...
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map model) {
//status:異常錯(cuò)誤狀態(tài)碼
ModelAndView modelAndView = resolve(String.valueOf(status), 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 model) {
//視圖名稱(chēng),默認(rèn)是error/+“status”錯(cuò)誤狀態(tài)碼;比如:error/500、error/404
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/500或error/404等頁(yè)面
private ModelAndView resolveResource(String viewName, Map model) {
for (String location : this.resourceProperties.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
...
}
BasicErrorController根據(jù)Accept頭的內(nèi)容,輸出不同格式的錯(cuò)誤響應(yīng)。比如針對(duì)瀏覽器的請(qǐng)求生成html頁(yè)面,針對(duì)其它請(qǐng)求生成json格式的返回。
可以通過(guò)配置error/HttpStatus頁(yè)面實(shí)現(xiàn)自定義錯(cuò)誤頁(yè)面。
ErrorPageCustomizer類(lèi)
/**
* {@link EmbeddedServletContainerCustomizer} that configures the container's error
* pages.
*/
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
private final ServerProperties properties;
protected ErrorPageCustomizer(ServerProperties properties) {
this.properties = properties;
}
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()
+ this.properties.getError().getPath());
errorPageRegistry.addErrorPages(errorPage);
}
@Override
public int getOrder() {
return 0;
}
}
將錯(cuò)誤頁(yè)面注冊(cè)到內(nèi)嵌的tomcat的servlet容器中。
PreserveErrorControllerTargetClassPostProcessor實(shí)現(xiàn)BeanFactoryPostProcessor接口,可以修改BEAN的配置信息
ErrorAutoConfiguration內(nèi)的兩個(gè)配置
//2個(gè)config配置
@Configuration
static class DefaultErrorViewResolverConfiguration {
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
ResourceProperties resourceProperties) {
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
}
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext,
this.resourceProperties);
}
}
@Configuration
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {
private final SpelView defaultErrorView = new SpelView(
"
Whitelabel Error Page
"+ "
This application has no explicit mapping for /error, so you are seeing this as a fallback.
"+ "
${timestamp}"+ "
There was an unexpected error (type=${error}, status=${status})."+ "
${message}");@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
// If the user adds @EnableWebMvc then the bean name view resolver from
// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
@Bean
@ConditionalOnMissingBean(BeanNameViewResolver.class)
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
DefaultErrorViewResolverConfiguration:默認(rèn)的error視圖解析配置;
WhitelabelErrorViewConfiguration:默認(rèn)設(shè)置了/error的頁(yè)面,和Whitelabel Error Page頁(yè)面響應(yīng)內(nèi)容。
如果Spring MVC在處理業(yè)務(wù)的過(guò)程中拋出異常,會(huì)被 Servlet 容器捕捉到,Servlet 容器再將請(qǐng)求轉(zhuǎn)發(fā)給注冊(cè)好的異常處理映射 /error 做響應(yīng)處理。
springboot配置文件默認(rèn)error相關(guān)配置
springboot配置文件application.properties中關(guān)于error默認(rèn)配置:
server.error.include-stacktrace=never # When to include a "stacktrace" attribute.
server.error.path=/error # Path of the error controller.
server.error.whitelabel.enabled=true # Enable the default error page displayed in browsers in case of a server error.
springboot 自定義異常處理
通過(guò)跟蹤springboot對(duì)異常處理得源碼跟蹤,根據(jù)業(yè)務(wù)需要,可以細(xì)分前端響應(yīng)的錯(cuò)誤頁(yè)面,也可以統(tǒng)一使用/error頁(yè)面+錯(cuò)誤提示信息進(jìn)行處理。
根據(jù)自己的需求自定義異常處理機(jī)制;具體可實(shí)施的操作如下:
可以通過(guò)配置error/HttpStatus(錯(cuò)誤狀態(tài)碼)頁(yè)面實(shí)現(xiàn)自定義錯(cuò)誤頁(yè)面【底層實(shí)現(xiàn),詳見(jiàn):BasicErrorController源碼】;
可以實(shí)現(xiàn)BasicErrorController,自定義普通請(qǐng)求的異常頁(yè)面響應(yīng)信息和異步請(qǐng)求的響應(yīng)信息,統(tǒng)一使用/error頁(yè)面進(jìn)行錯(cuò)誤響應(yīng)提示;
自定義實(shí)現(xiàn)ErrorAttributes接口,覆蓋DefaultErrorAttributes實(shí)現(xiàn),或是繼承DefaultErrorAttributes類(lèi),重寫(xiě)里面的方法【TODO,不推薦】。
1和2的方法可單獨(dú)使用,也可以結(jié)合使用。
自定義異常頁(yè)面
可以根據(jù)不同的錯(cuò)誤狀態(tài)碼,在前端細(xì)分不同的響應(yīng)界面給用戶(hù)進(jìn)行提示;資源路徑必須是:靜態(tài)資源路徑下/error/HttpStats(比如:/error/404等)
自定義異常頁(yè)面
404友情提示訪問(wèn)的資源未找到(404)
404.html
500.html等,這里只演示404。
統(tǒng)一異常處理
普通請(qǐng)求,前端使用error頁(yè)面+自定義錯(cuò)誤響應(yīng)信息;
其他請(qǐng)求(異步),統(tǒng)一自定義錯(cuò)誤響應(yīng)信息,規(guī)范處理異步響應(yīng)的錯(cuò)誤判斷和處理。
使用springMVC注解ControllerAdvice
/**
*
* @項(xiàng)目名稱(chēng):wyait-manage
* @類(lèi)名稱(chēng):GlobalExceptionHandler
* @類(lèi)描述:統(tǒng)一異常處理,包括【普通調(diào)用和ajax調(diào)用】
* ControllerAdvice來(lái)做controller內(nèi)部的全局異常處理,但對(duì)于未進(jìn)入controller前的異常,該處理方法是無(wú)法進(jìn)行捕獲處理的,SpringBoot提供了ErrorController的處理類(lèi)來(lái)處理所有的異常(TODO)。
* 1.當(dāng)普通調(diào)用時(shí),跳轉(zhuǎn)到自定義的錯(cuò)誤頁(yè)面;2.當(dāng)ajax調(diào)用時(shí),可返回約定的json數(shù)據(jù)對(duì)象,方便頁(yè)面統(tǒng)一處理。
* @創(chuàng)建人:wyait
* @創(chuàng)建時(shí)間:2018年5月22日 上午11:44:55
* @version:
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory
.getLogger(GlobalExceptionHandler.class);
public static final String DEFAULT_ERROR_VIEW = "error";
/**
*
* @描述:針對(duì)普通請(qǐng)求和ajax異步請(qǐng)求的異常進(jìn)行處理
* @創(chuàng)建人:wyait
* @創(chuàng)建時(shí)間:2018年5月22日 下午4:48:58
* @param req
* @param e
* @return
* @throws Exception
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ModelAndView errorHandler(HttpServletRequest request,
HttpServletResponse response, Exception e) {
logger.debug(getClass().getName() + ".errorHandler】統(tǒng)一異常處理:request="+request);
ModelAndView mv=new ModelAndView();
logger.info(getClass().getName() + ".errorHandler】統(tǒng)一異常處理:"+e.getMessage());
//1 獲取錯(cuò)誤狀態(tài)碼
HttpStatus httpStatus=getStatus(request);
logger.info(getClass().getName() + ".errorHandler】統(tǒng)一異常處理!錯(cuò)誤狀態(tài)碼httpStatus:"+httpStatus);
//2 返回錯(cuò)誤提示
ExceptionEnum ee=getMessage(httpStatus);
//3 將錯(cuò)誤信息放入mv中
mv.addObject("type", ee.getType());
mv.addObject("code", ee.getCode());
mv.addObject("msg", ee.getMsg());
if(!ShiroFilterUtils.isAjax(request)){
//不是異步請(qǐng)求
mv.setViewName(DEFAULT_ERROR_VIEW);
logger.debug(getClass().getName() + ".errorHandler】統(tǒng)一異常處理:普通請(qǐng)求。");
}
logger.debug(getClass().getName() + ".errorHandler】統(tǒng)一異常處理響應(yīng)結(jié)果:MV="+mv);
return mv;
}
...
}
運(yùn)行測(cè)試:先走GlobalExceptionHandler(使用注解@ControllerAdvice)類(lèi)里面的方法,而后又執(zhí)行了BasicErrorController方法;被springboot自帶的BasicErrorController覆蓋。
實(shí)現(xiàn)springboot的AbstractErrorController
自定義實(shí)現(xiàn)AbstractErrorController,添加響應(yīng)的錯(cuò)誤提示信息。
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
ModelAndView mv = new ModelAndView(ERROR_PATH);
/** model對(duì)象包含了異常信息 */
Map model = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.TEXT_HTML));
// 1 獲取錯(cuò)誤狀態(tài)碼(也可以根據(jù)異常對(duì)象返回對(duì)應(yīng)的錯(cuò)誤信息)
HttpStatus httpStatus = getStatus(request);
// 2 返回錯(cuò)誤提示
ExceptionEnum ee = getMessage(httpStatus);
Result result = new Result(
String.valueOf(ee.getType()), ee.getCode(), ee.getMsg());
// 3 將錯(cuò)誤信息放入mv中
mv.addObject("result", result);
logger.info("統(tǒng)一異常處理【" + getClass().getName()
+ ".errorHtml】統(tǒng)一異常處理!錯(cuò)誤信息mv:" + mv);
return mv;
}
@RequestMapping
@ResponseBody
//設(shè)置響應(yīng)狀態(tài)碼為:200,結(jié)合前端約定的規(guī)范處理。也可不設(shè)置狀態(tài)碼,前端ajax調(diào)用使用error函數(shù)進(jìn)行控制處理
@ResponseStatus(value=HttpStatus.OK)
public Result error(HttpServletRequest request, Exception e) {
/** model對(duì)象包含了異常信息 */
Map model = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.TEXT_HTML));
// 1 獲取錯(cuò)誤狀態(tài)碼(也可以根據(jù)異常對(duì)象返回對(duì)應(yīng)的錯(cuò)誤信息)
HttpStatus httpStatus = getStatus(request);
// 2 返回錯(cuò)誤提示
ExceptionEnum ee = getMessage(httpStatus);
Result result = new Result(
String.valueOf(ee.getType()), ee.getCode(), ee.getMsg());
// 3 將錯(cuò)誤信息返回
// ResponseEntity
logger.info("統(tǒng)一異常處理【" + getClass().getName()
+ ".error】統(tǒng)一異常處理!錯(cuò)誤信息result:" + result);
return result;
}
針對(duì)異步請(qǐng)求,統(tǒng)一指定響應(yīng)狀態(tài)碼:200;也可以不指定,前端在處理異步請(qǐng)求的時(shí)候,可以通過(guò)ajax的error函數(shù)進(jìn)行控制。
這里是繼承的AbstractErrorController類(lèi),自定義實(shí)現(xiàn)統(tǒng)一異常處理,也可以直接實(shí)現(xiàn)ErrorController接口。
前端ajax異步統(tǒng)一處理:
通過(guò)約定,前端ajax異步請(qǐng)求,進(jìn)行統(tǒng)一的錯(cuò)誤處理。
/**
* 針對(duì)不同的錯(cuò)誤可結(jié)合業(yè)務(wù)自定義處理方式
* @param result
* @returns {Boolean}
*/
function isError(result){
var flag=true;
if(result && result.status){
flag=false;
if(result.status == '-1' || result.status=='-101' || result.status=='400' || result.status=='404' || result.status=='500'){
layer.alert(result.data);
}else if(result.status=='403'){
layer.alert(result.data,function(){
//跳轉(zhuǎn)到未授權(quán)界面
window.location.href="/403";
});
}
}
return flag;//返回true
}
使用方式:
...
success:function(data){
//異常過(guò)濾處理
if(isError(data)){
alert(data);
}
},
...
error.html頁(yè)面:
出錯(cuò)了
()
測(cè)試效果
普通請(qǐng)求:
異步請(qǐng)求:
線上get請(qǐng)求亂碼
問(wèn)題描述
前臺(tái)通過(guò)html頁(yè)面,發(fā)送請(qǐng)求到后臺(tái)查詢(xún)數(shù)據(jù),在日志中打印的sql語(yǔ)句顯示傳入的參數(shù)亂碼:
SELECT ...
[2018-05-11 09:15:00.582][http-bio-8280-exec-2][DEBUG][org.apache.ibatis.logging.jdbc.BaseJdbcLogger][159]:==> Parameters: 1(Integer), ?è′o(String)
[2018-05-11 09:15:00.585][http-bio-8280-exec-2][DEBUG][org.apache.ibatis.logging.jdbc.BaseJdbcLogger][159]:<== Total: 1
...
本地windows開(kāi)發(fā)環(huán)境測(cè)試沒(méi)有亂碼問(wèn)題;
請(qǐng)求信息
前端頁(yè)面發(fā)送get請(qǐng)求,瀏覽器默認(rèn)對(duì)get請(qǐng)求路徑進(jìn)行URL編碼處理。
后臺(tái)Controller打印的日志
分頁(yè)查詢(xún)用戶(hù)列表!搜索條件:userSearch:UserSearchDTO{page=1, limit=10, uname='??', umobile='', insertTimeStart='', insertTimeEnd=''},page:1,每頁(yè)記錄數(shù)量limit:10,請(qǐng)求編碼:UTF-8
Controller層在接收到這個(gè)uname參數(shù)時(shí),已經(jīng)是亂碼,ISO-8859-1解碼后的結(jié)果。
請(qǐng)求參數(shù)編碼流程
前端頁(yè)面發(fā)送get請(qǐng)求,瀏覽器默認(rèn)在中文的UTF-8后加上上%得到URL編碼,比如:%e8%b4%b9%e7...;
get請(qǐng)求到tomcat應(yīng)用服務(wù)器后,會(huì)以默認(rèn)的ISO-8859-1進(jìn)行解碼;
在controller中,接收到的是經(jīng)過(guò)URL編碼和iso-8859-1解碼后的參數(shù)值。
具體編碼細(xì)節(jié):TODO
解決方案
項(xiàng)目編碼配置【可以不配置】
開(kāi)發(fā)前,默認(rèn)必須統(tǒng)一編碼環(huán)境;正常都是設(shè)置為utf-8。
spring boot 與spring mvc不同,在web應(yīng)用中,spring boot默認(rèn)的編碼格式為UTF-8,而spring mvc的默認(rèn)編碼格式為iso-8859-1。
spring boot項(xiàng)目中如果沒(méi)有特殊需求,該編碼不需要修改。如果要強(qiáng)制其他編碼格式,spring boot提供了設(shè)置方式:
通過(guò)application.properties配置文件設(shè)置:
# 默認(rèn)utf-8配置
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8
此時(shí)攔截器中返回的中文已經(jīng)不亂碼了,但是controller中返回的數(shù)據(jù)可能會(huì)依舊亂碼。
參考spring MVC的方式,自定義實(shí)現(xiàn)WebMvcConfigurerAdapter類(lèi),處理響應(yīng)數(shù)據(jù)亂碼問(wèn)題:
@Bean
public HttpMessageConverter responseBodyConverter() {
StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
return converter;
}
@Override
public void configureMessageConverters(List> converters) {
super.configureMessageConverters(converters);
converters.add(responseBodyConverter());
}
也可以在controller方法@RequestMapping上添加:
produces="text/plain;charset=UTF-8"
這種方法的弊端是限定了數(shù)據(jù)類(lèi)型。
亂碼解決方案
表單采用get方式提交,中文亂碼解決方案:
改為post請(qǐng)求;
手動(dòng)編解碼:
param = new String(param.getBytes("iso8859-1"), "utf-8");
修改tomcat配置server.xml文件:
找到如下代碼:
在這里添加一個(gè)屬性:URIEncoding,將該屬性值設(shè)置為UTF-8,即可讓Tomcat(默認(rèn)ISO-8859-1編碼)以UTF-8的編碼處理get請(qǐng)求。
修改完成后:
發(fā)送get請(qǐng)求前,瀏覽器中兩次URL編碼:
兩次編碼兩次解碼的過(guò)程為:
==UTF-8編碼->UTF-8(iso-8859-1)編碼->iso-8859-1解碼->UTF-8解碼,編碼和解碼的過(guò)程是對(duì)稱(chēng)的,所以不會(huì)出現(xiàn)亂碼。==
//js代碼
param = encodeURI(param);
// alert("第一次URL編碼:" + param);
param = encodeURI(param);
// alert("第二次URL編碼:" + param);
后臺(tái)代碼:
//兩次解碼
URLDecoder.decode(URLDecoder.decode(param,"utf-8"),"utf-8");
總結(jié)
以上四種解決方案,可結(jié)合具體情況進(jìn)行使用。
no session異常
異常日志1:
[2018-05-21 18:00:51.574][http-nio-8280-exec-6][DEBUG][org.apache.shiro.web.servlet.SimpleCookie][389]:Found 'SHRIOSESSIONID' cookie value [fc6b7b64-6c59-4f82-853b-e2ca20135b99]
[2018-05-21 18:00:51.575][http-nio-8280-exec-6][DEBUG][org.apache.shiro.mgt.DefaultSecurityManager][447]:Resolved SubjectContext context session is invalid. Ignoring and creating an anonymous (session-less) Subject instance.
org.apache.shiro.session.UnknownSessionException: There is no session with id [fc6b7b64-6c59-4f82-853b-e2ca20135b99]
at org.apache.shiro.session.mgt.eis.AbstractSessionDAO.readSession(AbstractSessionDAO.java:170) ~[shiro-all-1.3.1.jar:1.3.1]
異常日志2【偶爾出現(xiàn)】:
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811) ~[sunjce_provider.jar:1.7.0_85]
UnknownSessionException
UnknownSessionException: There is no session with id [...]
原因
結(jié)合項(xiàng)目配置,分析問(wèn)題原因:
1,用戶(hù)退出后,瀏覽器中的SHIROSESSIONID依然存在;
2,再次發(fā)送請(qǐng)求時(shí),攜帶SHIROSESSIONID,會(huì)在shiro的DefaultWebSecurityManager.getSessionKey(context)中,逐層跟蹤對(duì)應(yīng)在sessionManager中session值,沒(méi)有的話,最終在AbstractSessionDAO.readSession(sessionID)中拋出異常。
解決方案
在程序中退出的地方,清除cookie:
//刪除cookie
Cookie co = new Cookie("username", "");
co.setMaxAge(0);// 設(shè)置立即過(guò)期
co.setPath("/");// 根目錄,整個(gè)網(wǎng)站有效
servletResponse.addCookie(co);
設(shè)置SimpleCookie的過(guò)期時(shí)間,和session、ehcache緩存時(shí)間保持一致;
@Bean
public SimpleCookie sessionIdCookie() {
//DefaultSecurityManager
SimpleCookie simpleCookie = new SimpleCookie();
//如果在Cookie中設(shè)置了"HttpOnly"屬性,那么通過(guò)程序(JS腳本、Applet等)將無(wú)法讀取到Cookie信息,這樣能防止XSS×××。
simpleCookie.setHttpOnly(true);
simpleCookie.setName("SHRIOSESSIONID");
simpleCookie.setMaxAge(86400000*3);
return simpleCookie;
}
手動(dòng)實(shí)現(xiàn)shiro的logout方法,清除瀏覽器cookie;
重寫(xiě)AbstractSessionDAO.readSession方法,如果session為null,清空瀏覽器cookie;
不做處理;實(shí)際項(xiàng)目運(yùn)行中,不影響功能執(zhí)行。
源碼
源碼已集成到項(xiàng)目中:
前篇
20200808新版本更新
版本升級(jí)及內(nèi)容優(yōu)化版本,改動(dòng)內(nèi)容:
版本更新,springboot從1.5+升級(jí)到2.1+;
權(quán)限緩存使用redis;
驗(yàn)證碼使用redis;
權(quán)限驗(yàn)證完善。
總結(jié)
以上是生活随笔為你收集整理的log中文乱码 springboot_springboot + shiro 权限注解、统一异常处理、请求乱码解决的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 辽宁省大学生计算机系统与程序设计竞赛,2
- 下一篇: python中type用法_Python