springboot + shiro 權限注解要糊、統(tǒng)一異常處理、請求亂碼解決

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的認證注解處理是有內定的處理順序的,如果有多個注解的話宦言,前面的通過了會繼續(xù)檢查后面的扇单,若不通過則直接返回,處理順序依次為(與實際聲明順序無關):

  • 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)即可實現(xiàn)此功能
* </br>Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor(保證實現(xiàn)了Shiro內部lifecycle函數(shù)的bean執(zhí)行) has run
* </br>不使用注解的話扮碧,可以注釋掉這兩個配置
* @創(chuàng)建人:wyait
* @創(chuàng)建時間: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異常處理原理

場景:當用戶正常訪問網站時,因為某種原因后端出現(xiàn)exception的時候杏糙,直接暴露異常信息或頁面顯示給用戶慎王;

這種操作體驗不是我們想要的。所以要對異常進行統(tǒng)一管理宏侍,能提高用戶體驗的同時赖淤,后臺能詳細定位到異常的問題點。

springboot異常概況

Spring Boot提供了默認的統(tǒng)一錯誤頁面谅河,這是Spring MVC沒有提供的咱旱。在理解了Spring Boot提供的錯誤處理相關內容之后,我們可以方便的定義自己的錯誤返回的格式和內容绷耍。

編寫by zero異常

在home頁面吐限,手動創(chuàng)建兩個異常:普通異常和異步異常!

前端頁面:

<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);
    });
}

后端代碼:

/**
 * 
 * @描述:普通請求異常
 * @創(chuàng)建人:wyait
 * @創(chuàng)建時間:2018年5月24日 下午5:30:50
 */
