1. 程式人生 > >樂優商城(三十)——授權中心

樂優商城(三十)——授權中心

目錄

一、無狀態登入原理

1.1 什麼是有狀態

1.2 什麼是無狀態

1.3 如何實現無狀態

1.4 JWT

1.4.1 簡介

1.4.2 資料格式

1.4.3 JWT互動流程

1.4.4 非對稱加密

1.5 結合Zuul的鑑權流程

1.5.1 沒有RSA加密時

1.5.2 結合RSA的鑑權

二、授權中心

2.1 建立授權中心

2.1.1 建立父module

2.1.2 通用module

2.1.3 授權服務

2.2 JWT工具類

2.3 測試工具類

2.4 編寫登入授權介面

2.4.1 生成公鑰和私鑰

2.4.2 Controller

2.4.3 CookieUtils

2.4.4 UserClient

2.4.5 Service

2.4.6 專案結構

2.4.7 測試

2.5 登入頁面

2.6 解決cookie寫入問題

2.6.1 問題分析

2.6.2 跟蹤CookieUtils

2.6.3 解決host地址的變化

2.6.4 再次測試

2.6.5 Zuul的敏感頭過濾

2.6.6 最後的測試


一、無狀態登入原理

1.1 什麼是有狀態

有狀態服務,即服務端需要記錄每次會話的客戶端資訊,從而識別客戶端身份,根據使用者身份進行請求的處理,典型的設計如tomcat中的session。

例如登入:使用者登入後,我們把登入者的資訊儲存在服務端session中,並且給使用者一個cookie值,記錄對應的session。然後下次請求,使用者攜帶cookie值來,就能識別到對應session,從而找到使用者的資訊。

缺點是什麼?

  • 服務端儲存大量資料,增加服務端壓力

  • 服務端儲存使用者狀態,無法進行水平擴充套件

  • 客戶端請求依賴服務端,多次請求必須訪問同一臺伺服器

1.2 什麼是無狀態

微服務叢集中的每個服務,對外提供的都是Rest風格的介面。而Rest風格的一個最重要的規範就是:服務的無狀態性,即:

  • 服務端不儲存任何客戶端請求者資訊

  • 客戶端的每次請求必須具備自描述資訊,通過這些資訊識別客戶端身份

帶來的好處是什麼呢?

  • 客戶端請求不依賴服務端的資訊,任何多次請求不需要必須訪問到同一臺服務

  • 服務端的叢集和狀態對客戶端透明

  • 服務端可以任意的遷移和伸縮

  • 減小服務端儲存壓力

1.3 如何實現無狀態

無狀態登入的流程:

  • 當客戶端第一次請求服務時,服務端對使用者進行資訊認證(登入)

  • 認證通過,將使用者資訊進行加密形成token,返回給客戶端,作為登入憑證

  • 以後每次請求,客戶端都攜帶認證的token

  • 服務端對token進行解密,判斷是否有效。

整個登入過程中,最關鍵的點是什麼?

token的安全性

token是識別客戶端身份的唯一標示,如果加密不夠嚴密,被人偽造那就完蛋了。

採用何種方式加密才是安全可靠的呢?

採用JWT + RSA非對稱加密

1.4 JWT

1.4.1 簡介

JWT,全稱是Json Web Token, 是JSON風格輕量級的授權和身份認證規範,可實現無狀態、分散式的Web應用授權;官網:https://jwt.io

GitHub上jwt的java客戶端:https://github.com/jwtk/jjwt

1.4.2 資料格式

JWT包含三部分資料:

  • Header:頭部,通常頭部有兩部分資訊:

    對頭部進行base64加密(可解密),得到第一部分資料

    • 宣告型別,這裡是JWT

    • 加密演算法,自定義

  • Payload:載荷,就是有效資料,一般包含下面資訊:

    這部分也會採用base64加密,得到第二部分資料

    • 使用者身份資訊(注意,這裡因為採用base64加密,可解密,因此不要存放敏感資訊)

    • 註冊宣告:如token的簽發時間,過期時間,簽發人等

  • Signature:簽名,是整個資料的認證資訊。一般根據前兩步的資料,再加上服務的的金鑰(secret)(不要洩漏,最好週期性更換),通過加密演算法生成。用於驗證整個資料完整和可靠性

生成的資料格式:

1.4.3 JWT互動流程

流程圖:

