整合營銷服務商

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

          免費咨詢熱線:

          Spring Boot 系列@ControllerA

          Spring Boot 系列@ControllerAdvice 攔截異常并統一處理

          、介紹

          創建 MyControllerAdvice,并添加 @ControllerAdvice注解。

          package com.sam.demo.controller;
          import org.springframework.ui.Model;
          import org.springframework.web.bind.WebDataBinder;
          import org.springframework.web.bind.annotation.*;
          import java.util.HashMap;
          import java.util.Map;
          @ControllerAdvice
          public class MyControllerAdvice {
           /**
           * 應用到所有@RequestMapping注解方法,在其執行之前初始化數據綁定器
           * @param binder
           */
           @InitBinder
           public void initBinder(WebDataBinder binder) {}
           /**
           * 把值綁定到Model中,使全局@RequestMapping可以獲取到該值
           * @param model
           */
           @ModelAttribute
           public void addAttributes(Model model) {
           model.addAttribute("author", "Magical Sam");
           }
           /**
           * 全局異常捕捉處理
           * @param ex
           * @return
           */
           @ResponseBody
           @ExceptionHandler(value=Exception.class)
           public Map errorHandler(Exception ex) {
           Map map=new HashMap();
           map.put("code", 100);
           map.put("msg", ex.getMessage());
           return map;
           }
          }
          

          啟動應用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法,都會作用在 被 @RequestMapping 注解的方法上。

          @ModelAttribute:在Model上設置的值,對于所有被 @RequestMapping 注解的方法中,都可以通過 ModelMap 獲取,如下:

          @RequestMapping("/home")
          public String home(ModelMap modelMap) {
           System.out.println(modelMap.get("author"));
          }
          //或者 通過@ModelAttribute獲取
          @RequestMapping("/home")
          public String home(@ModelAttribute("author") String author) {
           System.out.println(author);
          }
          

          @ExceptionHandler 攔截了異常,我們可以通過該注解實現自定義異常處理。其中,@ExceptionHandler 配置的 value 指定需要攔截的異常類型,上面攔截了 Exception.class 這種異常。

          二、自定義異常處理(全局異常處理)

          spring boot 默認情況下會映射到 /error 進行異常處理,但是提示并不十分友好,下面自定義異常處理,提供友好展示。

          1、編寫自定義異常類:

          package com.sam.demo.custom;
          public class MyException extends RuntimeException {
           public MyException(String code, String msg) {
           this.code=code;
           this.msg=msg;
           }
           private String code;
           private String msg;
           // getter & setter
          }
          

          注:spring 對于 RuntimeException 異常才會進行事務回滾。

          2、編寫全局異常處理類

          創建 MyControllerAdvice.java,如下:

          package com.sam.demo.controller;
          import org.springframework.ui.Model;
          import org.springframework.web.bind.WebDataBinder;
          import org.springframework.web.bind.annotation.*;
          import java.util.HashMap;
          import java.util.Map;
          @ControllerAdvice
          public class MyControllerAdvice {
           /**
           * 全局異常捕捉處理
           * @param ex
           * @return
           */
           @ResponseBody
           @ExceptionHandler(value=Exception.class)
           public Map errorHandler(Exception ex) {
           Map map=new HashMap();
           map.put("code", 100);
           map.put("msg", ex.getMessage());
           return map;
           }
           
           /**
           * 攔截捕捉自定義異常 MyException.class
           * @param ex
           * @return
           */
           @ResponseBody
           @ExceptionHandler(value=MyException.class)
           public Map myErrorHandler(MyException ex) {
           Map map=new HashMap();
           map.put("code", ex.getCode());
           map.put("msg", ex.getMsg());
           return map;
           }
          }
          

          3、controller中拋出異常進行測試。

          @RequestMapping("/home")
          public String home() throws Exception {
          // throw new Exception("Sam 錯誤");
           throw new MyException("101", "Sam 錯誤");
          }
          

          啟動應用,訪問:http://localhost:8080/home ,正常顯示以下json內容,證明自定義異常已經成功被攔截。

          {"msg":"Sam 錯誤","code":"101"}
          

          * 如果不需要返回json數據,而要渲染某個頁面模板返回給瀏覽器,那么MyControllerAdvice中可以這么實現:

          @ExceptionHandler(value=MyException.class)
          public ModelAndView myErrorHandler(MyException ex) {
           ModelAndView modelAndView=new ModelAndView();
           modelAndView.setViewName("error");
           modelAndView.addObject("code", ex.getCode());
           modelAndView.addObject("msg", ex.getMsg());
           return modelAndView;
          }
          

          在 templates 目錄下,添加 error.ftl(這里使用freemarker) 進行渲染:

          <!DOCTYPE html>
          <html lang="en">
          <head>
           <meta charset="UTF-8">
           <title>錯誤頁面</title>
          </head>
          <body>
           <h1>${code}</h1>
           <h1>${msg}</h1>
          </body>
          </html>
          

          重啟應用,http://localhost:8080/home 顯示自定的錯誤頁面內容。

          補充:如果全部異常處理返回json,那么可以使用 @RestControllerAdvice 代替 @ControllerAdvice ,這樣在方法上就可以不需要添加 @ResponseBody。

          文通過一個簡易安全認證示例的開發實踐,理解過濾器和攔截器的工作原理。

          很多文章都將過濾器(Filter)、攔截器(Interceptor)和監聽器(Listener)這三者和Spring關聯起來講解,并認為過濾器(Filter)、攔截器(Interceptor)和監聽器(Listener)是Spring提供的應用廣泛的組件功能。

          但是嚴格來說,過濾器和監聽器屬于Servlet范疇的API,和Spring沒什么關系。

          因為過濾器繼承自javax.servlet.Filter接口,監聽器繼承自javax.servlet.ServletContextListener接口,只有攔截器繼承的是org.springframework.web.servlet.HandlerInterceptor接口。

          上面的流程圖參考自網上資料,一圖勝千言。看完本文以后,將對過濾器和攔截器的調用過程會有更深刻理解。

          一、安全認證設計思路

          有時候內外網調用API,對安全性的要求不一樣,很多情況下外網調用API的種種限制在內網根本沒有必要,但是網關部署的時候,可能因為成本和復雜度等問題,內外網要調用的API會部署在一起。

          實現REST接口的安全性,可以通過成熟框架如Spring Security或者shiro搞定。

          但是因為安全框架往往實現復雜(我數了下Spring Security,洋洋灑灑大概有11個核心模塊,shiro的源碼代碼量也比較驚人)同時可能要引入復雜配置(能不能讓人痛快一點),不利于中小團隊的靈活快速開發、部署及問題排查。

          很多團隊自己造輪子實現安全認證,本文這個簡易認證示例參考自我所在的前廠開發團隊,可以認為是個基于token的安全認證服務。

          大致設計思路如下:

          1、自定義http請求頭,每次調用API都在請求頭里傳人一個token值

          2、token放在緩存(如redis)中,根據業務和API的不同設置不同策略的過期時間

          3、token可以設置白名單和黑名單,可以限制API調用頻率,便于開發和測試,便于緊急處理異狀,甚至臨時關閉API

          4、外網調用必須傳人token,token可以和用戶有關系,比如每次打開頁面或者登錄生成token寫入請求頭,頁面驗證cookie和token有效性等

          在Spring Security框架里有兩個概念,即認證授權,認證指可以訪問系統的用戶,而授權則是用戶可以訪問的資源。

          實現上述簡易安全認證需求,你可能需要獨立出一個token服務,保證生成token全局唯一,可能包含的模塊有自定義流水生成器、CRM、加解密、日志、API統計、緩存等,但是和用戶(CRM)其實是弱綁定關系。某些和用戶有關系的公共服務,比如我們經常用到的發送短信SMS和郵件服務,也可以通過token機制解決安全調用問題。

          綜上,本文的簡易安全認證其實和Spring Security框架提供的認證和授權有點不一樣,當然,這種“安全”處理方式對專業人士沒什么新意,但是可以對外擋掉很大一部分小白用戶。

          二、自定義過濾器

          和Spring MVC類似,Spring Boot提供了很多servlet過濾器(Filter)可使用,并且它自動添加了一些常用過濾器,比如CharacterEncodingFilter(用于處理編碼問題)、HiddenHttpMethodFilter(隱藏HTTP函數)、HttpPutFormContentFilter(form表單處理)、RequestContextFilter(請求上下文)等。通常我們還會自定義Filter實現一些通用功能,比如記錄日志、判斷是否登錄、權限驗證等。

          1、自定義請求頭

          很簡單,在request header添加自定義請求頭authtoken:

           @RequestMapping(value="/getinfobyid", method=RequestMethod.POST)
           @ApiOperation("根據商品Id查詢商品信息")
           @ApiImplicitParams({
           @ApiImplicitParam(paramType="header", name="authtoken", required=true, value="authtoken", dataType="String"),
           })
           public GetGoodsByGoodsIdResponse getGoodsByGoodsId(@RequestHeader String authtoken, @RequestBody GetGoodsByGoodsIdRequest request) {
           return _goodsApiService.getGoodsByGoodsId(request);
           }
          

          加了@RequestHeader修飾的authtoken字段就可以在swagger這樣的框架下顯示出來。

          調用后,可以根據http工具看到請求頭,本文示例是authtoken(和某些框架的token區分開):

          備注:很多httpclient工具都支持動態傳人請求頭,比如RestTemplate。

          2、實現Filter

          Filter接口共有三個方法,即init,doFilter和destory,看到名稱就大概知道它們主要用途了,通常我們只要在doFilter這個方法內,對Http請求進行處理:

          package com.power.demo.controller.filter;
          import com.power.demo.common.AppConst;
          import com.power.demo.common.BizResult;
          import com.power.demo.service.contract.AuthTokenService;
          import com.power.demo.util.PowerLogger;
          import com.power.demo.util.SerializeUtil;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.stereotype.Component;
          import javax.servlet.*;
          import javax.servlet.http.HttpServletRequest;
          import java.io.IOException;
          @Component
          public class AuthTokenFilter implements Filter {
           @Autowired
           private AuthTokenService authTokenService;
           @Override
           public void init(FilterConfig var1) throws ServletException {
           }
           @Override
           public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
           throws IOException, ServletException {
           HttpServletRequest req=(HttpServletRequest) request;
           String token=req.getHeader(AppConst.AUTH_TOKEN);
           BizResult<String> bizResult=authTokenService.powerCheck(token);
           System.out.println(SerializeUtil.Serialize(bizResult));
           if (bizResult.getIsOK()==true) {
           PowerLogger.info("auth token filter passed");
           chain.doFilter(request, response);
           } else {
           throw new ServletException(bizResult.getMessage());
           }
           }
           @Override
           public void destroy() {
           }
          }
          

          注意,Filter這樣的東西,我認為從實際分層角度,多數處理的還是表現層偏多,不建議在Filter中直接使用數據訪問層Dao,雖然這樣的代碼一兩年前我在很多老古董項目中看到過很多次,而且<<Spring實戰>>的書里也有這樣寫的先例。

          3、認證服務

          這里就是主要業務邏輯了,示例代碼只是簡單寫下思路,不要輕易就用于生產環境:

          package com.power.demo.service.impl;
          import com.power.demo.cache.PowerCacheBuilder;
          import com.power.demo.common.BizResult;
          import com.power.demo.service.contract.AuthTokenService;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.stereotype.Component;
          import org.springframework.util.StringUtils;
          @Component
          public class AuthTokenServiceImpl implements AuthTokenService {
           @Autowired
           private PowerCacheBuilder cacheBuilder;
           /*
           * 驗證請求頭token是否合法
           * */
           @Override
           public BizResult<String> powerCheck(String token) {
           BizResult<String> bizResult=new BizResult<>(true, "驗證通過");
           System.out.println("token的值為:" + token);
           if (StringUtils.isEmpty(token)==true) {
           bizResult.setFail("authtoken為空");
           return bizResult;
           }
           //處理黑名單
           bizResult=checkForbidList(token);
           if (bizResult.getIsOK()==false) {
           return bizResult;
           }
           //處理白名單
           bizResult=checkAllowList(token);
           if (bizResult.getIsOK()==false) {
           return bizResult;
           }
           String key=String.format("Power.AuthTokenService.%s", token);
           //cacheBuilder.set(key, token);
           //cacheBuilder.set(key, token.toUpperCase());
           //從緩存中取
           String existToken=cacheBuilder.get(key);
           if (StringUtils.isEmpty(existToken)==true) {
           bizResult.setFail(String.format("不存在此authtoken:%s", token));
           return bizResult;
           }
           //比較token是否相同
           Boolean isEqual=token.equals(existToken);
           if (isEqual==false) {
           bizResult.setFail(String.format("不合法的authtoken:%s", token));
           return bizResult;
           }
           //do something
           return bizResult;
           }
          }
          

          用到的緩存服務可以參考這里,這個也是我在前廠的經驗總結。

          4、注冊Filter

          常見的有兩種寫法:

          (1)、使用@WebFilter注解來標識Filter

          @Order(1)
          @WebFilter(urlPatterns={"/api/v1/goods/*", "/api/v1/userinfo/*"})
          public class AuthTokenFilter implements Filter {
          

          使用@WebFilter注解,還可以配合使用@Order注解,@Order注解表示執行過濾順序,值越小,越先執行,這個Order大小在我們編程過程中就像處理HTTP請求的生命周期一樣大有用處。當然,如果沒有指定Order,則過濾器的調用順序跟添加的過濾器順序相反,過濾器的實現是責任鏈模式。

          最后,在啟動類上添加@ServletComponentScan 注解即可正常使用自定義過濾器了。

          (2)、使用FilterRegistrationBean對Filter進行自定義注冊

          本文以第二種實現自定義Filter注冊:

          package com.power.demo.controller.filter;
          import com.google.common.collect.Lists;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.boot.web.servlet.FilterRegistrationBean;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.stereotype.Component;
          import java.util.List;
          @Configuration
          @Component
          public class RestFilterConfig {
           @Autowired
           private AuthTokenFilter filter;
           @Bean
           public FilterRegistrationBean filterRegistrationBean() {
           FilterRegistrationBean registrationBean=new FilterRegistrationBean();
           registrationBean.setFilter(filter);
           //設置(模糊)匹配的url
           List<String> urlPatterns=Lists.newArrayList();
           urlPatterns.add("/api/v1/goods/*");
           urlPatterns.add("/api/v1/userinfo/*");
           registrationBean.setUrlPatterns(urlPatterns);
           registrationBean.setOrder(1);
           registrationBean.setEnabled(true);
           return registrationBean;
           }
          }
          

          請大家特別注意urlPatterns,屬性urlPatterns指定要過濾的URL模式。對于Filter的作用區域,這個參數居功至偉。

          注冊好Filter,當Spring Boot啟動時監測到有javax.servlet.Filter的bean時就會自動加入過濾器調用鏈ApplicationFilterChain。

          調用一個API試試效果:

          通常情況下,我們在Spring Boot下都會自定義一個全局統一的異常管理增強GlobalExceptionHandler(和上面這個顯示會略有不同)。

          根據我的實踐,過濾器里拋出異常,不會被全局唯一的異常管理增強捕獲到并進行處理,這個和攔截器Inteceptor以及下一篇文章介紹的自定義AOP攔截不同。

          到這里,一個通過自定義Filter實現的簡易安全認證服務就搞定了。

          三、自定義攔截器

          1、實現攔截器

          繼承接口HandlerInterceptor,實現攔截器,接口方法有下面三個:

          preHandle是請求執行前執行

          postHandle是請求結束執行

          afterCompletion是視圖渲染完成后執行

          package com.power.demo.controller.interceptor;
          import com.power.demo.common.AppConst;
          import com.power.demo.common.BizResult;
          import com.power.demo.service.contract.AuthTokenService;
          import com.power.demo.util.PowerLogger;
          import com.power.demo.util.SerializeUtil;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.stereotype.Component;
          import org.springframework.web.servlet.HandlerInterceptor;
          import org.springframework.web.servlet.ModelAndView;
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
          /*
           * 認證token攔截器
           * */
          @Component
          public class AuthTokenInterceptor implements HandlerInterceptor {
           @Autowired
           private AuthTokenService authTokenService;
           /*
           * 請求執行前執行
           * */
           @Override
           public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
           boolean handleResult=false;
           String token=request.getHeader(AppConst.AUTH_TOKEN);
           BizResult<String> bizResult=authTokenService.powerCheck(token);
           System.out.println(SerializeUtil.Serialize(bizResult));
           handleResult=bizResult.getIsOK();
           PowerLogger.info("auth token interceptor攔截結果:" + handleResult);
           if (bizResult.getIsOK()==true) {
           PowerLogger.info("auth token interceptor passed");
           } else {
           throw new Exception(bizResult.getMessage());
           }
           return handleResult;
           }
           /*
           * 請求結束執行
           * */
           @Override
           public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
           }
           /*
           * 視圖渲染完成后執行
           * */
           @Override
           public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
           }
          }
          

          示例中,我們選擇在請求執行前進行token安全認證。

          認證服務就是過濾器里介紹的AuthTokenService,業務邏輯層實現復用。

          2、注冊攔截器

          定義一個InterceptorConfig類,繼承自WebMvcConfigurationSupport,WebMvcConfigurerAdapter已經過時。

          將AuthTokenInterceptor作為bean注入,其他設置攔截器攔截的URL和過濾器非常相似:

          package com.power.demo.controller.interceptor;
          import com.google.common.collect.Lists;
          import org.springframework.context.annotation.Bean;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.stereotype.Component;
          import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
          import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
          import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
          import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
          import java.util.List;
          @Configuration
          @Component
          public class InterceptorConfig extends WebMvcConfigurationSupport { //WebMvcConfigurerAdapter已經過時
           private static final String FAVICON_URL="/favicon.ico";
           /**
           * 發現如果繼承了WebMvcConfigurationSupport,則在yml中配置的相關內容會失效。
           *
           * @param registry
           */
           @Override
           public void addResourceHandlers(ResourceHandlerRegistry registry) {
           registry.addResourceHandler("/").addResourceLocations("/**");
           registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
           }
           /**
           * 配置servlet處理
           */
           @Override
           public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
           configurer.enable();
           }
           @Override
           public void addInterceptors(InterceptorRegistry registry) {
           //設置(模糊)匹配的url
           List<String> urlPatterns=Lists.newArrayList();
           urlPatterns.add("/api/v1/goods/*");
           urlPatterns.add("/api/v1/userinfo/*");
           registry.addInterceptor(authTokenInterceptor()).addPathPatterns(urlPatterns).excludePathPatterns(FAVICON_URL);
           super.addInterceptors(registry);
           }
           //將攔截器作為bean寫入配置中
           @Bean
           public AuthTokenInterceptor authTokenInterceptor() {
           return new AuthTokenInterceptor();
           }
          }
          

          啟動應用后,調用接口就可以看到攔截器攔截的效果了。全局統一的異常管理GlobalExceptionHandler捕獲異常后處理如下:

          和過濾器顯示的主要錯誤提示信息幾乎一樣,但是堆棧信息更加豐富。

          四、過濾器和攔截器區別

          主要區別如下:

          1、攔截器主要是基于java的反射機制的,而過濾器是基于函數回調

          2、攔截器不依賴于servlet容器,過濾器依賴于servlet容器

          3、攔截器只能對action請求起作用,而過濾器則可以對幾乎所有的請求起作用

          4、攔截器可以訪問action上下文、值棧里的對象,而過濾器不能訪問

          5、在action的生命周期中,攔截器可以多次被調用,而過濾器只能在容器初始化時被調用一次

          參考過的一些文章,有的說“攔截器可以獲取IOC容器中的各個bean,而過濾器就不行,這點很重要,在攔截器里注入一個service,可以調用業務邏輯”,經過實際驗證,這是不對的。

          注意:過濾器的觸發時機是容器后,servlet之前,所以過濾器的doFilter(ServletRequest request, ServletResponse response, FilterChain chain)的入參是ServletRequest,而不是HttpServletRequest,因為過濾器是在HttpServlet之前。下面這個圖,可以讓你對Filter和Interceptor的執行時機有更加直觀的認識:

          只有經過DispatcherServlet 的請求,才會走攔截器鏈,自定義的Servlet請求是不會被攔截的,比如我們自定義的Servlet地址http://localhost:9090/testServlet是不會被攔截器攔截的。但不管是屬于哪個Servlet,只要符合過濾器的過濾規則,過濾器都會執行。

          根據上述分析,理解原理,實際操作就簡單了,哪怕是ASP.NET過濾器亦然。

          問題:實現更加靈活的安全認證

          在Java Web下通過自定義過濾器Filter或者攔截器Interceptor配置urlPatterns,可以實現對特定匹配的API進行安全認證,比如匹配所有API、匹配某個或某幾個API等,但是有時候這種匹配模式對開發人員相對不夠友好。

          我們可以參考Spring Security那樣,通過注解+SpEL實現強大功能。

          又比如在ASP.NET中,我們經常用到Authorized特性,這個特性可以加在類上,也可以作用于方法上,可以更加動態靈活地控制安全認證。

          我們沒有選擇Spring Security,那就自己實現類似Authorized的靈活的安全認證,主要實現技術就是我們所熟知的AOP。

          通過AOP方式實現更靈活的攔截的基礎知識本文就先不提了,更多的關于AOP的話題將在下篇文章分享。

          原文:https://www.cnblogs.com/jeffwongishandsome/p/spring-boot-use-filter-and-interceptor-to-implement-an-easy-auth-system.html

          同源策略:判斷是否是同源的,主要看這三點,協議,ip,端口。

          同源策略就是瀏覽器出于網站安全性的考慮,限制不同源之間的資源相互訪問的一種政策。

          比如在域名https://www.baidu.com下,腳本不能夠訪問https://www.sina.com源下的資源,否則將會被瀏覽器攔截。

          注意兩點:

          1.必須是腳本請求,比如AJAX請求。

          但是如下情況不會產生跨域攔截

          <img src="xxx"/>
          <a href='xxx"> </a>
          

          2.跨域攔截是前端請求已經發出,并且在后端返回響應時檢查相關參數,是否允許接收后端請求。

          在微服務開發中,一個系統包含多個微服務,會存在跨域請求的場景。

          本文主要講解SpringBoot解決跨域請求攔截的問題。

          搭建項目

          這里創建兩個web項目,web1 和 web2.

          web2項目請求web1項目的資源。

          這里只貼關鍵代碼,完整代碼參考GitHub

          WEB2

          創建一個Controller返回html頁面

          @Slf4j
          @Controller
          public class HomeController {
           @RequestMapping("/index")
           public String home(){
           log.info("/index");
           return "/home";
           }
          }
          

          html頁面 home.html

          這里創建了一個按鈕,按鈕按下則請求資源:"http://localhost:8301/hello"

          <!DOCTYPE html>
          <html lang="en">
          <head>
           <meta charset="UTF-8">
           <title>web2</title>
           <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
           </script>
           <script>
           $(function () {
           
           $("#testBtn").click(function () {
           console.log("testbtn ...");
           $.get("http://localhost:8301/hello",function(data,status){
           alert("數據: " + data + "\n狀態: " + status);
           });
           })
           
           })
           </script>
          </head>
          <body>
           web2
           <button id="testBtn">測試</button>
          </body>
          </html>
          

          WEB1

          @Slf4j
          @RestController
          public class Web1Controller {
           @RequestMapping("/hello")
           public String hello(){
           log.info("hello ");
           return "hello," + new Date().toString();
           }
          }
          

          這里配置兩個項目為不同的端口。

          WEB1為8301

          WEB2為8302

          因此是不同源的。

          測試

          在web1還沒有配置允許跨域訪問的情況下

          按下按鈕,將會出現錯誤。顯示Header中沒有Access-Control-Allow-Origin

          Access to XMLHttpRequest at 'http://localhost:8301/hello' from origin 'http://localhost:8300' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
          

          WEB1添加允許跨域請求,通過實現WebMvcConfigurer

          @Configuration
          public class WebMvcConfig implements WebMvcConfigurer {
           @Override
           public void addCorsMappings(CorsRegistry registry) {
           registry.addMapping("/hello");
           }
          }
          

          再次訪問將會返回正常數據。

          除了以上的配置外,還可以做更細致的限制

          比如對請求的headers,請求的方法POST/GET...。請求的源進行限制。

          同時還可以使用注解 @CrossOrigin來替換上面的配置。

          @Slf4j
          @RestController
          public class Web1Controller {
           @CrossOrigin
           @RequestMapping("/hello")
           public String hello(){
           log.info("hello ");
           return "hello," + new Date().toString();
           }
          }
          

          注解可以用在類上,也可以用在方法上,但必須是控制器類

          配置和上面一樣,也是可以對方法,header,源進行個性化限制。

          @Target({ElementType.METHOD, ElementType.TYPE})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public @interface CrossOrigin {
           /** @deprecated */
           @Deprecated
           String[] DEFAULT_ORIGINS=new String[]{"*"};
           /** @deprecated */
           @Deprecated
           String[] DEFAULT_ALLOWED_HEADERS=new String[]{"*"};
           /** @deprecated */
           @Deprecated
           boolean DEFAULT_ALLOW_CREDENTIALS=false;
           /** @deprecated */
           @Deprecated
           long DEFAULT_MAX_AGE=1800L;
           @AliasFor("origins")
           String[] value() default {};
           @AliasFor("value")
           String[] origins() default {};
           String[] allowedHeaders() default {};
           String[] exposedHeaders() default {};
           RequestMethod[] methods() default {};
           String allowCredentials() default "";
           long maxAge() default -1L;
          }
          

          歡迎工作一到五年的Java工程師朋友們加入Java程序員開發: 721575865

          群內提供免費的Java架構學習資料(里面有高可用、高并發、高性能及分布式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!


          主站蜘蛛池模板: 久久国产精品免费一区| 久久亚洲中文字幕精品一区| 美女视频在线一区二区三区| 亚洲AV无码一区二区二三区软件| 亚洲日韩一区精品射精| 一区二区三区无码视频免费福利| 国模大胆一区二区三区| 国产AⅤ精品一区二区三区久久| 亚洲AV一区二区三区四区| 无码囯产精品一区二区免费| 国模无码人体一区二区| 久久久久人妻精品一区三寸蜜桃| 国产在线一区二区杨幂| 日韩精品国产一区| 日本精品无码一区二区三区久久久 | 国产精品免费一区二区三区| 国产精品熟女视频一区二区| 色一情一乱一伦一区二区三欧美| 久久精品免费一区二区三区| 97久久精品一区二区三区| 538国产精品一区二区在线| 日韩精品一区二区三区国语自制| 久久中文字幕无码一区二区| 毛片一区二区三区无码| 美日韩一区二区三区| 日韩精品一区二区三区中文3d| 秋霞日韩一区二区三区在线观看 | 亚洲国产精品第一区二区三区| 精品一区二区三区中文| 亚洲国产精品一区二区九九| 国产在线不卡一区二区三区| 午夜福利一区二区三区在线观看| 无码人妻少妇色欲AV一区二区| 精品一区二区久久久久久久网站| 国产精品女同一区二区| 亚洲一区二区三区四区视频| 精品国产区一区二区三区在线观看| 精品女同一区二区| 一本AV高清一区二区三区| 亚洲色偷偷偷网站色偷一区| 成人毛片无码一区二区|