久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

實戰(zhàn);單點登錄,,前后端分離 Spring OAuth2

 小傅哥 2025-01-22 發(fā)布于北京

作者:小傅哥
博客:https://

?

沉淀,、分享、成長,,讓自己和他人都能有所收獲,!??

?

大家好,我是技術(shù)UP主小傅哥,。

當(dāng)你進入一個較大一些的中大廠互聯(lián)網(wǎng)公司以后,,你會發(fā)現(xiàn)自己參與的業(yè)務(wù)系統(tǒng)開發(fā),好像從來沒有關(guān)心過關(guān)于用戶的身份鑒權(quán),,而是直接拿到用戶的ID就做業(yè)務(wù)了,。那這里的鑒權(quán)跑到哪里去了呢???

其實在公司里是一套統(tǒng)一的授權(quán)服務(wù)和組件的,,并且維護用戶的ID,、用戶的關(guān)聯(lián)綁定也都是這套系統(tǒng)來處理的。之后這套系統(tǒng)會和 API 網(wǎng)關(guān)進行對接,,等網(wǎng)關(guān)下發(fā)到你的后端服務(wù)系統(tǒng)時,,在內(nèi)部微服間流轉(zhuǎn)就是真實的用戶ID啦。

那么為了讓伙伴們更好的理解關(guān)于 OAuth2 SSO 統(tǒng)一單點登錄的前后端分離服務(wù),,小傅哥這里做了一個結(jié)合 Spring Security OAuth2很容易理解案例工程,。學(xué)習(xí)后就可以擴展使用 SSO 到你自己的系統(tǒng)了,比如可以做一個統(tǒng)一的用戶鑒權(quán)中心,。

一,、單點登錄

單點登錄(Single Sign-On,SSO)是一種認(rèn)證技術(shù),,用戶只需進行一次身份驗證,,就可以訪問多個相互信任的應(yīng)用系統(tǒng),,而無需再次輸入憑證。SSO的主要目的是簡化用戶的登錄過程,,提高用戶體驗和安全性,,同時減少管理多個用戶名和密碼的復(fù)雜性。

SSO的工作原理通常涉及以下幾個步驟:

  1. 身份驗證:用戶在第一次訪問SSO系統(tǒng)時輸入用戶名和密碼等憑據(jù)進行驗證,。
  2. 創(chuàng)建會話:成功驗證后,,系統(tǒng)創(chuàng)建一個會話,可以是令牌,、票證或其他憑據(jù),以證明用戶的身份,。
  3. 訪問授權(quán):當(dāng)用戶訪問不同的應(yīng)用時,,SSO系統(tǒng)將會話信息傳遞給這些應(yīng)用,以確認(rèn)用戶的身份并授予訪問權(quán)限,。
  4. 信任機制:應(yīng)用之間需要建立信任關(guān)系,,通常通過共享密鑰或使用公鑰基礎(chǔ)設(shè)施(PKI)來實現(xiàn)驗證和授權(quán)。

SSO的優(yōu)點包括:

  • 提高用戶體驗:用戶只需記住一個用戶名和密碼,,減少了填寫登錄信息的次數(shù),。
  • 增強安全性:集中管理用戶身份,方便監(jiān)控和保護密碼策略,。
  • 降低管理成本:減少IT部門處理密碼重置等事務(wù)的工作量,。

二、案例工程

1. 編程環(huán)境

  • JDK 1.8

  • SpringBoot 2.6.2

  • Maven 3.8.1

  • Docker - 負(fù)責(zé)安裝 Nginx,,如果沒有 Docker 就本地直接安裝 Nginx

  • SwitchHosts - 切換host,,映射自定義域名地址,可以避免跨域問題,。如果沒有就直接修改本地的 host 文件,。你可以配置自己的。

192.168.1.107 sso.
192.168.1.107 client1.
192.168.1.107 client2.
  • 工程:https://github.com/fuzhengwei/xfg-dev-tech-oauth2-sso

2. 工程結(jié)構(gòu)

  • xfg-dev-tech-app 是 SSO Auth 的鑒權(quán)服務(wù),。
  • test 模塊下有2個 client,,方便驗證一個登錄成功后,另外一個不會再跳轉(zhuǎn)登錄了,。
  • docs/dev-ops 下提供了 docker compose 腳本,,用于部署 Nginx 以及配合的前后端分離的前端頁面。

3. 鑒權(quán)服務(wù)

server:
  port: 8091
  application:
    name: xfg-dev-tech-sso
  servlet:
    context-path: /auth
    session:
      cookie:
        name: OAuth2SSOToken

  • yml 配置了 auth 路徑和一個 session 名稱,。

3.1 鑒權(quán)配置