步驟翻譯:

  • 1、使用者登入

  • 2、服務的認證,通過後根據secret生成token

  • 3、將生成的token返回給瀏覽器

  • 4、使用者每次請求攜帶token

  • 5、服務端利用公鑰解讀jwt簽名,判斷簽名有效後,從Payload中獲取使用者資訊

  • 6、處理請求,返回響應結果

因為JWT簽發的token中已經包含了使用者的身份資訊,並且每次請求都會攜帶,這樣服務的就無需儲存使用者資訊,甚至無需去資料庫查詢,完全符合了Rest的無狀態規範。

1.4.4 非對稱加密

加密技術是對資訊進行編碼和解碼的技術,編碼是把原來可讀資訊(又稱明文)譯成程式碼形式(又稱密文),其逆過程就是解碼(解密),加密技術的要點是加密演算法,加密演算法可以分為三類:

  • 對稱加密,如AES

    • 基本原理:將明文分成N個組,然後使用金鑰對各個組進行加密,形成各自的密文,最後把所有的分組密文進行合併,形成最終的密文。

    • 優勢:演算法公開、計算量小、加密速度快、加密效率高

    • 缺陷:雙方都使用同樣金鑰,安全性得不到保證

  • 非對稱加密,如RSA

    • 基本原理:同時生成兩把金鑰:私鑰和公鑰,私鑰隱祕儲存,公鑰可以下發給信任客戶端

      • 私鑰加密,持有私鑰或公鑰才可以解密

      • 公鑰加密,持有私鑰才可解密

    • 優點:安全,難以破解

    • 缺點:演算法比較耗時

  • 不可逆加密,如MD5,SHA

    • 基本原理:加密過程中不需要使用金鑰,輸入明文後由系統直接經過加密演算法處理成密文,這種加密後的資料是無法被解密的,無法根據密文推算出明文。

1.5 結合Zuul的鑑權流程

需要注意的是:secret是簽名的關鍵,因此一定要保密,所以放到鑑權中心儲存,其它任何服務中都不能獲取secret。

1.5.1 沒有RSA加密時

在微服務架構中,可以把服務的鑑權操作放到閘道器中,將未通過鑑權的請求直接攔截,如圖:

  • 1、使用者請求登入

  • 2、Zuul將請求轉發到授權中心,請求授權

  • 3、授權中心校驗完成,頒發JWT憑證

  • 4、客戶端請求其它功能,攜帶JWT

  • 5、Zuul將jwt交給授權中心校驗,通過後放行

  • 6、使用者請求到達微服務

  • 7、微服務將jwt交給鑑權中心,鑑權同時解析使用者資訊

  • 8、鑑權中心返回使用者資料給微服務

  • 9、微服務處理請求,返回響應

缺點:

每次鑑權都需要訪問鑑權中心,系統間的網路請求頻率過高,效率略差,鑑權中心的壓力較大。

1.5.2 結合RSA的鑑權

  • 首先利用RSA生成公鑰和私鑰。私鑰儲存在授權中心,公鑰儲存在Zuul和各個微服務

  • 使用者請求登入

  • 授權中心校驗,通過後用私鑰對JWT進行簽名加密

  • 返回jwt給使用者

  • 使用者攜帶JWT訪問

  • Zuul直接通過公鑰解密JWT,進行驗證,驗證通過則放行

  • 請求到達微服務,微服務直接用公鑰解析JWT,獲取使用者資訊,無需訪問授權中心

二、授權中心

授權中心的主要職責:

  • 使用者鑑權:

    • 接收使用者的登入請求,通過使用者中心的介面進行校驗,通過後生成JWT

    • 使用私鑰生成JWT並返回

  • 服務鑑權:微服務間的呼叫不經過Zuul,會有風險,需要鑑權中心進行認證

    • 原理與使用者鑑權類似,但邏輯稍微複雜一些(此處不做實現)

因為生成jwt,解析jwt這樣的行為以後在其它微服務中也會用到,因此抽取成工具。把鑑權中心進行聚合,一個工具module,一個提供服務的module。

2.1 建立授權中心

2.1.1 建立父module

將打包方式改為pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>leyou</artifactId>
        <groupId>com.leyou.parent</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.authentication</groupId>
    <artifactId>leyou-authentication</artifactId>
    <packaging>pom</packaging>


</project>

2.1.2 通用module

2.1.3 授權服務

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>leyou-authentication</artifactId>
        <groupId>com.leyou.authentication</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.authentication</groupId>
    <artifactId>leyou-authentication-service</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.leyou.authentication</groupId>
            <artifactId>leyou-authentication-common</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

