整合營銷服務商

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

          免費咨詢熱線:

          SpringSecurity和JWT實現認證和授權

          SpringSecurity和JWT實現認證和授權

          pringSecurity

          SpringSecurity是一個強大的可高度定制的認證和授權框架,對于Spring應用來說它是一套Web安全標準。SpringSecurity注重于為Java應用提供認證和授權功能,像所有的Spring項目一樣,它對自定義需求具有強大的擴展性。

          JWT

          JWT是JSON WEB TOKEN的縮寫,它是基于 RFC 7519
          標準定義的一種可以安全傳輸的的JSON對象,由于使用了數字簽名,所以是可信任和安全的。

          JWT的組成

          • WT token的格式:header.payload.signature
          • header中用于存放簽名的生成算法
          {"alg": "HS512"}
          • payload中用于存放用戶名、token的生成時間和過期時間
          {"sub":"admin","created":1489079981393,"exp":1489684781}
          • signature為以header和payload生成的簽名
          String signature=HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)

          JWT實例

          eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE1NTY3NzkxMjUzMDksImV4cCI6MTU1NzM4MzkyNX0.d-iki0193X0bBOETf2UN3r3PotNIEAV7mzIxxeI5IxFyzzkOZxS0PGfF_SK6wxCv2K8S0cZjMkv6b5bCqc0VBw

          JWT實現認證和授權的原理

          • 用戶調用登錄接口,登錄成功后獲取到JWT的token;
          • 之后用戶每次調用接口都在http的header中添加一個叫Authorization的頭,值為JWT的token;
          • 后臺程序通過對Authorization頭中信息的解碼及數字簽名校驗來獲取其中的用戶信息,從而實現認證和授權。

          項目實戰

          1. 依賴引入

          <!--SpringSecurity依賴配置-->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-security</artifactId>
          </dependency>
          <!--JWT(Json Web Token)登錄支持-->
          <dependency>
              <groupId>io.jsonwebtoken</groupId>
              <artifactId>jjwt</artifactId>
              <version>0.9.0</version>
          </dependency>

          2. JWT工具類

          • generateToken(UserDetails userDetails) :用于根據登錄用戶信息生成token
          • getUserNameFromToken(String token):從token中獲取登錄用戶的信息
          • validateToken(String token, UserDetails userDetails):判斷token是否還有效
              /**
               * 根據負責生成JWT的token
               */
              private String generateToken(Map<String, Object> claims) {
                  return Jwts.builder()
                          .setClaims(claims)
                          .setExpiration(generateExpirationDate())
                          .signWith(SignatureAlgorithm.HS512, secret)
                          .compact();
              }
              /**
               * 從token中獲取登錄用戶名
               */
              public String getUserNameFromToken(String token) {
                  String username;
                  try {
                      Claims claims=getClaimsFromToken(token);
                      username=claims.getSubject();
                  } catch (Exception e) {
                      username=null;
                  }
                  return username;
              }
          
              /**
               * 驗證token是否還有效
               *
               * @param token       客戶端傳入的token
               * @param userDetails 從數據庫中查詢出來的用戶信息
               */
              public boolean validateToken(String token, UserDetails userDetails) {
                  String username=getUserNameFromToken(token);
                  return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
              }

          3. SpringSecurity配置

          @Override
              protected void configure(HttpSecurity httpSecurity) throws Exception {
                  httpSecurity.csrf()// 由于使用的是JWT,我們這里不需要csrf
                          .disable()
                          .sessionManagement()// 基于token,所以不需要session
                          .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                          .and()
                          .authorizeRequests()
                          .antMatchers(HttpMethod.GET, // 允許對于網站靜態資源的無授權訪問
                                  "/", "/*.html", "/favicon.ico","/**/*.html",
                                  "/**/*.css","/**/*.js","/swagger-resources/**", "/v2/api-docs/**"
                          )
                          .permitAll()
                          .antMatchers("/admin/login", "/admin/register")// 對登錄注冊要允許匿名訪問
                          .permitAll()
                          .antMatchers(HttpMethod.OPTIONS)//跨域請求會先進行一次options請求
                          .permitAll()
                         // .antMatchers("/**")//測試時全部運行訪問
                        // .permitAll()
                          .anyRequest()// 除上面外的所有請求全部需要鑒權認證
                          .authenticated();
                  // 禁用緩存
                  httpSecurity.headers().cacheControl();
                  // 添加JWT filter
                  httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
                  
                //添加自定義未授權和未登錄結果返回
                  httpSecurity.exceptionHandling()
                          .accessDeniedHandler(restfulAccessDeniedHandler)
                          .authenticationEntryPoint(restAuthenticationEntryPoint);
              }

          4.JwtAuthenticationTokenFilter

          在用戶名和密碼校驗前添加的過濾器,如果請求中有jwt的token且有效,會取出token中的用戶名,然后調用SpringSecurity的API進行登錄操作。

          2021祝大家牛氣沖天,事業家庭順順順


          @Override
              protected void doFilterInternal(HttpServletRequest request,  HttpServletResponse response,
                                              FilterChain chain) throws ServletException, IOException {
                  String authHeader=request.getHeader(this.tokenHeader);
                  if (authHeader !=null && authHeader.startsWith(this.tokenHead)) {
                        // The part after "Bearer "
                      String authToken=authHeader.substring(this.tokenHead.length());
                      String username=jwtTokenUtil.getUserNameFromToken(authToken);
                      LOGGER.info("checking username:{}", username);
                    
                      if (username !=null && SecurityContextHolder.getContext().getAuthentication()==null) {
                          UserDetails userDetails=this.userDetailsService.loadUserByUsername(username);
                          if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                              UsernamePasswordAuthenticationToken authentication=new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                              authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                              LOGGER.info("authenticated user:{}", username);
                              SecurityContextHolder.getContext().setAuthentication(authentication);
                          }
                      }
                  }
                  chain.doFilter(request, response);
              }

          5. 備注說明

          • configure(HttpSecurity httpSecurity):用于配置需要攔截的url路徑、jwt過濾器及出異常后的處理器;
          • configure(AuthenticationManagerBuilder auth):用于配置UserDetailsService及PasswordEncoder;
          • RestfulAccessDeniedHandler:當用戶沒有訪問權限時的處理器,用于返回JSON格式的處理結果;
          • RestAuthenticationEntryPoint:當未登錄或token失效時,返回JSON格式的結果;
          • UserDetailsService:SpringSecurity定義的核心接口,用于根據用戶名獲取用戶信息,需要自行實現;
          • UserDetails:SpringSecurity定義用于封裝用戶信息的類(主要是用戶信息和權限),需要自行實現;
          • PasswordEncoder:SpringSecurity定義的用于對密碼進行編碼及比對的接口,目前使用的是BCryptPasswordEncoder;
          • JwtAuthenticationTokenFilter:在用戶名和密碼校驗前添加的過濾器,如果有jwt的token,會自行根據token信息進行登錄。

          端部分(使用Vue.js框架和axios庫):

          htmlCopy code<!-- 登錄表單 -->
          <template>
            <form>
              <input v-model="username" type="text" placeholder="用戶名">
              <input v-model="password" type="password" placeholder="密碼">
              <button @click.prevent="login">登錄</button>
            </form>
          </template>
          
          <script>
            import axios from 'axios'
          
            export default {
              data() {
                return {
                  username: '',
                  password: ''
                }
              },
              methods: {
                login() {
                  axios.post('/api/login', {
                    username: this.username,
                    password: this.password
                  })
                  .then(response=> {
                    // 登錄成功,保存Token到本地存儲
                    localStorage.setItem('token', response.data.token)
                    // 跳轉到用戶信息頁面
                    this.$router.push('/user')
                  })
                  .catch(error=> {
                    // 登錄失敗,提示錯誤信息
                    alert(error.response.data.message)
                  })
                }
              }
            }
          </script>
          

          后端部分(使用Node.js和Express框架):

          javascriptCopy codeconst express=require('express')
          const jwt=require('jsonwebtoken')
          const bcrypt=require('bcrypt')
          const app=express()
          
          // 用戶信息,實際中應該從數據庫中查詢
          const users=[
            { id: 1, username: 'admin', password: '$2b$10$PUg2e7Qf.nEIkLIl7hww1erx5D5VFXP5K6EqZ0BlzKU6N8U6YJcDu' } // 密碼為123456
          ]
          
          // 生成JWT Token
          function generateToken(user) {
            return jwt.sign({ id: user.id, username: user.username }, 'secret', { expiresIn: '1h' })
          }
          
          // 校驗密碼
          function verifyPassword(password, hash) {
            return bcrypt.compareSync(password, hash)
          }
          
          // 用戶登錄
          app.post('/api/login', (req, res)=> {
            const { username, password }=req.body
            const user=users.find(u=> u.username===username)
            if (!user) {
              res.status(401).json({ message: '用戶名不存在' })
              return
            }
            if (!verifyPassword(password, user.password)) {
              res.status(401).json({ message: '密碼錯誤' })
              return
            }
            const token=generateToken(user)
            res.json({ token })
          })
          
          // 獲取用戶信息
          app.get('/api/user', (req, res)=> {
            const token=req.headers.authorization?.split(' ')[1]
            try {
              const decoded=jwt.verify(token, 'secret')
              res.json({ id: decoded.id, username: decoded.username })
            } catch (err) {
              res.status(401).json({ message: '認證失敗' })
            }
          })
          
          app.listen(3000, ()=> console.log('Server started'))
          

          以上示例中,前端使用axios庫發送POST請求到后端的/api/login接口進行登錄驗證,后端根據請求中的用戶名和密碼進行校驗,如果驗證成功則生成JWT Token并返回給前端,前端保存Token到本地存儲,并使用路由跳轉到用戶信息頁面。后續的請求中,前端在請求頭中帶上Token,后端在后續的請求中,前端在請求頭中帶上Token,后端在/api/user接口中解析Token并返回用戶信息。

          在示例中,我們使用了JWT(JSON Web Token)作為登錄憑證,它具有以下優點:

          • 無狀態:JWT是基于Token的認證方式,因此不需要在服務端存儲會話信息,每個請求都包含認證信息,服務端只需要驗證Token的有效性即可。
          • 可擴展性:JWT中可以包含任意的JSON數據,因此可以將用戶的一些基本信息(如ID、用戶名等)放入Token中,便于在服務端解析使用。
          • 安全性:JWT中包含了簽名信息,服務端可以根據簽名驗證Token是否被篡改過。

          在實際應用中,為了提高安全性,我們還可以采用以下一些措施:

          • 使用HTTPS協議傳輸數據,防止數據被竊聽和篡改。
          • 對密碼進行加密處理,可以使用bcrypt等加密算法。
          • 設置Token的有效期,防止Token長期有效帶來的安全風險。
          • 使用黑名單機制,記錄已失效的Token,在認證時檢查Token是否在黑名單中。
          • 針對敏感操作,如修改密碼等,可以要求用戶輸入原密碼進行驗證。

          另外,需要注意以下幾點:

          1. 不要將用戶密碼明文存儲在數據庫中,應該采用加密方式存儲,如使用bcrypt等加密算法,確保用戶密碼的安全性。
          2. 在登錄驗證成功后,返回的Token應該包含一些必要的用戶信息,如用戶ID、用戶名等,方便后續在服務端中使用。
          3. 對于需要進行認證的接口,需要在服務端對Token進行驗證,并且限制只有已登錄的用戶才能訪問。同時,需要限制Token的有效期,防止Token長時間有效帶來的安全風險。
          4. 在用戶登錄成功后,需要將Token存儲在客戶端,可以采用localStorage等方式存儲。在每次發送請求時,需要將Token放入請求頭中,并在服務端進行驗證。
          5. 在服務端對Token進行驗證時,需要注意Token是否過期,是否被篡改等情況,可以使用JWT庫等工具進行驗證。

          總之,用戶登錄驗證是應用中非常重要的一部分,需要在設計時充分考慮安全性和可擴展性等因素,并采取相應的安全措施。用戶登錄驗證是一個比較復雜的問題,需要綜合考慮安全性和可擴展性等因素,同時采取相應的安全措施。

          文目錄:

          一:SSO體系結構

          SSO

          體系結構

          Token(令牌)

          同域SSO原理分析

          跨域SSO原理分析

          二:Cookie增刪改查

          如何讀取Cookie?

          如何寫入Cookie帶瀏覽器?

          修改Cookie

          刪除Cookie

          三:跨域讀寫Cookie

          1.利用HTML的script標簽跨域寫Cookie

          P3P協議

          2.通過URL參數實現跨域信息傳遞

          3.讀取其它域的Cookie

          四:跨域Ajax請求

          1.Jsonp的方式

          2.CORS簡介

          CORS流程

          3.兩種跨域AJax請求對比


          一:SSO體系結構

          SSO

          SSO英文全稱Single Sign On,單點登錄。SSO是在多個應用系統中,用戶只需要登錄一次就可以訪問所有相互信任的應用系統。它包括可以將這次主要的登錄映射到其他應用中用于同一個用戶的登錄的機制。它是目前比較流行的企業業務整合的解決方案之一。

          體系結構

          當用戶第一次訪問應用系統1的時候,因為還沒有登錄,會被引導到認證系統中進行登錄;根據用戶提供的登錄信息,認證系統進行身份校驗,如果通過校驗,應該返回給用戶一個認證的憑據--token;用戶再訪問別的應用的時候就會將這個token帶上,作為自己認證的憑據,應用系統接受到請求之后會把token送到認證系統進行校驗,檢查token的合法性。如果通過校驗,用戶就可以在不用再次登錄的情況下訪問應用系統2和應用系統3了 。

          Token(令牌)

          token的意思是“令牌”,是服務端生成的一串字符串,作為客戶端進行請求的一個標識。

          當用戶第一次登錄后,服務器生成一個token并將此token返回給客戶端,客戶端收到token后把它存儲起來,可以放在cookie或者Local Storage(本地存儲)里。以后客戶端只需帶上這個token前來請求數據即可,無需再次帶上用戶名和密碼。

          簡單token的組成;uid(用戶唯一的身份標識)、time(當前時間的時間戳)、sign(簽名,token的前幾位以哈希算法壓縮成的一定長度的十六進制字符串。為防止token泄露)。

          設計token的值可以有以下方式

          1. 用設備mac地址作為token
          2. 用sessionid作為token


          同域SSO原理分析

          實際上,HTTP協議是無狀態的,單個系統的會話由服務端Session進行維持,Session保持會話的原理是通過Cookie把sessionId寫入瀏覽器,每次訪問都會自動攜帶全部Cookie,在服務端讀取其中的sessionId進行驗證實現會話保持。同域下單點登錄其實就是手寫token代替sessionId進行會話認證。

          token的生成

          服務端生成token后,將token與user對象存儲在Map結構中,token為Key,user對象為value,response.addCookie()生成新的Cookie,名為token,值為token的值。

          token過期移除

          將服務端的token從Map中移除,再刪除瀏覽器端的名為token的Cookie。

          認證流程

          跨域SSO原理分析

          當有多個系統時,認證機制的流程如下:

          1. 提供用戶登錄界面,供用戶進行身份認證
          2. 用戶驗證通過后,生成新token
          3. 將token<->user 對存入全局MAP中供校驗
          4. 將token寫入所有域的Cookie中
          5. 頁面重定向回原始請求URL

          分析

          當系統有多個并且在不同域(domain)時,Cookie只會作用在當前域下。

          將token寫入所有域的Cookie中才是解決跨域SSO的核心。

          二:Cookie增刪改查

          如何讀取Cookie?

          通過Servlet中的request對象可以讀取到Cookie數組,然后foreach遍歷讀取,一般只是獲取到nam和value,其他信息寫入到瀏覽器后,瀏覽器不主動再發回來,讀取并無意義。

           Cookie[] cookies=request.getCookies();
           if (cookies !=null) {
           for (Cookie cookie : cookies) {
           System.out.println(
           cookie.getName() +
           cookie.getValue() +
           cookie.getMaxAge() +
           cookie.getPath() +
           cookie.getDomain() +
           cookie.getSecure() +
           cookie.isHttpOnly()//客戶端js是否可以獲取
           );
           }
           }
          

          如何寫入Cookie帶瀏覽器?

          新建Cookie對象設置一系列屬性,然后添加到response中去。需要注意的是,當設置path為“/”時,表示所有路徑都會被該Cookie作用到,如果設置為/path1那么由/path2發起請求就不會攜帶該Cookie。默認不設置只作用在當前路徑下。

           Cookie cookie=new Cookie("myCookieName","myCookieValue");
           cookie.setHttpOnly(false);//Javascript不能處理
           //一個正值表示cookie將在經過許多秒之后過期。注意,值是cookie過期的最大時間,而不是cookie當前的時間。
           //負值表示cookie沒有持久存儲,在Web瀏覽器退出時將被刪除。零值會導致刪除cookie。
           cookie.setMaxAge(-1000);
           cookie.setSecure(false);//如果為true,僅支持HTTPS協議
           //cookie對指定目錄中的所有頁面以及該目錄子目錄中的所有頁面都可見。
           cookie.setPath("/");
           //cookie.setDomain("www.a.com");//默認情況下,cookie只返回給發送cookie的服務器。
           response.addCookie(cookie);
          

          修改Cookie

          修改更新Cookie時,除了要保證Cookie的name是相同的,也要保證Cookie的一系列屬性是相同的,否則瀏覽器會生成新的Cookie。

          刪除Cookie

          只需要設置Cookie的MaxAge為負值,意味著是過去的Cookie,瀏覽器就會清除。

          三:跨域讀寫Cookie

          1.利用HTML的script標簽跨域寫Cookie

          比如當前域是www.a.com,下面的script標簽是跨域寫cookie的核心,通過此標簽實現了向www.b.com域寫入cookie:

          <script type="text/javascript" src="http://www.b.com/setCookie?cname=token&cval=123456"></script>
          


          P3P協議

          P3P是一種被稱為個人隱私安全平臺項目(the Platform for Privacy Preferences)的標準,能夠保護在線隱私權,使Internet沖浪者可以選擇在瀏覽網頁時,是否被第三方收集并利用自己的個人信息。如果一個站點不遵守P3P標準的話,那么有關它的Cookies將被自動拒絕,并且P3P還能夠自動識破多種Cookies的嵌入方式。p3p是由全球資訊聯盟網所開發的。

          舉個例子:

          我們在訪問A網站時,理論上說,我們只能把Cookie信息保存到A站域名下,而不能寫入到B網站下。如果想要跨域讀寫Cookie,只是通過script標簽變相訪問B網站在一些瀏覽器是行不通的,此時B網站的服務器應該告訴瀏覽器允許A網站寫入Cookie,否則瀏覽器將會拒絕執行,這就是P3P協議。

          服務端如何告訴瀏覽器?

          P3P提供了一種簡單的方式 ,來加載用戶隱私策略,只要在http響應的頭信息中增加response.setHeader("P3P","CP=NON DSP COR CURa ADMa DEVa TAIa PSAa PSDa IVAa IVDa CONa HISa TELa OTPa OUR UNRa IND UNI COM NAV INT DEM CNT PRE LOC);而無需指定隱私策略文件也可以達到指定隱私策略的目的。CP=后面的字符串分別代表不同的策略信息。

          總結

          因為P3P協議所以不能保證所有瀏覽器都能通過script標簽方式跨域寫Cookie,有的瀏覽器本身就是拒絕跨域的。

          顯然這種方式是不能保證跨域寫cookie的成功性。

          2.通過URL參數實現跨域信息傳遞

          我們要在A域實現寫入token到B域,需要在A域設計一個servlet接收請求,代碼:

          @WebServlet(name="tg")
          public class Servlet extends HttpServlet {
           protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
           //獲取請求的目標域
           String from=request.getParameter("from");
           //生成token,
           String token="123456";
           //重定向到目標域
           response.sendRedirect(from + "?cname=token&cval=" + token);
           }
           ...
           }
          

          由a域發起請求,請求地址:http://www.a.com/tg?from=http://www.b.com/set_cookie, 請求后該Servlet會獲取from參數的值并生成token最后讓客戶端重定向到http://www.b.com/set_cookie?cname=token&cval=123456,然后B域的Servlet("set_cookie")獲取Url參數寫入Cookie到客戶端,代碼:

           //將要寫入的cookie項,調用者通過參數傳遞
           String cookieName=request.getParameter("cname");
           String cookieValue=request.getParameter("cval");
           //生成cookie
           Cookie cookie=new Cookie(cookieName,cookieValue);
           cookie.setPath("/");
           //一般可以將domain設置到頂級域
           //cookie.setDomain("www.b.com");
           response.addCookie(cookie);
          

          這時候再查看B域下的Cookie就可以發現(token=123456)已經被寫入到瀏覽器。

          3.讀取其它域的Cookie

          利用script標簽

          利用script標簽執行另一個域實現的讀取cookie方法,script標簽返回結果將是變量定義形式的JS代碼,每一個變量表示一個cookie項,這些代碼加載后,此頁面后續JS代碼可以直接在script腳本中讀取已定義的變量值,即各cookie值。

          <script type="text/javascript" src="http://www.b.com/reaf_cookies"></script>
          


          HTML頁面讀取

          <script>
          alert(token);
          </script>
          

          B域的url為/read_cookies的Servlet是如何實現的?

          如圖,首先我們先在request中獲取cookie數組,然后for循環遍歷拼接為類似var token='test123';的字符串。最重要的是設置ContentType為application/javascript,代碼如下:

           protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
           Cookie[] cookies=request.getCookies();
           StringBuilder stringBuilder=new StringBuilder();
           //一定要設置響應類型,否則可能導致IE不解析js直接進行下載操作
           response.setContentType("application/javascript");
           if (cookies !=null) {
           for (Cookie cookie : cookies) {
           //結果類似于這樣 var token='123456';
           stringBuilder.append("var ")
           .append(cookie.getName())
           .append("=")
           .append("'")
           .append(cookie.getValue())
           .append("'")
           .append(";");
           }
           response.getWriter().append(stringBuilder.toString());
           }
           }
          

          四:跨域Ajax請求

          1.Jsonp的方式

          跨域Ajax請求在瀏覽器階段就會被阻止,我們可以通過script標簽返回想要的json數據。如圖:

           <script type="text/javascript" src="http://www.b.com/user_info_2"></script>
          


          后臺Servlet代碼

           //要正確設置響應類型,避免IE出現下載
           response.setContentType("application/javascript");
           String userInfo="{\"id\":1,\"name\":\"zhangsan\"}";
           //返回拼接的javascript語句字符串,語句本身執行一個調用函數的操作
           String ret="showResult("+userInfo+")";
          

          在Servlet中設置返回類型為javascript,并正常獲取json格式的數據,最關鍵的是在最后拼接為js語句字符串,語句本身就是執行一個調用函數的操作:

          showResult({"id":1,"name":"zhangsan"})
          


          而showResult(ret)回調函數自然需要我們在之前就定義好:

           <script>
           function showResult(ret){
           console.log(ret)
           }
           </script>
          

          優化

          這種方式,前端的回調函數和后端耦合度較高。前端可以在調用后端方法時帶上回調函數名(?callback=xxxxx),后端優化后的代碼:

           //通過參數傳遞回調函數名,一定程度降低了前后端代碼的耦合度
           String callback=request.getParameter("callback");
           //返回拼接的javascript語句字符串,語句本身執行一個調用函數的操作
           String ret=callback+"("+userInfo+")";
          

          再優化

          HTML頁面加載到我們定義的script標簽時就會執行我們的回調方法,更多時候我們想要控制回調方法的執行時機。這個問題可以通過前端動態生成節點來解決,當我們執行完之后再移除節點即可:

           <script>
           var script=document.createElement("script");
           script.src="http://www.b.com/user_info_2?callback=showResult";
           document.body.appendChild(script);
           script.onload=function () {
           document.body.removeChild(script);
           }
           </script>
          

          JQuery

          我們可以把這些封裝到一個方法里,隨時調用。這里可以使用Jquery封裝好的API。

           $.ajax({
           url: "http://localhost:9090/query",
           type: "GET",
           dataType: "jsonp", //指定服務器返回的數據類型
           jsonpCallback: "showData", //指定回調函數名稱
           success: function (data) {
           console.info("調用success");
           }
           });
           function showData(data){
           var result=JSON.stringify(data);
           }
          

          2.CORS簡介

          出于安全原因,瀏覽器限制從腳本內發起的跨源HTTP請求。例如,XMLHttpRequest和Fetch API遵循同源策略。這意味著使用這些API的Web應用程序只能從加載應用程序的同一個域請求HTTP資源,除非使用CORS頭文件。

          跨域資源共享( CORS )機制允許 Web 應用服務器進行跨域訪問控制,從而使跨域數據傳輸得以安全進行。瀏覽器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以降低跨域 HTTP 請求所帶來的風險。

          GET跨域請求原理

          當客戶端瀏覽器發起一個跨域的HTTP請求,瀏覽器經過請求響應,如果沒有看到Access-Control-Allow-Origin的header頭部,會認為你的請求是不合法的。換句話說,我們只要在被請求的服務器上設置這個頭部,瀏覽器就會允許我們進行請求。

          解決方法

          對于簡單的請求,我們直接在服務端 設置就可以了。如圖,只要請求的地址是www.a.com就會被瀏覽器允許跨域。如果想要允許對于多個來源可以用,號進行隔開;如果想要允許所有來源,設置為*就可以,不過建議不要使用,這樣會造成安全隱患。

           protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
           //簡單請求,直接設置Access-Control-Allow-Origin就可以了
           response.setHeader("Access-Control-Allow-Origin","*");
           //要正確設置響應類型,避免IE出現下載
           response.setContentType("application/json");
           response.getWriter().write("{\"id\":1,\"name\":\"zhangsan\"}");
           }
          

          對于復雜的請求,比如POST,或者加入了自定義header頭部,上面的方法就不適用了。下面繼續看。


          CORS流程

          請求發起時,瀏覽器先判斷當前是否是跨域的AJAX;

          如果是,判斷是否是普通類型請求(GET類型,無自定義頭數據);

          普通請求,直接發起GET到服務端,在響應頭中尋找 Access-Contro-Alow- Origin,如果有且允許,處理響應結果;

          不是普通請求(非GET類型,或有自定義頭), 先 PreFlight(即發起一個 method=OPTIONS)的請求,

          要求返回 Access-Control-Allow- Methods和 Access-Control-Allow- Headers, 內容體為空

          PreFlight正確執行后, 再發起GET請求, 獲得響應結果, 并處理結果.

          實現

          歸根到我們的代碼中的實現,只需要在servlet中定義options請求的處理方法即可。如圖

           protected void doOptions(HttpServletRequest req, HttpServletResponse response) {
           response.setHeader("Access-Control-Allow-Origin","*");
           response.setHeader("Access-Control-Allow-Methods","GET,POST,OPTIONS,DELETE");
           response.setHeader("Access-Control-Allow-Headers","reqid,xxx");
           }
          

          注意:Access-Control-Allow-Origin是必需的。

          3.兩種跨域AJax請求對比

          兼容性

          Jsonp對所有瀏覽器兼容,CORS對現代瀏覽器兼容(IE8之后)。

          請求方式

          Jsonp只支持GET方式,CORS支持GET,POST等。

          調用方式

          Jsonp需要服務端封裝返回信息,CORS更像原生AJax一樣使用。

          看完的朋友記得要點贊轉發噢!!!


          主站蜘蛛池模板: 天堂va在线高清一区| 国产成人综合精品一区| 精品国产精品久久一区免费式 | 国模精品一区二区三区视频| 国产精品男男视频一区二区三区| 色狠狠一区二区三区香蕉| 一区二区三区四区电影视频在线观看| 色偷偷一区二区无码视频| 日本v片免费一区二区三区 | 清纯唯美经典一区二区| 国产一区二区在线观看麻豆 | 亚洲午夜日韩高清一区| 天美传媒一区二区三区| 麻豆国产一区二区在线观看| 久久久久人妻精品一区蜜桃 | 无码国产精品一区二区免费式影视| 国产AV国片精品一区二区| 午夜爽爽性刺激一区二区视频| 91秒拍国产福利一区| 亚洲AV无码一区二区二三区软件| www一区二区www免费| 色精品一区二区三区| 综合激情区视频一区视频二区| 国产乱码精品一区二区三区| 亚洲色偷精品一区二区三区| 国产成人精品第一区二区| 麻豆一区二区在我观看| 少妇无码一区二区三区| 色一乱一伦一图一区二区精品| 伊人久久大香线蕉av一区| 日本免费一区二区久久人人澡| 国产av成人一区二区三区| 熟妇人妻一区二区三区四区| 三上悠亚国产精品一区| 亚洲永久无码3D动漫一区| 精品一区二区三区免费| 亚洲午夜精品一区二区| 91视频国产一区| 国产精品一区二区AV麻豆| 福利一区二区视频| 国产一区二区三区不卡AV|