Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 一级毛片大全免费播放,久久免费视频2,亚洲人成在线观看男人自拍

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          springboot + shiro 權限注解、統一異常處理、請求亂碼解決

          后臺權限管理系統

          相關:

          spring boot + mybatis + layui + shiro后臺權限管理系統

          springboot + shiro之登錄人數限制、登錄判斷重定向、session時間設置

          springboot + shiro 動態更新用戶信息

          基于前篇,新增功能:

          1. 新增shiro權限注解;
          2. 請求亂碼問題解決;
          3. 統一異常處理。

          源碼已集成到項目中:

          github源碼: https://github.com/wyait/manage.git

          碼云:https://gitee.com/wyait/manage.git

          github對應項目源碼目錄:wyait-manage-1.2.0

          碼云對應項目源碼目錄:wyait-manage-1.2.0

          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 "異步請求成功!";
          }
          

          異常效果

          • 普通異常:

          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。

          • 異步異常:


          • 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的類

          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();
          }
          
          1. 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中取到錯誤信息。

          1. 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頁面實現自定義錯誤頁面。

          1. 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容器中。

          1. 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;
           }
          }
          
          1. DefaultErrorViewResolverConfiguration:默認的error視圖解析配置;
          2. 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頁面+錯誤提示信息進行處理。

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

          1. 可以通過配置error/HttpStatus(錯誤狀態碼)頁面實現自定義錯誤頁面【底層實現,詳見:BasicErrorController源碼】;
          2. 可以實現BasicErrorController,自定義普通請求的異常頁面響應信息和異步請求的響應信息,統一使用/error頁面進行錯誤響應提示;
          3. 自定義實現ErrorAttributes接口,覆蓋DefaultErrorAttributes實現,或是繼承DefaultErrorAttributes類,重寫里面的方法【TODO,不推薦】。

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

          自定義異常頁面

          可以根據不同的錯誤狀態碼,在前端細分不同的響應界面給用戶進行提示;資源路徑必須是:靜態資源路徑下/error/HttpStats(比如:/error/404等)

          1. 自定義異常頁面
          <!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;
           }
           ...
          }
          

          運行測試:先走GlobalExceptionHandler(使用注解@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>
          

          測試效果

          普通請求:

          異步請求:

          線上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), ???è′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開發環境測試沒有亂碼問題;

          請求信息

          前端頁面發送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解碼后的結果。

          請求參數編碼流程

          1. 前端頁面發送get請求,瀏覽器默認在中文的UTF-8后加上上%得到URL編碼,比如:%e8%b4%b9%e7...;
          2. get請求到tomcat應用服務器后,會以默認的ISO-8859-1進行解碼;
          3. 在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提供了設置方式:

          1. 通過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中返回的數據可能會依舊亂碼。

          1. 參考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方式提交,中文亂碼解決方案:

          1. 改為post請求;
          2. 手動編解碼:
          param = new String(param.getBytes("iso8859-1"), "utf-8");
          
          1. 修改tomcat配置server.xml文件:
          2. 找到如下代碼:
          <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" />
          
          1. 發送get請求前,瀏覽器中兩次URL編碼:
          2. 兩次編碼兩次解碼的過程為:
          3. ==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)中拋出異常。

          解決方案

          1. 在程序中退出的地方,清除cookie:
          //刪除cookie
          Cookie co = new Cookie("username", "");
          co.setMaxAge(0);// 設置立即過期
          co.setPath("/");// 根目錄,整個網站有效
          servletResponse.addCookie(co);
          
          1. 設置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;
          }
          
          1. 手動實現shiro的logout方法,清除瀏覽器cookie;
          2. 重寫AbstractSessionDAO.readSession方法,如果session為null,清空瀏覽器cookie;
          3. 不做處理;實際項目運行中,不影響功能執行。

          源碼

          源碼已集成到項目中:

          github源碼: https://github.com/wyait/manage.git

          碼云:https://gitee.com/wyait/manage.git

          github對應項目源碼目錄:wyait-manage-1.2.0

          碼云對應項目源碼目錄:wyait-manage-1.2.0

          轉載:https://blog.51cto.com/wyait/2125708

          作者:wyait

          hiro-550反序列化漏洞(CVE-2016-4437)


          漏洞簡介

          shiro-550主要是由shiro的rememberMe內容反序列化導致的命令執行漏洞,造成的原因是默認加密密鑰是硬編碼在shiro源碼中,任何有權訪問源代碼的人都可以知道默認加密密鑰。于是攻擊者可以創建一個惡意對象,對其進行序列化、編碼,然后將其作為cookie的rememberMe字段內容發送,Shiro 將對其解碼和反序列化,導致服務器運行一些惡意代碼。

          ? 特征:cookie中含有rememberMe字段

          ? 修復建議:

          • 更新shiro到1.2.4以上的版本。
          • 不使用默認的加密密鑰,改為隨機生成密鑰。

          漏洞原理

          一、Shiro簡介

          ? Apache Shiro 是一個強大易用的 Java 安全框架,提供了認證、授權、加密和會話管理等功能,對于任何一個應用程序,Shiro 都可以提供全面的安全管理服務。

          ? 在Apache Shiro<=1.2.4版本中AES加密時采用的key是硬編碼在代碼中的,于是我們就可以構造RememberMe的值,然后讓其反序列化執行。

          Primary Cocnerns(基本關注點):

          • Authentication(認證):經常和登錄掛鉤,是證明用戶說他們是誰的一個工作
          • Authorization(授權):訪問控制的過程,即,決定‘誰’可以訪問‘什么
          • Session Management(會話管理):管理用戶特定的會話,即使在非web或是EJB的應用中
          • Crytography(加密):通過加密算法保證數據的安全,且易于使用

          Supporting Features(輔助特性):

          • Web Support(網絡支持):web support API可以幫助在web應用中方便的使用shiro
          • Caching(緩存):保證安全操作使用快速有效
          • Concurrency(并發):支持多線程應用
          • Testing(測試):支持集成單元測試
          • Run As(以..運行):可以假定用戶為另一個用戶
          • Remeber Me:記住用戶,無需再次登錄

          二、Shiro服務器識別身份加解密處理的流程

          1、加密

          1. 用戶使用賬號密碼進行登錄,并勾選”Remember Me“。
          2. Shiro驗證用戶登錄信息,通過后,查看用戶是否勾選了”Remember Me“。
          3. 若勾選,則將用戶身份序列化,并將序列化后的內容進行AES加密,再使用base64編碼。
          4. 最后將處理好的內容放于cookie中的rememberMe字段。

          2、解密

          1. 當服務端收到來自未經身份驗證的用戶的請求時,會在客戶端發送請求中的cookie中獲取rememberMe字段內容。
          2. 將獲取到的rememberMe字段進行base64解碼,再使用AES解密。
          3. 最后將解密的內容進行反序列化,獲取到用戶身份。

          三、Key

          AES加密的密鑰Key被硬編碼在代碼里

          漏洞復現

          攻擊機IP:192.168.0.109

          靶機IP:192.168.72.128

          1、訪問靶機

          存在Remember me選項,嘗試抓包

          2、漏洞利用

          Github上工具很多,我們隨便拿一款來進行驗證

          爆破密鑰成功后,即可執行命令

          Shiro-721反序列化漏洞(CVE-2019-12422)


          漏洞簡介

          0x01首先講一下面試官經常會問到的一個問題

          Shiro550和Shiro721的區別是什么

          Shiro550只需要通過碰撞key,爆破出來密鑰,就可以進行利用
          Shiro721的ase加密的key一般情況下猜不到,是系統隨機生成的,并且當存在有效的用戶信息時才會進入下一階段的流程所以我們需要使用登錄后的rememberMe Cookie,才可以進行下一步攻擊
          

          0x02 漏洞指紋

          • URL中含有Shiro字段
          • cookie中含有rememberMe字段
          • 返回包中含有rememberMe

          0x03 漏洞介紹

          ? 在Shiro721中,Shiro通過AES-128-CBC對cookie中的rememberMe字段進行加密,所以用戶可以通過Padding Oracle加密生成的攻擊代碼來構造惡意的rememberMe字段,進行反序列化攻擊,需要執行的命令越復雜,生成payload需要的時間就越長。

          漏洞原理

          ? 由于Apache Shiro cookie中通過 AES-128-CBC 模式加密的rememberMe字段存在問題,用戶可通過Padding Oracle 加密生成的攻擊代碼來構造惡意的rememberMe字段,用有效的RememberMe cookie作為Padding Oracle Attack 的前綴,然后制作精心制作的RememberMe來執行Java反序列化攻擊

          攻擊流程

          ? 登錄網站,并從cookie中獲取RememberMe。使用RememberMe cookie作為Padding Oracle Attack的前綴。加密syserial的序列化有效負載,以通過Padding Oracle Attack制作精心制作的RememberMe。請求帶有新的RememberMe cookie的網站,以執行反序列化攻擊。攻擊者無需知道RememberMe加密的密碼密鑰。

          AES-128-CBC

          屬于AES加密算法的CBC模式,使用128位數據塊為一組進行加密解密,即16字節明文,對應16字節密文,,明文加密時,如果數據不夠16字節,則會將數據補全剩余字節

          • 若最后剩余的明文不夠16字節,需要進行填充,通常采用PKCS7進行填充。比如最后缺3個字節,則填充3個字節的0x03;若最后缺10個字節,則填充10個字節的0x0a;
          • 若明文正好是16個字節的整數倍,最后要再加入一個16字節0x10的組再進行加密

          Padding Oracle Attack原理

          ? Padding Oracle攻擊可以在沒有密鑰的情況下加密或解密密文

          ? Shiro Padding Oracle Attack(Shiro填充Oracle攻擊)是一種針對Apache Shiro身份驗證框架的安全漏洞攻擊。Apache Shiro是Java應用程序中廣泛使用的身份驗證和授權框架,用于管理用戶會話、權限驗證等功能。

          ? Padding Oracle Attack(填充Oracle攻擊)是一種針對加密算法使用填充的安全漏洞攻擊。在加密通信中,填充用于將明文數據擴展到加密算法塊大小的倍數。在此攻擊中,攻擊者利用填充的響應信息來推斷出加密算法中的秘密信息。

          ? Shiro Padding Oracle Attack利用了Shiro框架中的身份驗證過程中的一個漏洞,該漏洞允許攻擊者通過填充信息的不同響應時間來確定身份驗證過程中的錯誤。通過不斷嘗試不同的填充方式,攻擊者可以逐步推斷出加密秘鑰,并最終獲取訪問權限。

          ? 這種攻擊利用了填充錯誤的身份驗證響應來獲取關于秘密信息的信息泄漏,然后根據這些信息進行進一步的攻擊。為了防止Shiro Padding Oracle Attack,建議及時更新Apache Shiro版本,確保已修復該漏洞,并采取其他安全措施,如使用安全的加密算法和密鑰管理策略。

          漏洞復現

          1、拉取環境

          docker pull vulfocus/shiro-721
          docker run -d -p 8080:8080 vulfocus/shiro-721
          

          環境啟動完成后,在本地瀏覽器訪問靶場地址:your-ip:8080

          2、攻擊環節

          ? 0x01 登錄成功后,我們從Cookie中獲取到rememberMe字段的值

          0x02 使用ysoserial生成Payload

          java -jar ysoserial.jar CommonsCollections1 "touch /tmp/YikJiang" > payload.class
          

          0x03 使用rememberMe值作為prefix,加載Payload,進行Padding Oracle攻擊。

          python shiro_exp.py http://47.95.201.15:8080/account/ k+3DDsh6f+macxMUtS2QvAS7Fm3CyMpFB6wz4apvrieZhTIMaLey74RYMgywpM2fFncf3y7cRTU6F73MIJ5ygJ0QqzYlvX2xcmOUCe+uLiH66B0aAcs7vY6Ipimbo8tTX3vbReu0vovnmDVK4fT+lfmhZxtgFp8imCapqIb6KYr3NtmQTfORGhFZ+I2vzMN2geaYRwFkTbzfuo8vHgmzHJaR1jTn2sLVaxiIuqMYqsjiCVvN7q64wpde0JGQs1eowMKJ5VSlnUnp1NGficIFYdTETxDjJJHrmKNSxdHPCstWfQD3N6jEK1CT3vE+UxxVrtSO2XoBEHYrSTdK1bxVtunwVu5+F7lfwex3b2qY/F6EzCUjzKQN13AmqhrnyesRx+AYNzVFCZ49oYfj/dtz1XKbGr9anMuw6dq/avJdMfHzlEUThYFgZ2yRSUBAlOGliwwV+GRuhjRocka3wAgjxyG80VdJiovtXhoEhvd3peYC6TzPi2hPVXppVq3P+F8s payload.class
          

          生成的payload.class內容越多時間就越長,所以盡量選擇較短的命令執行來復現漏洞即可。最終會生成如下rememberMe cookies

          我們將跑出來的Cookie添加到數據包中進行發送,就可以 發現在靶機中成果創建了對應的文件。

          到這未知其實經常遇到的Shrio漏洞已經表述的差不多了,下面幾個是shiro存在但是在現實中利用難度大或者是比較少的洞,可以簡單了解一下

          Shiro 認證繞過漏洞(CVE-2020-1957)

          漏洞原理

          ? 在Apache Shiro 1.5.2以前的版本中,在使用Spring動態控制器時,攻擊者通過構造..;這樣的跳轉,可以繞過Shiro中對目錄的權限限制。

          URL請求過程:

          • 客戶端請求URL:/xxx/..;/admin/
          • Shrio 內部處理得到校驗URL為/xxxx/..,校驗通過
          • SpringBoot 處理/xxx/..;/admin/, 最終請求/admin/, 成功訪問了后臺請求。

          漏洞復現

          1、權限配置

          @Bean
          public ShiroFilterChainDefinition shiroFilterChainDefinition() {
              DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
              chainDefinition.addPathDefinition("/login.html", "authc"); // need to accept POSTs from the login form
              chainDefinition.addPathDefinition("/logout", "logout");
              chainDefinition.addPathDefinition("/admin/**", "authc");
              return chainDefinition;
          }
          

          2、默認狀態

          直接請求管理頁面/admin/,無法訪問,將會被重定向到登錄頁面

          3、繞過POC

          構造惡意請求/xxx/..;/admin/,即可繞過權限校驗,訪問到管理頁面:

          Shiro 身份驗證繞過 (CVE-2020-13933)

          漏洞簡介

          ? CVE-2020-11989的修復補丁存在缺陷,在1.5.3及其之前的版本,由于shiro在處理url時與spring仍然存在差異,依然存在身份校驗繞過漏洞由于處理身份驗證請求時出錯,遠程攻擊者可以發送特制的HTTP請求,繞過身份驗證過程并獲得對應用程序的未授權訪問。

          ? 該漏洞產生的原因主要是shiro層在處理url上和spring上存在差異,主要是在處理;上的問題,通過構造含有;符號的url即可繞過shiro在權限上的處理,而spring不負責權限管控,所以最終會導致權限繞過。ant風格的路徑僅出現一個*時才能成功,而**無法繞過,*:匹配一個或者多個任意的字符。

          **:匹配零個或者多個目錄。

          漏洞復現

          1、開啟環境

          2、正常訪問敏感界面

          /admin/admin

          提示讓我們登錄

          3、通過%3b繞過,不觸發身份驗證并且繞過權限

          /admin/%3badmin

          Shiro 授權繞過 (CVE-2022-32532)

          漏洞簡介

          Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。

          1.9.1 之前的 Apache Shiro,RegexRequestMatcher 可能被錯誤配置為在某些 servlet 容器上被繞過。在正則表達式中使用帶有.的 RegExPatternMatcher 的應用程序可能容易受到授權繞過。

          漏洞概述

          2022年6月29日,Apache 官方披露 Apache Shiro 權限繞過漏洞(CVE-2022-32532),當 Apache Shiro 中使用 RegexRequestMatcher 進行權限配置,且正則表達式中攜帶“.”時,未經授權的遠程攻擊者可通過構造惡意數據包繞過身份認證。

          影響范圍

          Apache Shiro < 1.9.1

          利用流程

          訪問

          抓包修改

          GET /permit/any HTTP/1.1
          Host: 123.58.224.8:36930
          Cache-Control: max-age=0
          Upgrade-Insecure-Requests: 1
          User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
          Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
          Accept-Encoding: gzip, deflate
          Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
          Cookie: think_lang=zh-cn
          Connection: close
          

          需要攜帶token字段

          改包繞過

          GET /permit/%0any HTTP/1.1
          Host: 123.58.224.8:36930
          Cache-Control: max-age=0
          Upgrade-Insecure-Requests: 1
          User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
          Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
          Accept-Encoding: gzip, deflate
          Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
          Cookie: think_lang=zh-cn
          Connection: close
          


          from https://www.freebuf.com/articles/web/367363.html

          hiro 概述

          Apache Shiro 是一款 Java 安全框架,不依賴任何容器,可以運行在 Java SE 和 Java EE 項目中,它的主要作用是用來做身份認證、授權、會話管理和加密等操作。

          什么意思?大白話就是判斷用戶是否登錄、是否擁有某些操作的權限等。

          其實不用 Shiro,我們使用原生 Java API 就可以完成安全管理,很簡單,使用過濾器去攔截用戶的各種請求,然后判斷是否登錄、是否擁有某些權限即可。

          我們完全可以完成這些操作,但是對于一個大型的系統,分散去管理編寫這些過濾器的邏輯會比較麻煩,不成體系,所以需要使用結構化、工程化、系統化的解決方案。

          任何一個業務邏輯,一旦上升到企業級的體量,就必須考慮使用系統化的解決方案,也就是框架,否則后期的開發成本是相當巨大的,Shiro 就是來解決安全管理的系統化框架。

          Shiro 核心組件

          1、UsernamePasswordToken,Shiro 用來封裝用戶登錄信息,使用用戶的登錄信息創建令牌 Token,登錄的過程即 Shiro 驗證令牌是否具有合法身份以及相關權限。

          2、 SecurityManager,Shiro 的核心部分,負責安全認證與授權。

          3、Subject,Shiro 的一個抽象概念,包含了用戶信息。

          4、Realm,開發者自定義的模塊,根據項目的需求,驗證和授權的邏輯在 Realm 中實現。

          5、AuthenticationInfo,用戶的角色信息集合,認證時使用。

          6、AuthorizationInfo,角色的權限信息集合,授權時使用。

          7、DefaultWebSecurityManager,安全管理器,開發者自定義的 Realm 需要注入到 DefaultWebSecurityManager 進行管理才能生效。

          8、ShiroFilterFactoryBean,過濾器工廠,Shiro 的基本運行機制是開發者定制規則,Shiro 去執行,具體的執行操作就是由 ShiroFilterFactoryBean 創建一個個 Filter 對象來完成。

          Shiro 的運行機制如下圖所示。


          Shiro 整合 Spring Boot

          1、我們使用 Spring Boot 集成 Shiro 的方式快速構建工程,創建 Spring Boot Initializr 工程,使用最新版的 Spring Boot 2.3.0。

          2、選擇需要添加的 dependencies 依賴。


          3、我們會發現 Spring Boot 官方的 Security 依賴庫中并沒有 Shiro,而是其他的框架。


          也就是說 Spring Boot 官方并沒有納入 Shiro,怎么解決?很簡單,官方不提供支持,我們就自己手動在 pom.xml 中添加依賴,如下所示,我們全部選擇最新版。

          <!-- Shiro整合Spring -->org.apache.shiroshiro-spring1.5.3

          搞定之后,工程的 Maven 依賴如下所示。


          自定義 Shiro 過濾器

          對 URL 進行攔截,沒有認證的需要認證,認證成功的則可以根據需要判斷角色及權限。

          這個過濾器需要開發者自定義,然后去指定認證和授權的邏輯,繼承抽象類 AuthorizingRealm,實現兩個抽象方法分別完成授權和認證的邏輯。

          首先來完成認證的邏輯,需要連接數據庫,這里我們使用 MyBatis Plus 來完成,pom.xml 中添加 MyBatis Plus 依賴,如下所示。

          mysqlmysql-connector-java8.0.20com.baomidoumybatis-plus-boot-starter3.3.1.tmp

          創建數據表 account,添加兩條記錄,SQL 如下所。

          CREATETABLE`account`(`id`intNOTNULLAUTO_INCREMENT,`username`varchar(20)DEFAULTNULL,`password`varchar(20)DEFAULTNULL,`perms`varchar(20)DEFAULTNULL,`role`varchar(20)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=4DEFAULTCHARSET=utf8;LOCKTABLES`account`WRITE;INSERTINTO`account`VALUES(1,'zs','123123','',''),(2,'ls','123123','manage',''),(3,'ww','123123','manage','administrator');UNLOCKTABLES;

          創建實體類 Account。

          @DatapublicclassAccount {privateInteger id;privateStringusername;privateStringpassword;privateStringperms;privateStringrole;}

          創建 AccountMapper 接口。

          publicinterfaceAccountMapperextendsBaseMapper{}

          創建 application.yml。

          spring:datasource:url:jdbc:mysql://localhost:3306/testusername:rootpassword:rootdriver-class-name:com.mysql.cj.jdbc.Drivermybatis-plus:configuration:log-impl:org.apache.ibatis.logging.stdout.StdOutImpl

          啟動類添加 @MapperScan 注解掃描 Mapper 接口。

          @SpringBootApplication@MapperScan("com.southwind.springbootshirodemo.mapper")publicclassSpringbootshirodemoApplication{publicstaticvoidmain(String[] args){        SpringApplication.run(SpringbootshirodemoApplication.class, args);    }}

          首先通過單元測試調試 AccoutMapper 接口。

          @SpringBootTestclassAccountMapperTest{@AutowiredprivateAccountMapper accountMapper;@Testvoidtest(){QueryWrapper wrapper =newQueryWrapper();wrapper.eq("username","user");        Account account = accountMapper.selectOne(wrapper);        System.out.println(account);    }}


          返回上圖表示調試成功,MyBatis Plus 調試成功,接下來完成 Service 層代碼編寫。

          publicinterfaceAccountService{publicAccountfindByUsername(String username);}
          publicclassAccountServiceImplimplementsAccountService{@AutowiredprivateAccountMapper accountMapper;@OverridepublicAccountfindByUsername(String username){QueryWrapper wrapper =newQueryWrapper();wrapper.eq("username",username);returnaccountMapper.selectOne(wrapper);    }}

          回到 Shiro 完成用戶認證,在 MyRealm 中完成代碼的編寫。

          publicclassMyRealmextendsAuthorizingRealm{@AutowiredprivateAccountService accountService;/**    * 授權*@paramprincipalCollection*@return    */@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principalCollection){returnnull;    }/**    * 認證*@paramauthenticationToken*@return*@throwsAuthenticationException    */@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationToken authenticationToken)throwsAuthenticationException{        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;        Account account = accountService.findByUsername(token.getUsername());if(account !=null){returnnewSimpleAuthenticationInfo(account,account.getPassword(),getName());        }returnnull;    }}

          客戶端傳來的 username 和 password 會自動封裝到 token,先根據 username 進行查詢,如果返回 null,則表示用戶名錯誤,直接 return null 即可,Shiro 會自動拋出 UnknownAccountException 異常。

          如果返回不為 null,則表示用戶名正確,再驗證密碼,直接返回 SimpleAuthenticationInfo 對象即可,如果密碼驗證成功,Shiro 認證通過,否則返回 IncorrectCredentialsException 異常。

          自定義過濾器創建完成之后,需要進行配置才能生效,在 Spring Boot 應用中,不需要任何的 XML 配置,直接通過配置類進行裝配,代碼如下所示。

          @ConfigurationpublicclassShiroConfig{@BeanpublicShiroFilterFactoryBean filterFactoryBean(@Qualifier("manager")DefaultWebSecurityManager manager){        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();        factoryBean.setSecurityManager(manager);returnfactoryBean;    }@BeanpublicDefaultWebSecurityManager manager(@Qualifier("myRealm")MyRealm myRealm){        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();        manager.setRealm(myRealm);returnmanager;    }@BeanpublicMyRealm myRealm(){returnnew MyRealm();    }}

          這個配置類中一共自動裝配了 3 個 bean 實例,第一個是自定義過濾器 MyRealm,我們的業務邏輯全部定義在這個 bean 中。

          然后需要創建第二個 bean 示例 DefaultWebSecurityManager,并且將 MyRealm 注入到 DefaultWebSecurityManager bean 中,完成注冊。

          最終需要裝配第三個 bean ShiroFilterFactoryBean,這是 Shiro 自帶的一個 Filter 工廠實例,所有的認證和授權判斷都是由這個 bean 生成的 Filter 對象來完成的,這就是 Shiro 框架的運行機制,開發者只需要定義規則,進行配置,具體的執行者全部由 Shiro 自己創建的 Filter 來完成。

          所以我們需要給 ShiroFilterFactoryBean 實例注入認證及授權規則,如下所示。

          認證過濾器:

          anon:無需認證即可訪問,游客身份。

          authc:必須認證(登錄)才能訪問。

          authcBasic:需要通過 httpBasic 認證。

          user:不一定已通過認證,只要是曾經被 Shiro 記住過登錄狀態的用戶就可以正常發起請求,比如 rememberMe。

          授權過濾器:

          perms:必須擁有對某個資源的訪問權限(授權)才能訪問。

          role:必須擁有某個角色權限才能訪問。

          port:請求的端口必須為指定值才可以訪問。

          rest:請求必須是 RESTful,method 為 post、get、delete、put。

          ssl:必須是安全的 URL 請求,協議為 HTTPS。

          比如,我們創建三個頁面,main.html、manage.html、administrator.html,要求如下:

          1、必須是登錄狀態才可以訪問 main.html。

          2、用戶必須擁有 manage 授權才可以訪問 manage.html。

          3、用戶必須擁有 administrator 角色才能訪問 administrator.html。

          代碼如下所示。

          @BeanpublicShiroFilterFactoryBeanfilterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager manager){ShiroFilterFactoryBean factoryBean =newShiroFilterFactoryBean();    factoryBean.setSecurityManager(manager);Mapmap=newHashMap<>();map.put("/main","authc");map.put("/manage","perms[manage]");map.put("/administrator","roles[administrator]");factoryBean.setFilterChainDefinitionMap(map);//設置登錄頁面factoryBean.setLoginUrl("/login");//未授權頁面factoryBean.setUnauthorizedUrl("/unauth");returnfactoryBean;}

          Controller 如下所示。

          @ControllerpublicclassMyController{@GetMapping("/{url}")publicString redirect(@PathVariable("url")String url){returnurl;    }@PostMapping("/login")publicString login(String username, String password, Model model){        Subject subject = SecurityUtils.getSubject();        UsernamePasswordToken token = new UsernamePasswordToken(username,password);try{            subject.login(token);return"index";}catch(UnknownAccountException e) {model.addAttribute("msg","用戶名錯誤");return"login";}catch(IncorrectCredentialsException e) {model.addAttribute("msg","密碼錯誤");return"login";        }    }@RequestMapping("/unauth")@ResponseBodypublicString unauth(){return"未授權沒有訪問權限";    }}

          現在只需要登錄就可以訪問 main.html,但是無法訪問 manage.html,這是因為沒有授權,接下來我們完成授權操作,回到 MyRealm,代碼如下所示。

          @OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principalCollection){//獲取當前登錄對象    Subject subject = SecurityUtils.getSubject();    Account account = (Account) subject.getPrincipal();//設置角色Set roles =newHashSet<>();roles.add(account.getRole());SimpleAuthorizationInfo info =newSimpleAuthorizationInfo(roles);//設置權限    info.addStringPermission(account.getPerms());returninfo;}

          數據庫數據如下所示。


          zs 沒有權限和角色,所以登錄之后只能訪問 main.html。

          ls 擁有 manage 權限,沒有角色,所以登錄之后可以訪問 main.html、manage.html。

          ww 擁有 manage 權限和 administrator 角色,所以登錄之后可以訪問 main.html、manage.html、administrator.html。

          Shiro 整合 Thymeleaf

          1、pom.xml 中引入依賴。

          <!-- Shiro整合Thymeleaf -->com.github.theborakompanionithymeleaf-extras-shiro2.0.0

          2、配置類添加 ShiroDialect。

          @BeanpublicShiroDialectshiroDialect(){returnnewShiroDialect();}

          3、Controller 登錄成功后將用戶信息存入 session,同時添加退出操作。

          @PostMapping("/login")publicString login(String username, String password, Model model){    Subject subject = SecurityUtils.getSubject();    UsernamePasswordToken token = new UsernamePasswordToken(username,password);try{        subject.login(token);        Account account = (Account) subject.getPrincipal();subject.getSession().setAttribute("account",account);return"index";}catch(UnknownAccountException e) {model.addAttribute("msg","用戶名錯誤");return"login";}catch(IncorrectCredentialsException e) {model.addAttribute("msg","密碼錯誤");return"login";    }}@GetMapping("/logout")publicString logout(){    Subject subject = SecurityUtils.getSubject();    subject.logout();return"login";}

          4、index.html。


          主站蜘蛛池模板: 91精品福利一区二区三区野战| 农村人乱弄一区二区| 在线视频一区二区| 无码乱码av天堂一区二区 | 欧美日韩精品一区二区在线观看| 精品一区二区91| 中文乱码人妻系列一区二区 | 国产高清在线精品一区| 国产午夜福利精品一区二区三区| 国产主播福利精品一区二区| 国产日韩一区二区三区在线播放 | 人成精品视频三区二区一区| 女人18毛片a级毛片一区二区| 久久久老熟女一区二区三区| 日本道免费精品一区二区| 久久精品人妻一区二区三区 | 色噜噜狠狠一区二区| 一区二区三区国产精品| 亚洲电影一区二区三区| 国产成人精品一区二区秒拍 | 日本一道高清一区二区三区| 国产精品福利一区二区| AA区一区二区三无码精片| 国产91久久精品一区二区| 无码国产精品一区二区免费式直播 | 日韩十八禁一区二区久久| 色婷婷香蕉在线一区二区| 无码夜色一区二区三区| 蜜桃臀无码内射一区二区三区| 日韩精品成人一区二区三区| 国产精品日本一区二区不卡视频 | 交换国产精品视频一区| 国产精品无码一区二区在线| 成人精品视频一区二区三区尤物| 精品人妻无码一区二区色欲产成人 | 国产免费私拍一区二区三区| 日韩一区二区a片免费观看| 亚洲熟妇成人精品一区| 日韩社区一区二区三区| 亚洲AV色香蕉一区二区| 亚洲熟妇无码一区二区三区 |