</project>

啟動器

package com.leyou.auth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @Author: 98050
 * @Time: 2018-10-23 20:11
 * @Feature: 授權服務啟動器
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class LyAuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(LyAuthApplication.class,args);
    }
}

application.yml

server:
  port: 8087
spring:
  application:
    name: auth-service
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
    registry-fetch-interval-seconds: 10
  instance:
    instance-id: ${spring.application.name}:${server.port}
    prefer-ip-address: true  #當你獲取host時,返回的不是主機名,而是ip
    ip-address: 127.0.0.1
    lease-expiration-duration-in-seconds: 10 #10秒不傳送九過期
    lease-renewal-interval-in-seconds: 5 #每隔5秒發一次心跳

結構:

在leyou-gateway工程的application.yml中,修改路由:

2.2 JWT工具類

在leyou-authentication-common中匯入工具類:

需要在leyou-auth-common中引入JWT依賴:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>leyou-authentication</artifactId>
        <groupId>com.leyou.authentication</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.authentication</groupId>
    <artifactId>leyou-authentication-common</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>

        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

</project>

2.3 測試工具類

在leyou-authentication-common中編寫測試類:

public class JwtTest {

    private static final String pubKeyPath = "C:\\tmp\\rsa\\rsa.pub";

    private static final String priKeyPath = "C:\\tmp\\rsa\\rsa.pri";

    private PublicKey publicKey;

    private PrivateKey privateKey;

    @Test
    public void testRsa() throws Exception {
        RsaUtils.generateKey(pubKeyPath, priKeyPath, "234");
    }

    @Before
    public void testGetRsa() throws Exception {
        this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
        this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
    }

    @Test
    public void testGenerateToken() throws Exception {
        // 生成token
        String token = JwtUtils.generateToken(new UserInfo(20L, "jack"), privateKey, 5);
        System.out.println("token = " + token);
    }

    @Test
    public void testParseToken() throws Exception {
        String token = "eyJhbGciOiJSUzI1NiJ9.eyJpZCI6MjAsInVzZXJuYW1lIjoiamFjayIsImV4cCI6MTUzMzI4MjQ3N30.EPo35Vyg1IwZAtXvAx2TCWuOPnRwPclRNAM4ody5CHk8RF55wdfKKJxjeGh4H3zgruRed9mEOQzWy79iF1nGAnvbkraGlD6iM-9zDW8M1G9if4MX579Mv1x57lFewzEo-zKnPdFJgGlAPtNWDPv4iKvbKOk1-U7NUtRmMsF1Wcg";

        // 解析token
        UserInfo user = JwtUtils.getInfoFromToken(token, publicKey);
        System.out.println("id: " + user.getId());
        System.out.println("userName: " + user.getUsername());
    }
}

測試生成公鑰和私鑰,執行程式碼(注意將testGetRsa方法註釋掉):

檢視目標目錄:

公鑰和私鑰已經生成了!

測試生成token,把@Before的註釋去掉:

測試解析token:

正常解析:

JWT工具類

2.4 編寫登入授權介面

接下來,需要在leyou-auth-servcice編寫一個介面,對外提供登入授權服務。基本流程如下:

  • 客戶端攜帶使用者名稱和密碼請求登入

  • 授權中心呼叫客戶中心介面,根據使用者名稱和密碼查詢使用者資訊

  • 如果使用者名稱密碼正確,能獲取使用者,否則為空,則登入失敗

  • 如果校驗成功,則生成JWT並返回

2.4.1 生成公鑰和私鑰

在授權中心生成真正的公鑰和私鑰。所以必須有一個生成公鑰和私鑰的secret,這個可以配置到application.yml中:

然後編寫屬性類,載入這些資料:

package com.leyou.auth.properties;

import com.leyou.auth.utils.RsaUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;

import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * @Author: 98050
 * @Time: 2018-10-23 22:20
 * @Feature: jwt配置引數
 */
@ConfigurationProperties(prefix = "leyou.jwt")
public class JwtProperties {

    /**
     * 金鑰
     */
    private String secret;

    /**
     * 公鑰地址
     */
    private String pubKeyPath;

    /**
     * 私鑰地址
     */
    private String priKeyPath;

    /**
     * token過期時間
     */
    private int expire;

    /**
     * 公鑰
     */
    private PublicKey publicKey;

    /**
     * 私鑰
     */
    private PrivateKey privateKey;

