shiro注解的使用
shiro权限注解
Shiro 提供了相应的注解用于权限控制,如果使用这些注解就需要使用AOP 的功能来进行判断,如Spring AOP;Shiro 提供了Spring AOP 集成用于权限注解的解析和验证。
@RequiresAuthentication
表示当前Subject已经通过login 进行了身份验证;即Subject.isAuthenticated()返回true。
@RequiresUser
表示当前Subject已经身份验证或者通过记住我登录的。
@RequiresGuest
表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。
@RequiresRoles(value={"admin", "user"}, logical= Logical.AND)
@RequiresRoles(value={"admin"})
@RequiresRoles({"admin"})
表示当前Subject需要角色admin 和user。
@RequiresPermissions(value={"user:a", "user:b"}, logical= Logical.OR)
表示当前Subject需要权限user:a或user:b。
Shiro的认证注解处理是有内定的处理顺序的,如果有多个注解的话,前面的通过了会继续检查后面的,若不通过则直接返回,处理顺序依次为(与实际声明顺序无关):
- RequiresRoles
- RequiresPermissions
- RequiresAuthentication
- RequiresUser
- RequiresGuest
以上注解既可以用在controller中,也可以用在service中使用;
建议将shiro注解放在controller中,因为如果service层使用了spring的事物注解,那么shiro注解将无效。
shiro权限注解springAOP配置
shiro权限注解要生效,必须配置springAOP通过设置shiro的SecurityManager进行权限验证。
/**
*
* @描述:开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
* </br>Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor(保证实现了Shiro内部lifecycle函数的bean执行) has run
* </br>不使用注解的话,可以注释掉这两个配置
* @创建人:wyait
* @创建时间: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异常处理原理
场景:当用户正常访问网站时,因为某种原因后端出现exception的时候,直接暴露异常信息或页面显示给用户;
这种操作体验不是我们想要的。所以要对异常进行统一管理,能提高用户体验的同时,后台能详细定位到异常的问题点。
springboot异常概况
Spring Boot提供了默认的统一错误页面,这是Spring MVC没有提供的。在理解了Spring Boot提供的错误处理相关内容之后,我们可以方便的定义自己的错误返回的格式和内容。
编写by zero异常
在home页面,手动创建两个异常:普通异常和异步异常!
前端页面:
<p>
普通请求异常:
<a href="/error/getError">点击</a>
</p>
<p>
ajax异步请求异常:
<a href="javascript:void(0)" onclick="ajaxError()">点击</a>
</p>
...
//js代码
function ajaxError(){
$.get("/error/ajaxError",function(data){
layer.alert(data);
});
}
后端代码:
/**
*
* @描述:普通请求异常
* @创建人:wyait
* @创建时间:2018年5月24日 下午5:30:50
*/
@RequestMapping("getError")
public void toError(){
System.out.println(1/0);
}
/**
*
* @描述:异步异常
* @创建人:wyait
* @创建时间:2018年5月24日 下午5:30:39
*/
@RequestMapping("ajaxError")
@ResponseBody
public String ajaxError(){
System.out.println(1/0);
return "异步请求成功!";
}
异常效果
普通异常:
image
console错误信息:
[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'
...
通过日志可知,springboot返回的错误页面,是通过:org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml处理返回ModelAndView。
异步异常:
image
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<java.util.Map<java.lang.String, java.lang.Object>> 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<java.util.Map<java.lang.String, java.lang.Object>> 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
...
通过日志可知,springboot返回的错误信息,是通过:org.springframework.boot.autoconfigure.web.BasicErrorController.error
处理返回ResponseEntity<String,Object>
。
异常都是通过org.springframework.boot.autoconfigure.web.BasicErrorController
控制处理的。
springboot异常处理解析
查看org.springframework.boot.autoconfigure.web
包下面的类,跟踪springboot对error异常处理机制。自动配置通过一个MVC error控制器处理错误
通过spring-boot-autoconfigure引入
查看springboot 处理error的类
image
springboot的自动配置,在web中处理error相关的自动配置类:ErrorMvcAutoConfiguration。查看与处理error相关的类:
ErrorMvcAutoConfiguration.class
ErrorAttibutes.class
ErrorController.class
ErrorProperties.class
ErrorViewResolver.class
...
ErrorAutoConfiguration类源码//TODO
ErrorAutoConfiguration注册的bean
//4个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类
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes
implements ErrorAttributes, HandlerExceptionResolver, Ordered {
...
}
ErrorAttributes:
public interface ErrorAttributes {
Map<String, Object> 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类:
- 实现了ErrorAttributes接口,当处理/error错误页面时,可以在该bean中读取错误信息响应返回;
- 实现了HandlerExceptionResolver接口。
debug跟踪源码:即DispatcherServlet在doDispatch过程中有异常抛出时:
- 先由HandlerExceptionResolver.resolveException解析异常并保存在request中;
- 再DefaultErrorAttributes.getErrorAttributes处理;DefaultErrorAttributes在处理过程中,从request中获取错误信息,将错误信息保存到RequestAttributes中;
- 最后在获取错误信息getError(RequestAttributes)时,从RequestAttributes中取到错误信息。
BasicErrorController类
@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<String, Object> 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<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<Map<String, Object>>(body, status);
}
...
}
**resolveErrorView方法(查找=error/“错误状态码”;的资源): **
如果不是异常请求,会执行resolveErrorView方法;该方法会先在默认或配置的静态资源路径下查找error/HttpStatus(错误状态码)的资源文件,如果没有;使用默认的error页面。
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
...
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
//status:异常错误状态码
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<String, Object> model) {
//视图名称,默认是error/+“status”错误状态码;比如: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等页面
private ModelAndView resolveResource(String viewName, Map<String, Object> 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根据Accept头的内容,输出不同格式的错误响应。比如针对浏览器的请求生成html页面,针对其它请求生成json格式的返回。
可以通过配置error/HttpStatus页面实现自定义错误页面。
ErrorPageCustomizer类
/**
* {@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;
}
}
将错误页面注册到内嵌的tomcat的servlet容器中。
PreserveErrorControllerTargetClassPostProcessor实现BeanFactoryPostProcessor
接口,可以修改BEAN的配置信息
ErrorAutoConfiguration
内的两个配置
//2个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(
"<html><body><h1>Whitelabel Error Page</h1>"
+ "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
+ "<div id='created'>${timestamp}</div>"
+ "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
+ "<div>${message}</div></body></html>");
@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
:默认的error视图解析配置;
WhitelabelErrorViewConfiguration
:默认设置了/error的页面,和Whitelabel Error Page页面响应内容。
如果Spring MVC在处理业务的过程中抛出异常,会被 Servlet 容器捕捉到,Servlet 容器再将请求转发给注册好的异常处理映射 /error 做响应处理。
springboot配置文件默认error相关配置
springboot配置文件application.properties中关于error默认配置:
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 自定义异常处理
通过跟踪springboot对异常处理得源码跟踪,根据业务需要,可以细分前端响应的错误页面,也可以统一使用/error页面+错误提示信息进行处理。
根据自己的需求自定义异常处理机制;具体可实施的操作如下:
- 可以通过配置error/HttpStatus(错误状态码)页面实现自定义错误页面【底层实现,详见:BasicErrorController源码】;
- 可以实现BasicErrorController,自定义普通请求的异常页面响应信息和异步请求的响应信息,统一使用/error页面进行错误响应提示;
- 自定义实现ErrorAttributes接口,覆盖DefaultErrorAttributes实现,或是继承DefaultErrorAttributes类,重写里面的方法【TODO,不推荐】。
1和2的方法可单独使用,也可以结合使用。
自定义异常页面
可以根据不同的错误状态码,在前端细分不同的响应界面给用户进行提示;资源路径必须是:静态资源路径下/error/HttpStats(比如:/error/404等)
自定义异常页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"></meta>
<title>404友情提示</title>
</head>
<body>
<h1>访问的资源未找到(404)</h1>
</body>
</html>
404.html
500.html等,这里只演示404。
统一异常处理
普通请求,前端使用error页面+自定义错误响应信息;
其他请求(异步),统一自定义错误响应信息,规范处理异步响应的错误判断和处理。
使用springMVC注解ControllerAdvice
/**
*
* @项目名称:wyait-manage
* @类名称:GlobalExceptionHandler
* @类描述:统一异常处理,包括【普通调用和ajax调用】
* </br>ControllerAdvice来做controller内部的全局异常处理,但对于未进入controller前的异常,该处理方法是无法进行捕获处理的,SpringBoot提供了ErrorController的处理类来处理所有的异常(TODO)。
* </br>1.当普通调用时,跳转到自定义的错误页面;2.当ajax调用时,可返回约定的json数据对象,方便页面统一处理。
* @创建人:wyait
* @创建时间: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";
/**
*
* @描述:针对普通请求和ajax异步请求的异常进行处理
* @创建人:wyait
* @创建时间: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】统一异常处理:request="+request);
ModelAndView mv=new ModelAndView();
logger.info(getClass().getName() + ".errorHandler】统一异常处理:"+e.getMessage());
//1 获取错误状态码
HttpStatus httpStatus=getStatus(request);
logger.info(getClass().getName() + ".errorHandler】统一异常处理!错误状态码httpStatus:"+httpStatus);
//2 返回错误提示
ExceptionEnum ee=getMessage(httpStatus);
//3 将错误信息放入mv中
mv.addObject("type", ee.getType());
mv.addObject("code", ee.getCode());
mv.addObject("msg", ee.getMsg());
if(!ShiroFilterUtils.isAjax(request)){
//不是异步请求
mv.setViewName(DEFAULT_ERROR_VIEW);
logger.debug(getClass().getName() + ".errorHandler】统一异常处理:普通请求。");
}
logger.debug(getClass().getName() + ".errorHandler】统一异常处理响应结果:MV="+mv);
return mv;
}
...
}
运行测试:先走GlobalExceptionHandle
r(使用注解@ControllerAdvice
)类里面的方法,而后又执行了BasicErrorController
方法;被springboot自带的BasicErrorController
覆盖。
实现springboot的AbstractErrorController
自定义实现AbstractErrorController,添加响应的错误提示信息。
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
ModelAndView mv = new ModelAndView(ERROR_PATH);
/** model对象包含了异常信息 */
Map<String, Object> model = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.TEXT_HTML));
// 1 获取错误状态码(也可以根据异常对象返回对应的错误信息)
HttpStatus httpStatus = getStatus(request);
// 2 返回错误提示
ExceptionEnum ee = getMessage(httpStatus);
Result<String> result = new Result<String>(
String.valueOf(ee.getType()), ee.getCode(), ee.getMsg());
// 3 将错误信息放入mv中
mv.addObject("result", result);
logger.info("统一异常处理【" + getClass().getName()
+ ".errorHtml】统一异常处理!错误信息mv:" + mv);
return mv;
}
@RequestMapping
@ResponseBody
//设置响应状态码为:200,结合前端约定的规范处理。也可不设置状态码,前端ajax调用使用error函数进行控制处理
@ResponseStatus(value=HttpStatus.OK)
public Result<String> error(HttpServletRequest request, Exception e) {
/** model对象包含了异常信息 */
Map<String, Object> model = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.TEXT_HTML));
// 1 获取错误状态码(也可以根据异常对象返回对应的错误信息)
HttpStatus httpStatus = getStatus(request);
// 2 返回错误提示
ExceptionEnum ee = getMessage(httpStatus);
Result<String> result = new Result<String>(
String.valueOf(ee.getType()), ee.getCode(), ee.getMsg());
// 3 将错误信息返回
// ResponseEntity
logger.info("统一异常处理【" + getClass().getName()
+ ".error】统一异常处理!错误信息result:" + result);
return result;
}
针对异步请求,统一指定响应状态码:200;也可以不指定,前端在处理异步请求的时候,可以通过ajax的error函数进行控制。
这里是继承的AbstractErrorController
类,自定义实现统一异常处理,也可以直接实现ErrorController
接口。
前端ajax异步统一处理:
通过约定,前端ajax异步请求,进行统一的错误处理。
/**
* 针对不同的错误可结合业务自定义处理方式
* @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(){
//跳转到未授权界面
window.location.href="/403";
});
}
}
return flag;//返回true
}
使用方式:
...
success:function(data){
//异常过滤处理
if(isError(data)){
alert(data);
}
},
...
error.html页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:include="layout :: htmlhead" th:with="title='wyait后台管理'">
<meta charset="UTF-8"></meta>
<title th:text="${result.status}"></title>
</head>
<body>
<h1>出错了</h1>
<p><span th:text="${result.message}"></span>(<span th:text="${result.data}"></span>)</p>
</body>
</html>
测试效果
普通请求:
image
异步请求:
image
线上get请求乱码
问题描述
前台通过html页面,发送请求到后台查询数据,在日志中打印的sql语句显示传入的参数乱码:
SELECT ...
[2018-05-11 09:15:00.582][http-bio-8280-exec-2][DEBUG][org.apache.ibatis.logging.jdbc.BaseJdbcLogger][159]:==> Parameters: 1(Integer), �贺(String)
[2018-05-11 09:15:00.585][http-bio-8280-exec-2][DEBUG][org.apache.ibatis.logging.jdbc.BaseJdbcLogger][159]:<== Total: 1
...
本地windows开发环境测试没有乱码问题;
请求信息
image
前端页面发送get请求,浏览器默认对get请求路径进行URL编码处理。
后台Controller打印的日志
分页查询用户列表!搜索条件:userSearch:UserSearchDTO{page=1, limit=10, uname='ç��ç��', umobile='', insertTimeStart='', insertTimeEnd=''},page:1,每页记录数量limit:10,请求编码:UTF-8
Controller层在接收到这个uname参数时,已经是乱码,ISO-8859-1解码后的结果。
请求参数编码流程
- 前端页面发送get请求,浏览器默认在中文的UTF-8后加上上%得到URL编码,比如:
%e8%b4%b9%e7...
; - get请求到tomcat应用服务器后,会以默认的ISO-8859-1进行解码;
- 在controller中,接收到的是经过URL编码和iso-8859-1解码后的参数值。
具体编码细节:TODO
解决方案
项目编码配置【可以不配置】
开发前,默认必须统一编码环境;正常都是设置为utf-8。
spring boot 与spring mvc不同,在web应用中,spring boot默认的编码格式为UTF-8,而spring mvc的默认编码格式为iso-8859-1。
spring boot项目中如果没有特殊需求,该编码不需要修改。如果要强制其他编码格式,spring boot提供了设置方式:
通过application.properties配置文件设置:
# 默认utf-8配置
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8
此时拦截器中返回的中文已经不乱码了,但是controller中返回的数据可能会依旧乱码。
参考spring MVC的方式,自定义实现WebMvcConfigurerAdapter类,处理响应数据乱码问题:
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
return converter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(responseBodyConverter());
}
也可以在controller方法@RequestMapping上添加:
produces="text/plain;charset=UTF-8"
这种方法的弊端是限定了数据类型。
乱码解决方案
表单采用get方式提交,中文乱码解决方案:
改为post请求;
手动编解码:
param = new String(param.getBytes("iso8859-1"), "utf-8");
修改tomcat配置server.xml文件:
找到如下代码:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
在这里添加一个属性:URIEncoding,将该属性值设置为UTF-8,即可让Tomcat(默认ISO-8859-1编码)以UTF-8的编码处理get请求。
修改完成后:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />
发送get请求前,浏览器中两次URL编码:
两次编码两次解码的过程为:
== UTF-8编码->UTF-8(iso-8859-1)编码->iso-8859-1解码->UTF-8解码,编码和解码的过程是对称的,所以不会出现乱码。==
//js代码
param = encodeURI(param);
// alert("第一次URL编码:" + param);
param = encodeURI(param);
// alert("第二次URL编码:" + param);
后台代码:
//两次解码
URLDecoder.decode(URLDecoder.decode(param,"utf-8"),"utf-8");
总结
以上四种解决方案,可结合具体情况进行使用。
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【偶尔出现】:
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 [...]
原因
结合项目配置,分析问题原因:
1,用户退出后,浏览器中的SHIROSESSIONID依然存在;
2,再次发送请求时,携带SHIROSESSIONID,会在shiro的DefaultWebSecurityManager.getSessionKey(context)
中,逐层跟踪对应在sessionManager
中session值,没有的话,最终在AbstractSessionDAO.readSession(sessionID)
中抛出异常。
解决方案
在程序中退出的地方,清除cookie:
//删除cookie
Cookie co = new Cookie("username", "");
co.setMaxAge(0);// 设置立即过期
co.setPath("/");// 根目录,整个网站有效
servletResponse.addCookie(co);
设置SimpleCookie的过期时间,和session、ehcache缓存时间保持一致;
@Bean
public SimpleCookie sessionIdCookie() {
//DefaultSecurityManager
SimpleCookie simpleCookie = new SimpleCookie();
//如果在Cookie中设置了"HttpOnly"属性,那么通过程序(JS脚本、Applet等)将无法读取到Cookie信息,这样能防止XSS×××。
simpleCookie.setHttpOnly(true);
simpleCookie.setName("SHRIOSESSIONID");
simpleCookie.setMaxAge(86400000*3);
return simpleCookie;
}
手动实现shiro的logout方法,清除浏览器cookie;
重写AbstractSessionDAO.readSession方法,如果session为null,清空浏览器cookie;
不做处理;实际项目运行中,不影响功能执行。