@RequestMapping("getError")
public void toError(){
    System.out.println(1/0);
}
/**
 * 
 * @描述:異步異常
 * @創(chuàng)建人:wyait
 * @創(chuàng)建時間: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類:

  • 實現(xiàn)了ErrorAttributes接口判莉,當處理/error錯誤頁面時齿诞,可以在該bean中讀取錯誤信息響應返回;
  • 實現(xiàn)了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/“錯誤狀態(tài)碼”私蕾;的資源): **

如果不是異常請求僵缺,會執(zhí)行resolveErrorView方法;該方法會先在默認或配置的靜態(tài)資源路徑下查找error/HttpStatus(錯誤狀態(tài)碼)的資源文件踩叭,如果沒有磕潮;使用默認的error頁面。

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
    ...
    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
            Map<String, Object> model) {
            //status:異常錯誤狀態(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<String, Object> model) {
        //視圖名稱容贝,默認是error/+“status”錯誤狀態(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等頁面
    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根據(jù)Accept頭的內容斤富,輸出不同格式的錯誤響應膏潮。比如針對瀏覽器的請求生成html頁面,針對其它請求生成json格式的返回满力。

可以通過配置error/HttpStatus頁面實現(xiàn)自定義錯誤頁面焕参。

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實現(xiàn)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在處理業(yè)務的過程中拋出異常讲岁,會被 Servlet 容器捕捉到,Servlet 容器再將請求轉發(fā)給注冊好的異常處理映射 /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對異常處理得源碼跟蹤缓艳,根據(jù)業(yè)務需要,可以細分前端響應的錯誤頁面看峻,也可以統(tǒng)一使用/error頁面+錯誤提示信息進行處理阶淘。

根據(jù)自己的需求自定義異常處理機制;具體可實施的操作如下:

  • 可以通過配置error/HttpStatus(錯誤狀態(tài)碼)頁面實現(xiàn)自定義錯誤頁面【底層實現(xiàn)互妓,詳見:BasicErrorController源碼】溪窒;
  • 可以實現(xiàn)BasicErrorController,自定義普通請求的異常頁面響應信息和異步請求的響應信息冯勉,統(tǒng)一使用/error頁面進行錯誤響應提示澈蚌;
  • 自定義實現(xiàn)ErrorAttributes接口,覆蓋DefaultErrorAttributes實現(xiàn)灼狰,或是繼承DefaultErrorAttributes類宛瞄,重寫里面的方法【TODO,不推薦】交胚。

1和2的方法可單獨使用份汗,也可以結合使用盈电。

自定義異常頁面

可以根據(jù)不同的錯誤狀態(tài)碼,在前端細分不同的響應界面給用戶進行提示杯活;資源路徑必須是:靜態(tài)資源路徑下/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。

統(tǒng)一異常處理

普通請求旁钧,前端使用error頁面+自定義錯誤響應信息吸重;
其他請求(異步),統(tǒng)一自定義錯誤響應信息歪今,規(guī)范處理異步響應的錯誤判斷和處理嚎幸。

使用springMVC注解ControllerAdvice

/**
 * 
 * @項目名稱:wyait-manage
 * @類名稱:GlobalExceptionHandler
 * @類描述:統(tǒng)一異常處理,包括【普通調用和ajax調用】
 * </br>ControllerAdvice來做controller內部的全局異常處理彤委,但對于未進入controller前的異常鞭铆,該處理方法是無法進行捕獲處理的或衡,SpringBoot提供了ErrorController的處理類來處理所有的異常(TODO)焦影。
 * </br>1.當普通調用時,跳轉到自定義的錯誤頁面封断;2.當ajax調用時斯辰,可返回約定的json數(shù)據(jù)對象,方便頁面統(tǒng)一處理坡疼。
 * @創(chuàng)建人:wyait
 * @創(chuàng)建時間: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異步請求的異常進行處理
     * @創(chuàng)建人:wyait
     * @創(chuàng)建時間: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 獲取錯誤狀態(tài)碼
        HttpStatus httpStatus=getStatus(request);
        logger.info(getClass().getName() + ".errorHandler】統(tǒng)一異常處理!錯誤狀態(tài)碼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】統(tǒng)一異常處理:普通請求彬呻。");
        }
        logger.debug(getClass().getName() + ".errorHandler】統(tǒng)一異常處理響應結果:MV="+mv);
        return mv;
    }
    ...
}

運行測試:先走GlobalExceptionHandler(使用注解@ControllerAdvice)類里面的方法,而后又執(zhí)行了BasicErrorController方法柄瑰;被springboot自帶的BasicErrorController覆蓋闸氮。

實現(xiàn)springboot的AbstractErrorController

自定義實現(xiàn)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 獲取錯誤狀態(tài)碼(也可以根據(jù)異常對象返回對應的錯誤信息)
        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("統(tǒng)一異常處理【" + getClass().getName()
                + ".errorHtml】統(tǒng)一異常處理!錯誤信息mv:" + mv);
        return mv;
    }

    @RequestMapping
    @ResponseBody
    //設置響應狀態(tài)碼為:200蒲跨,結合前端約定的規(guī)范處理。也可不設置狀態(tài)碼授翻,前端ajax調用使用error函數(shù)進行控制處理
    @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 獲取錯誤狀態(tài)碼(也可以根據(jù)異常對象返回對應的錯誤信息)
        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("統(tǒng)一異常處理【" + getClass().getName()
                + ".error】統(tǒng)一異常處理!錯誤信息result:" + result);
        return result;
    }

針對異步請求或悲,統(tǒng)一指定響應狀態(tài)碼:200;也可以不指定堪唐,前端在處理異步請求的時候巡语,可以通過ajax的error函數(shù)進行控制。

這里是繼承的AbstractErrorController類淮菠,自定義實現(xiàn)統(tǒng)一異常處理男公,也可以直接實現(xiàn)ErrorController接口。

前端ajax異步統(tǒng)一處理:

通過約定合陵,前端ajax異步請求理澎,進行統(tǒng)一的錯誤處理逞力。

/**
 * 針對不同的錯誤可結合業(yè)務自定義處理方式
 * @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頁面,發(fā)送請求到后臺查詢數(shù)據(jù)糠爬,在日志中打印的sql語句顯示傳入的參數(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開發(fā)環(huán)境測試沒有亂碼問題寇荧;

請求信息

image
前端頁面發(fā)送get請求,瀏覽器默認對get請求路徑進行URL編碼處理执隧。

后臺Controller打印的日志

分頁查詢用戶列表揩抡!搜索條件:userSearch:UserSearchDTO{page=1, limit=10, uname='??????', umobile='', insertTimeStart='', insertTimeEnd=''},page:1,每頁記錄數(shù)量limit:10,請求編碼:UTF-8

Controller層在接收到這個uname參數(shù)時镀琉,已經是亂碼峦嗤,ISO-8859-1解碼后的結果。

請求參數(shù)編碼流程

  • 前端頁面發(fā)送get請求屋摔,瀏覽器默認在中文的UTF-8后加上上%得到URL編碼烁设,比如:%e8%b4%b9%e7...
  • get請求到tomcat應用服務器后钓试,會以默認的ISO-8859-1進行解碼装黑;
  • 在controller中,接收到的是經過URL編碼和iso-8859-1解碼后的參數(shù)值弓熏。

具體編碼細節(jié):TODO

解決方案

項目編碼配置【可以不配置】

開發(fā)前恋谭,默認必須統(tǒng)一編碼環(huán)境;正常都是設置為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中返回的數(shù)據(jù)可能會依舊亂碼。

參考spring MVC的方式橄教,自定義實現(xiàn)WebMvcConfigurerAdapter類清寇,處理響應數(shù)據(jù)亂碼問題:

@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"

這種方法的弊端是限定了數(shù)據(jù)類型。

亂碼解決方案
表單采用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" />

發(fā)送get請求前,瀏覽器中兩次URL編碼:
兩次編碼兩次解碼的過程為:

== UTF-8編碼->UTF-8(iso-8859-1)編碼->iso-8859-1解碼->UTF-8解碼,編碼和解碼的過程是對稱的喂链,所以不會出現(xiàn)亂碼返十。==

//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【偶爾出現(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 [...]

原因
結合項目配置洞坑,分析問題原因:
1,用戶退出后蝇率,瀏覽器中的SHIROSESSIONID依然存在迟杂;
2,再次發(fā)送請求時本慕,攜帶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;
}

手動實現(xiàn)shiro的logout方法犹菱,清除瀏覽器cookie;

重寫AbstractSessionDAO.readSession方法吮炕,如果session為null,清空瀏覽器cookie腊脱;
不做處理;實際項目運行中龙亲,不影響功能執(zhí)行陕凹。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鳄炉,隨后出現(xiàn)的幾起案子杜耙,更是在濱河造成了極大的恐慌,老刑警劉巖拂盯,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佑女,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機团驱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門摸吠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嚎花,你說我怎么就攤上這事寸痢。” “怎么了紊选?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵轿腺,是天一觀的道長。 經常有香客問我丛楚,道長族壳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任趣些,我火速辦了婚禮仿荆,結果婚禮上,老公的妹妹穿的比我還像新娘坏平。我一直安慰自己拢操,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布舶替。 她就那樣靜靜地躺著令境,像睡著了一般。 火紅的嫁衣襯著肌膚如雪顾瞪。 梳的紋絲不亂的頭發(fā)上舔庶,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音陈醒,去河邊找鬼惕橙。 笑死,一個胖子當著我的面吹牛钉跷,可吹牛的內容都是我干的弥鹦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼爷辙,長吁一口氣:“原來是場噩夢啊……” “哼彬坏!你這毒婦竟也來了?” 一聲冷哼從身側響起膝晾,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤栓始,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后玷犹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體混滔,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡洒疚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了坯屿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片油湖。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖领跛,靈堂內的尸體忽然破棺而出乏德,到底是詐尸還是另有隱情,我是刑警寧澤吠昭,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布喊括,位于F島的核電站,受9級特大地震影響矢棚,放射性物質發(fā)生泄漏郑什。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一蒲肋、第九天 我趴在偏房一處隱蔽的房頂上張望蘑拯。 院中可真熱鬧,春花似錦兜粘、人聲如沸申窘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剃法。三九已至,卻和暖如春路鹰,著一層夾襖步出監(jiān)牢的瞬間贷洲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工悍引, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留恩脂,地道東北人帽氓。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓趣斤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親黎休。 傳聞我的和親對象是個殘疾皇子浓领,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)势腮,斷路器联贩,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評論 6 342
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,313評論 1 92
  • 其實我并不真的在乎 與別人一起占有你 我并不真的介意你的吻也蓋著別人的印記 如果這是你不能逃避的宿命 至少讓我...
    MissCapricorn91閱讀 146評論 0 0
  • ?.獲取字符串中:a.所有的正整數(shù) ['12', '13', '43', '257']b.所有的負整數(shù) ['-5'...
    曉曉的忍兒閱讀 218評論 0 7