    private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);

    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public String getPubKeyPath() {
        return pubKeyPath;
    }

    public void setPubKeyPath(String pubKeyPath) {
        this.pubKeyPath = pubKeyPath;
    }

    public String getPriKeyPath() {
        return priKeyPath;
    }

    public void setPriKeyPath(String priKeyPath) {
        this.priKeyPath = priKeyPath;
    }

    public int getExpire() {
        return expire;
    }

    public void setExpire(int expire) {
        this.expire = expire;
    }

    public PublicKey getPublicKey() {
        return publicKey;
    }

    public void setPublicKey(PublicKey publicKey) {
        this.publicKey = publicKey;
    }

    public PrivateKey getPrivateKey() {
        return privateKey;
    }

    public void setPrivateKey(PrivateKey privateKey) {
        this.privateKey = privateKey;
    }

    /**
     * @PostConstruct :在構造方法執行之後執行該方法
     */
    @PostConstruct
    public void init(){
        try {
            File pubKey = new File(pubKeyPath);
            File priKey = new File(priKeyPath);
            if (!pubKey.exists() || !priKey.exists()) {
                // 生成公鑰和私鑰
                RsaUtils.generateKey(pubKeyPath, priKeyPath, secret);
            }
            // 獲取公鑰和私鑰
            this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
            this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
        } catch (Exception e) {
            logger.error("初始化公鑰和私鑰失敗!", e);
            throw new RuntimeException();
        }
    }
}

2.4.2 Controller

編寫授權介面,接收使用者名稱和密碼,校驗成功後,寫入cookie中。

  • 請求方式:post

  • 請求路徑:/accredit

  • 請求引數:username和password

  • 返回結果:無

程式碼:

注意配置屬性類:@EnableConfigurationProperties(JwtProperties.class)

package com.leyou.auth.controller;

import com.leyou.auth.properties.JwtProperties;
import com.leyou.auth.service.AuthService;
import com.leyou.utils.CookieUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Author: 98050
 * @Time: 2018-10-23 22:43
 * @Feature: 登入授權
 */
@Controller
@EnableConfigurationProperties(JwtProperties.class)
public class AuthController {

    @Autowired
    private AuthService authService;

    @Autowired
    private JwtProperties properties;

    @PostMapping("accredit")
    public ResponseEntity<Void> authentication(
            @RequestParam("username") String username,
            @RequestParam("password") String password,
            HttpServletRequest request,
            HttpServletResponse response
    ){
        //1.登入校驗
        String token = this.authService.authentication(username,password);
        if (StringUtils.isBlank(token)){
            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
        }
        //2.將token寫入cookie,並指定httpOnly為true,防止通過js獲取和修改
        CookieUtils.setCookie(request,response,properties.getCookieName(),token,properties.getCookieMaxAge(),true);

        return ResponseEntity.ok().build();
    }
}

2.4.3 CookieUtils

作用,將生成好的token放入到cookie中,返回到客戶端。

程式碼:略

2.4.4 UserClient

對使用者密碼進行校驗,所以需要通過FeignClient去訪問 user-service微服務:

引入user-service依賴:

<dependency>
    <groupId>com.leyou.user</groupId>
    <artifactId>leyou-user-interface</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

編寫FeignClient:

package com.leyou.auth.client;

import com.leyou.user.api.UserApi;
import org.springframework.cloud.openfeign.FeignClient;

/**
 * @Author: 98050
 * @Time: 2018-10-23 23:48
 * @Feature: 使用者feignclient
 */
@FeignClient(value = "user-service")
public interface UserClient extends UserApi {
}

在leyou-user-interface中新增api介面:

package com.leyou.user.api;

import com.leyou.user.pojo.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @Author: 98050
 * @Time: 2018-10-23 23:50
 * @Feature: 使用者服務介面
 */
public interface UserApi {
    /**
     * 使用者驗證
     * @param username
     * @param password
     * @return
     */
    @GetMapping("query")
    User queryUser(@RequestParam("username")String username, @RequestParam("password")String password);
}

2.4.5 Service

介面

package com.leyou.auth.service;

/**
 * @Author: 98050
 * @Time: 2018-10-23 22:46
 * @Feature:
 */
public interface AuthService {
    /**
     * 使用者授權
     * @param username
     * @param password
     * @return
     */
    String authentication(String username, String password);
}

實現

package com.leyou.auth.service.impl;