AuthorizationServerConfig

@Bean
public ClientDetailsService inMemoryClientDetailsService() throws Exception {
    return new InMemoryClientDetailsServiceBuilder()
            // client1 mall
            .withClient("client1")
            .secret(passwordEncoder.encode("client1_secret"))
            .scopes("all")
            .authorizedGrantTypes("authorization_code""refresh_token")
            .redirectUris("http://client1./client1/login")
            .accessTokenValiditySeconds(7200)
            .autoApprove(true)
            .and()
      
            // client2 lottery
            .withClient("client2")
            .secret(passwordEncoder.encode("client2_secret"))
            .scopes("all")
            .authorizedGrantTypes("authorization_code""refresh_token")
            .redirectUris("http://client2./client2/login")
            .accessTokenValiditySeconds(7200)
            .autoApprove(true)
            .and()
            .build();
}
  • 配置鑒權(quán)信息,,這里配置了兩個客戶端信息。

3.2 驗證入口

@Component("unauthorizedEntryPoint")
public class AppUnauthorizedEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        Map<String, String[]> paramMap = request.getParameterMap();
        StringBuilder param = new StringBuilder();
        paramMap.forEach((k, v) -> {
            param.append("&").append(k).append("=").append(v[0]);
        });

        param.deleteCharAt(0);
        String isRedirectValue = request.getParameter("isRedirect");

        if (!StringUtils.isEmpty(isRedirectValue) && Boolean.parseBoolean(isRedirectValue)) {
            response.sendRedirect("http://sso./authPage/#/login?" + param);
            return;
        }

        String authUrl = "http://sso./auth/oauth/authorize?" + param + "&isRedirect=true";

        Map<String, Object> result = new HashMap<>();
        result.put("code"800);
        result.put("msg""授權(quán)地址");
        result.put("data", authUrl);

        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter writer = response.getWriter();
        ObjectMapper mapper = new ObjectMapper();
        writer.print(mapper.writeValueAsString(result));
        writer.flush();
        writer.close();
    }

}
  • 需要實現(xiàn) AuthenticationEntryPoint 接口,,配置一個轉(zhuǎn)發(fā)的地址,。

4. 客戶端 - client1/client2

4.1 client1

server:
  port: 8001
  servlet:
    context-path: /client1

security:
  oauth2:
    client:
      client-id: client1
      preEstablishedRedirectUri:
      client-secret: client1_secret
      access-token-uri: http://sso./auth/oauth/token
      user-authorization-uri: http://sso./auth/oauth/authorize
    resource:
      user-info-uri: http://sso./auth/user
      token-info-uri: http://sso./auth/oauth/check_token
@RestController
public class Client01Controller {

    @GetMapping("/create_pay_order")
    public Result createPayOrder() {
        Result result = new Result();
        result.setCode(0);
        result.setData("下單完成");
        return result;
    }

    @GetMapping("/")
    public void callback(HttpServletResponse response) throws IOException {
        response.sendRedirect("http://client1./client1Page/#/home");
    }

}
  • 模擬下單,,和 callback 地址配置。

4.2 client2

server:
  port: 8002
  servlet:
    context-path: /client2

security:
  oauth2:
    client:
      client-id: client2
      client-secret: client2_secret
      preEstablishedRedirectUri:
      access-token-uri: http://sso./auth/oauth/token
      user-authorization-uri: http://sso./auth/oauth/authorize
    resource:
      user-info-uri: http://sso./auth/user
      token-info-uri: http://sso./auth/oauth/check_token
@RestController
public class Client02Controller {

    @GetMapping("/lottery")
    public Result lottery() {
        Result result = new Result();
        result.setCode(0);
        result.setData("下單紅包,,金額:" + RandomStringUtils.randomNumeric(10) + "元");
        return result;
    }

    @GetMapping("/")
    public void callback(HttpServletResponse response) throws IOException {
        response.sendRedirect("http://client2./client2Page/#/home");
    }

}
  • 模擬另外一個微服務(wù)獲取紅包,,以及 callback 地址服務(wù)。

5. 前端頁面

5.1 校驗

<div class="login-container">
    <h2>登錄</h2>
    <input type="text" id="username" placeholder="用戶名" required>
    <input type="password" id="password" placeholder="密碼" required>
    <button id="login-btn">登錄</button>
</div>

<script src="https://cdn./npm/axios/dist/axios.min.js"></script>
<script>
    const base = 'http://sso.'// 設(shè)置你的基礎(chǔ)URL

    document.getElementById('login-btn').addEventListener('click'function({
        const loginForm = {
            usernamedocument.getElementById('username').value,
            passworddocument.getElementById('password').value
        };

        postRequest('/auth/login', loginForm).then(resp => {
            if (resp.data.code === 0) {
                const pageUrl = window.location.href;
                const param = pageUrl.split('?')[1];
                window.location.href = '/auth/oauth/authorize?' + param;
            } else {
                console.log('登錄失?。?#39; + resp.data.msg);
            }
        });
    });

    function postRequest(url, params{
        return axios({
            method'post',
            url`${base}${url}`,
            data: params,
            transformRequest: [function (data{
                let ret = '';
                for (let it in data) {
                    ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&';
                }
                return ret;
            }],
            headers: {
                'Content-Type''application/x-www-form-urlencoded'
            }
        });
    }
</script>
  • 登錄跳轉(zhuǎn)操作,,這里會走到 Nginx 中進行轉(zhuǎn)發(fā)。

5.2 客戶端01

<div>
    <button id="testButton">開始下單</button>
    <p id="result">下單結(jié)果:</p>
</div>

<script src="https://cdn./npm/axios/dist/axios.min.js"></script>
<script>

const base = 'http://client1.';
function getRequest(url{
    return axios.get(`${base}${url}`);
}

document.getElementById('testButton').addEventListener('click'function({
    getRequest('/client1/create_pay_order').then(resp => {
        const resultElement = document.getElementById('result');
        if (resp.data.code === 0) {
            const linkHtml = "&nbsp;&nbsp;<a href='http://client2./client2Page/#/home'>領(lǐng)紅包</a>";
            resultElement.innerHTML = resp.data.data + linkHtml;
        } else if (resp.data.code === 800) {
            window.location.href = resp.data.data;
        } else {
            console.log('失?。?#39; + resp.data);
        }
    }).catch(error => {
        console.log('請求失?。?#39;, error);
    });
});

</script>
  • 下單的時候會檢查是否登錄,否則會被調(diào)整到 auth 校驗,。

5.2 客戶端02

<div>
    <button id="testButton">隨機紅包</button>
    <p id="result">紅包結(jié)果:</p>
</div>

<script src="https://cdn./npm/axios/dist/axios.min.js"></script>
<script>

const base = 'http://client2.';
function getRequest(url{
    return axios.get(`${base}${url}`);
}

document.getElementById('testButton').addEventListener('click'function({
    getRequest('/client2/lottery').then(resp => {
        const resultElement = document.getElementById('result');
        if (resp.data.code === 0) {
            resultElement.textContent = resp.data.data;
        } else if (resp.data.code === 800) {
            window.location.href = resp.data.data;
        } else {
            console.log('失?。?#39; + resp.data);
        }
    }).catch(error => {
        console.log('請求失敗:', error);
    });
});

</script>
  • 與 client1 的操作是一樣的,,但這里只要有一個登錄了,,另外一個就不會調(diào)整到 auth 頁面登錄了。

6. Nginx 配置

  • Nginx 配置結(jié)構(gòu),,docker compose 啟動的時候會進行安裝,。

6.1 sso.conf

server {
    listen       80;
    server_name  sso.;

    location /auth/ {
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://192.168.1.107:8091/auth/;
    }

    location /authPage/ {
        alias /usr/share/nginx/html/;
        index auth.html;
    }

    location ~ .*\.(js|css)$ {
         alias /usr/share/nginx/html/;
         index auth.html;
    }

}

6.2 client1.conf

server {
    listen       80;
    server_name  client1.;

    location /client1/ {
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://192.168.1.107:8001/client1/;
    }

    location /client1Page/ {
        alias /usr/share/nginx/html/;
        index client1.html;
    }

    location ~ .*\.(js|css)$ {
        alias /usr/share/nginx/html/;
        index client1.html;
    }
}

6.3 client2.conf

server {
    listen       80;
    server_name  client2.;

    location /client2/ {
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://192.168.1.107:8002/client2/;
    }

    location /client2Page/ {
        alias /usr/share/nginx/html/;
        index client2.html;
    }

    location ~ .*\.(js|css)$ {
        alias /usr/share/nginx/html/;
        index client2.html;
    }
}

更多的代碼從工程中閱讀即可,復(fù)雜度不高,。

三,、測試驗證

1. 啟動服務(wù)

  • 你需要啟動 Docker 的 Nginx,之后順序啟動 SSO 服務(wù)和2個客戶端服務(wù),。
  • 另外要配置好 host,,這樣訪問你的自定義域名地址,才會正確的跳轉(zhuǎn),。(這東西在日常公司開發(fā)中會用到的很頻繁)

2. 訪問客戶端

你可以訪問地址1進行驗證,,登錄之后也可以進入地址2進行驗證;

  • http://client1./client1Page/#/home
  • http://client2./client2Page/#/home

    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多