import com.leyou.auth.client.UserClient;
import com.leyou.auth.entity.UserInfo;
import com.leyou.auth.properties.JwtProperties;
import com.leyou.auth.service.AuthService;
import com.leyou.auth.utils.JwtUtils;
import com.leyou.user.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author: 98050
 * @Time: 2018-10-23 22:47
 * @Feature:
 */
@Service
public class AuthServiceImpl implements AuthService {

    @Autowired
    private UserClient userClient;

    @Autowired
    private JwtProperties properties;

    /**
     * 使用者授權
     * @param username
     * @param password
     * @return
     */
    @Override
    public String authentication(String username, String password) {

        try{
            //1.呼叫微服務查詢使用者資訊
            User user = this.userClient.queryUser(username,password);
            System.out.println(user);
            //2.查詢結果為空,則直接返回null
            if (user == null){
                return null;
            }
            //3.查詢結果不為空,則生成token
            String token = JwtUtils.generateToken(new UserInfo(user.getId(), user.getUsername()),
                    properties.getPrivateKey(), properties.getExpire());
            System.out.println(token);
            return token;

        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
}

2.4.6 專案結構

2.4.7 測試

token已經生成,返回到客戶端。

2.5 登入頁面

請求路徑不對,因為認證介面是:

/api/auth/accredit

修改login.html:

然後登入,可以跳轉到首頁。

2.6 解決cookie寫入問題

檢視首頁cookie:

2.6.1 問題分析

之前測試時,看到了響應頭中,有Set-Cookie屬性,為什麼在這裡卻什麼都沒有?

因為涉及到跨域問題。跨域請求cookie生效的條件:

  • 服務的響應頭中需要攜帶Access-Control-Allow-Credentials並且為true。

  • 響應頭中的Access-Control-Allow-Origin一定不能為*,必須是指定的域名

  • 瀏覽器發起ajax需要指定withCredentials 為true

服務端cors配置:

沒有問題。

客戶端ajax配置:common.js中

也沒有問題。

那說明,問題一定出在響應的set-cookie頭中。仔細檢視剛才的響應頭:

 

cookie的 domain屬性似乎不太對

cookie也是有 的限制,一個網頁,只能操作當前域名下的cookie,但是現在看到的地址是0.0.1(為什麼是0.0.1,因為前端的地址是www.leyou.com,通過nginx反向代理到127.0.0.1,通過計算得到domain為0.0.1),而頁面是www.leyou.com,域名不匹配,cookie設定肯定失敗了!

2.6.2 跟蹤CookieUtils

再次登入,然後進行Debug

CookieUtils內部有一個獲取domain的方法:

它獲取domain是通過伺服器的host來計算的,然而我們的地址竟然是:127.0.0.1:8087,因此後續的運算,最終得到的domain就變成了:

問題找到了:

請求時的serverName是:api.leyou.com,現在卻被變成了127.0.0.1:,因此計算domain是錯誤的,從而導致cookie設定失敗!

2.6.3 解決host地址的變化

那麼問題來了:為什麼這裡的請求serverName變成了:127.0.0.1:8087呢?

這裡的server name其實就是請求時的主機名:Host,之所以改變,有兩個原因:

  • 使用了nginx反向代理,當監聽到api.leyou.com的時候,會自動將請求轉發至127.0.0.1:10010,即Zuul。

  • 而後請求到達我們的閘道器Zuul,Zuul就會根據路徑匹配,我們的請求是/api/auth,根據規則被轉發到了 127.0.0.1:8087 ,即授權中心。

所以,先更改nginx配置,讓它不要修改host:proxy_set_header Host $host;

nginx重啟。

然後再去解決Zuul的問題,因為Zuul還會有一次轉發,所以要去修改閘道器的配置(leyou-gateway):

重啟專案,再次測試:

最後計算得到domain:

2.6.4 再次測試

還是沒有cookie!

2.6.5 Zuul的敏感頭過濾

Zuul內部有預設的過濾器,會對請求和響應頭資訊進行重組,過濾掉敏感的頭資訊:

會發現,這裡會通過一個屬性為SensitiveHeaders的屬性,來獲取敏感頭列表,然後新增到IgnoredHeaders中,這些頭資訊就會被忽略。

而這個SensitiveHeaders的預設值就包含了set-cookie

解決方案有兩種:

全域性設定:

  • zuul.sensitive-headers=

指定路由設定:

  • zuul.routes.<routeName>.sensitive-headers=

  • zuul.routes.<routeName>.custom-sensitive-headers=true

思路都是把敏感頭設定為null

2.6.6 最後的測試

再次重啟,登入: