Compare commits
5 Commits
34dcf15ca4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 932edc1b95 | |||
| 0b736230cd | |||
| 0f111c9178 | |||
| a9d92c1252 | |||
| 43d0fdcee1 |
75
README.md
Normal file
75
README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# PHP/WordPress 기반 SSO 연동 데모
|
||||
|
||||
이 프로젝트는 PHP(WordPress) 환경에서 Baron SSO와 같은 외부 인증 서버와 연동하는 방법을 보여주는 데모입니다.
|
||||
|
||||
단순히 기능만 구현하는 것을 넘어, 실제 제품 페이지처럼 보이도록 UI/UX를 개선하는 과정을 포함합니다. 사용자는 팝업창을 통해 외부 SSO 서비스에 로그인하고, 그 결과(JWT)를 받아 WordPress 사이트에 자동으로 로그인 처리됩니다.
|
||||
|
||||
## 주요 기능
|
||||
|
||||
- **팝업 기반 로그인:** 페이지 이동 없이 팝업창을 통해 SSO 로그인을 진행하여 사용자 경험을 향상시켰습니다.
|
||||
- **JWT 인증:** SSO 공급자로부터 받은 JWT를 PHP 백엔드에서 파싱하여 사용자를 인증합니다.
|
||||
- **자동 회원가입 및 로그인:** SSO로부터 받은 고유 사용자 ID(`sub`)를 기반으로 WordPress 사용자를 조회하거나, 존재하지 않을 경우 자동으로 생성하고 즉시 로그인 처리합니다.
|
||||
- **WordPress 관리자 설정:** 관리자 페이지 > 설정 메뉴에 "Baron SSO Login" 메뉴를 추가하여 SSO 프론트엔드 URL을 쉽게 설정할 수 있도록 했습니다.
|
||||
- **향상된 UI/UX:**
|
||||
- 방문자의 시선을 끄는 세련된 히어로(Hero) 헤더
|
||||
- 로그인 상태를 명확히 보여주는 상태 배지(Status Badge)
|
||||
- 가독성을 높인 카드(Card) 형태의 블로그 글 목록
|
||||
- 사용자와의 상호작용을 시각적으로 보여주는 버튼 디자인
|
||||
- 데모의 목적을 명확히 안내하는 CTA(Call-to-Action) 배너
|
||||
|
||||
---
|
||||
|
||||
## 디렉터리 구조
|
||||
|
||||
```
|
||||
.
|
||||
├── docker-compose.yml # WordPress 실행을 위한 Docker 설정 파일
|
||||
├── README.md # 프로젝트 안내 문서
|
||||
└── sso-wordpress-plugin/
|
||||
├── sso-wordpress-plugin.php # SSO 연동 로직과 UI 개선 코드가 담긴 메인 플러그인 파일
|
||||
└── js/ # (현재 비어있음, 향후 JS 분리 시 사용)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 설치 및 실행 방법
|
||||
|
||||
**요구 사항:** Docker, Docker Compose
|
||||
|
||||
1. **WordPress 환경 실행**
|
||||
프로젝트 루트 디렉터리에서 아래 명령어를 실행하여 WordPress 사이트와 데이터베이스를 실행합니다.
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
2. **WordPress 접속 및 설정**
|
||||
- 웹 브라우저에서 `http://localhost:8000` 로 접속하여 WordPress 설치를 진행합니다.
|
||||
- 관리자 페이지(`http://localhost:8000/wp-admin`)로 로그인합니다.
|
||||
|
||||
3. **플러그인 설치 및 활성화**
|
||||
- `sso-wordpress-plugin` 폴더를 zip 파일로 압축합니다.
|
||||
- WordPress 관리자 페이지에서 `플러그인 > 새로 추가 > 플러그인 업로드` 메뉴로 이동합니다.
|
||||
- 압축한 zip 파일을 업로드하고 플러그인을 활성화합니다.
|
||||
|
||||
4. **SSO 플러그인 설정**
|
||||
- `설정 > Baron SSO Login` 메뉴로 이동합니다.
|
||||
- "Baron SSO Frontend URL" 입력 필드에 실제 SSO 서비스의 로그인 URL을 입력합니다. (예: `http://localhost:3000`)
|
||||
- `Save Settings` 버튼을 눌러 저장합니다.
|
||||
|
||||
5. **데모 확인**
|
||||
- 사이트 메인 페이지(`http://localhost:8000`)로 이동하여 우측 상단에 나타나는 "Baron SSO 로그인" 버튼을 통해 SSO 연동 기능을 테스트할 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
## 핵심 동작 원리
|
||||
|
||||
1. **로그인 시작:** 사용자가 "Baron SSO 로그인" 버튼을 클릭하면, `sso_inject_header_button` 함수에 의해 생성된 버튼이 `openSsoPopup` JavaScript 함수를 호출하여 SSO 로그인 페이지를 팝업창으로 띄웁니다.
|
||||
2. **인증 및 토큰 전달:** 사용자가 SSO 서비스에서 로그인을 성공하면, SSO 프론트엔드는 `window.postMessage`를 통해 JWT(JSON Web Token)가 포함된 객체를 원래의 WordPress 창으로 전송합니다.
|
||||
3. **토큰 수신 및 페이지 새로고침:** `sso_add_popup_script` 함수에 포함된 JavaScript 이벤트 리스너가 메시지를 수신하고, JWT를 `?token=...` 쿼리 파라미터로 추가하여 페이지를 새로고침합니다.
|
||||
4. **백엔드 처리:**
|
||||
- `sso_handle_jwt_login` 함수가 페이지 로드 시 `token` 파라미터를 감지하고 `sso_process_jwt_and_login` 함수를 호출합니다.
|
||||
- `sso_process_jwt_and_login` 함수는 JWT를 디코딩하고 `sub` 클레임(사용자 고유 ID)을 추출합니다.
|
||||
- 추출된 ID를 `sso_subject_id`라는 `user_meta` 키로 WordPress 사용자를 조회합니다.
|
||||
- 사용자가 없으면 해당 ID를 사용자명으로, `ID@sso.local` 형식의 가상 이메일로 새 사용자를 생성하고 `user_meta`에 `sso_subject_id`를 저장합니다.
|
||||
- `wp_set_auth_cookie` 등의 함수를 통해 사용자를 로그인 상태로 만듭니다.
|
||||
5. **UI 업데이트:** 페이지가 다시 로드되면 사용자는 로그인 상태가 되며, 우측 상단에는 "로그아웃" 버튼과 함께 "Logged in as: ..." 배지가 표시됩니다.
|
||||
@@ -28,6 +28,7 @@ services:
|
||||
WORDPRESS_DB_USER: wordpress
|
||||
WORDPRESS_DB_PASSWORD: password
|
||||
WORDPRESS_DB_NAME: wordpress
|
||||
WORDPRESS_DEBUG: 1
|
||||
|
||||
volumes:
|
||||
db_data: {}
|
||||
|
||||
83
docs/wordpress_sso_integration.md
Normal file
83
docs/wordpress_sso_integration.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# WordPress SSO 플러그인: 구현 및 동작 원리
|
||||
|
||||
이 문서는 Baron SSO와 WordPress를 연동하는 플러그인의 상세한 구현 과정과 핵심 동작 로직을 설명합니다.
|
||||
|
||||
## 1. 프로젝트 목표 및 결과물
|
||||
|
||||
**목표:** 외부 SSO 인증을 통해 WordPress 사이트에 별도의 회원가입이나 로그인 절차 없이 자동으로 사용자를 인증하고, 실제 제품 데모처럼 보이는 세련된 UI/UX를 제공하는 WordPress 플러그인을 개발합니다.
|
||||
|
||||
**핵심 결과물:**
|
||||
- 팝업창을 이용한 비동기 방식의 SSO 로그인 연동
|
||||
- JWT `sub` 클레임을 이용한 안정적인 사용자 식별 및 자동 회원가입
|
||||
- WordPress Hook을 활용하여 최소한의 코드로 핵심 기능 및 UI 구현
|
||||
|
||||
---
|
||||
|
||||
## 2. 핵심 로그인 플로우 (Sequence Diagram)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor User
|
||||
participant WP Frontend (JS)
|
||||
participant SSO Service (Popup)
|
||||
participant WP Backend (PHP)
|
||||
|
||||
User->>WP Frontend (JS): "Baron SSO 로그인" 버튼 클릭
|
||||
WP Frontend (JS)->>SSO Service (Popup): window.open() 팝업 실행
|
||||
SSO Service (Popup)-->>User: 로그인 UI 표시
|
||||
User->>SSO Service (Popup): 아이디/패스워드 입력 및 로그인
|
||||
SSO Service (Popup)->>SSO Service (Popup): 인증 처리 및 JWT 생성
|
||||
SSO Service (Popup)->>WP Frontend (JS): postMessage로 JWT 전송
|
||||
Note right of SSO Service (Popup): 팝업창 닫힘
|
||||
WP Frontend (JS)->>WP Frontend (JS): JWT 수신 후, `?token=[JWT]` 파라미터와 함께 페이지 새로고침
|
||||
|
||||
WP Frontend (JS)->>WP Backend (PHP): 페이지 요청 (GET /?token=...)
|
||||
WP Backend (PHP)->>WP Backend (PHP): `init` hook에서 `token` 파라미터 감지
|
||||
WP Backend (PHP)->>WP Backend (PHP): JWT 디코딩하여 `sub` 추출
|
||||
WP Backend (PHP)->>WP Backend (PHP): `sub` 값으로 사용자 조회 (usermeta)
|
||||
alt 사용자 없음
|
||||
WP Backend (PHP)->>WP Backend (PHP): 신규 사용자 생성 (wp_create_user)
|
||||
WP Backend (PHP)->>WP Backend (PHP): `sso_subject_id` 메타 정보 저장
|
||||
end
|
||||
WP Backend (PHP)->>WP Frontend (JS): 로그인 쿠키 생성 후 `token` 제거된 URL로 리다이렉트
|
||||
|
||||
WP Frontend (JS)->>WP Backend (PHP): 최종 페이지 요청 (GET /)
|
||||
WP Backend (PHP)-->>WP Frontend (JS): 로그인된 페이지 렌더링
|
||||
WP Frontend (JS)-->>User: 로그인 완료된 화면 표시
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 상세 구현 내용
|
||||
|
||||
### A. 프론트엔드 (JavaScript)
|
||||
|
||||
- **`sso_add_popup_script` 함수 (`wp_footer` hook)**
|
||||
- **팝업 실행:** `openSsoPopup` 함수는 `window.open`을 사용해 정해진 크기의 팝업창을 화면 중앙에 띄웁니다.
|
||||
- **토큰 수신:** `window.addEventListener('message', ...)`를 통해 다른 창으로부터 메시지를 안전하게 수신합니다. SSO 팝업창에서 `postMessage`로 보낸 데이터가 `type: 'LOGIN_SUCCESS'` 이고 `token`이 포함된 객체인지 검증합니다.
|
||||
- **페이지 리로드:** 수신한 토큰을 쿼리 파라미터로 추가하여 현재 페이지를 다시 로드합니다. 이 리로드 과정이 백엔드 로직을 트리거하는 핵심 열쇠입니다.
|
||||
|
||||
### B. 백엔드 (PHP)
|
||||
|
||||
- **`sso_handle_jwt_login` 함수 (`init` hook)**
|
||||
- WordPress가 초기화되는 시점에 실행되어, `$_GET['token']` 파라미터의 존재 여부를 가장 먼저 확인합니다. 이 방식을 통해 다른 어떤 콘텐츠보다 먼저 로그인 로직이 실행되도록 보장합니다.
|
||||
- **`sso_process_jwt_and_login` 함수**
|
||||
- **JWT 파싱:** 전달받은 JWT를 `.` 기준으로 분리하고, 두 번째 부분(Payload)을 Base64 디코딩하여 사용자 정보를 담은 객체를 얻습니다.
|
||||
- **사용자 식별:** 기존의 이메일 방식 대신, `sub` 클레임을 `sanitize_text_field`로 안전하게 처리한 후 `get_users` 함수와 `meta_query`를 사용해 `sso_subject_id`와 일치하는 사용자를 찾습니다. 이 방식은 이메일이 없거나 변경되어도 동일 사용자를 식별할 수 있게 해줍니다.
|
||||
- **자동 회원가입:** 일치하는 사용자가 없으면 `wp_create_user`를 호출합니다. 이때 WordPress의 필수 필드인 이메일은 `사용자명@sso.local`과 같이 고유한 가상 주소로 채워줍니다.
|
||||
- **메타 데이터 저장:** 신규 생성된 사용자의 경우, `update_user_meta`를 통해 `sso_subject_id`를 저장해두어 다음 로그인 시 동일 사용자로 인식할 수 있게 합니다.
|
||||
- **로그인 실행:** `wp_set_current_user`와 `wp_set_auth_cookie`를 통해 WordPress의 표준 로그인 상태를 만듭니다.
|
||||
|
||||
### C. UI/UX 개선
|
||||
|
||||
- **`sso_custom_styles` 함수 (`wp_head` hook)**
|
||||
- 하나의 함수에서 모든 CSS를 `<style>` 태그로 감싸 `wp_head` 시점에 주입합니다. 이를 통해 별도의 CSS 파일 없이 플러그인 파일 하나만으로 UI 커스터마이징을 완료했습니다.
|
||||
- **히어로 헤더:** `.entry-header` 클래스에 `calc(50% - 50vw)` 트릭을 사용한 음수 마진을 적용하여 부모 컨테이너의 너비 제약을 벗어나 화면 전체 너비의 배경을 갖도록 구현했습니다.
|
||||
- **카드형 목록:** `.post`, `.article` 클래스에 `background-color`, `border`, `box-shadow`, `border-radius` 속성을 부여하여 각 글이 입체적인 카드처럼 보이도록 스타일링했습니다.
|
||||
- **기타 요소:** 로그인 배지, CTA 배너, 버튼 인터랙션 등 모든 시각적 요소는 이 함수 내에 CSS 클래스로 정의되어 관리됩니다.
|
||||
- **`sso_inject_header_button` 함수 (`wp_body_open` hook)**
|
||||
- `is_user_logged_in()` 조건문을 통해 로그인 상태를 분기 처리합니다.
|
||||
- **로그인 시:** `wp_get_current_user()`로 현재 사용자 정보를 가져와 이메일을 상태 배지에 표시하고, 로그아웃 버튼을 출력합니다.
|
||||
- **로그아웃 시:** 설정 페이지에서 저장한 SSO 프론트엔드 URL을 기반으로 로그인 팝업을 띄우는 버튼을 출력합니다.
|
||||
- **`sso_add_cta_banner` 함수 (`loop_start` hook)**
|
||||
- `is_home() && !is_user_logged_in()` 조건문을 통해 블로그 메인 페이지에 로그아웃 상태인 경우에만 CTA 배너가 표시되도록 제어합니다. `loop_start` 훅을 사용하여 게시물 루프가 시작되기 직전에 배너가 나타나도록 했습니다.
|
||||
@@ -1,29 +0,0 @@
|
||||
# SSO 로그인 버튼 워드프레스 플러그인
|
||||
|
||||
## 설명
|
||||
|
||||
`[sso_login_button]` 숏코드를 제공하는 간단한 워드프레스 플러그인입니다. 이 숏코드는 로그인 버튼을 표시하며, 버튼을 클릭하면 설정된 SSO URL로 팝업 창을 엽니다.
|
||||
|
||||
이 플러그인은 프론트엔드 SSO 흐름을 워드프레스 사이트에 통합하는 방법을 보여주는 경량 데모용으로 제작되었습니다.
|
||||
|
||||
## 설치 방법
|
||||
|
||||
1. `sso-wordpress-plugin` 디렉토리를 ZIP 파일로 압축합니다.
|
||||
2. 워드프레스 관리자 대시보드에서 `플러그인` > `새로 추가`로 이동합니다.
|
||||
3. 페이지 상단의 `플러그인 업로드` 버튼을 클릭합니다.
|
||||
4. 다운로드한 ZIP 파일을 선택하고 `지금 설치`를 클릭합니다.
|
||||
5. 설치가 완료되면 `플러그인 활성화` 버튼을 클릭합니다.
|
||||
|
||||
## 설정 방법
|
||||
|
||||
1. 플러그인이 활성화되면 워드프레스 관리자 대시보드에서 `설정` > `SSO Login`으로 이동합니다.
|
||||
2. 입력 필드에 SSO 제공업체의 전체 URL을 입력합니다 (예: `http://localhost:5000/`).
|
||||
3. `변경 사항 저장` 버튼을 클릭합니다.
|
||||
|
||||
## 사용 방법
|
||||
|
||||
페이지나 글에 SSO 로그인 버튼을 표시하려면, 콘텐츠 편집기에 아래의 숏코드를 추가하기만 하면 됩니다:
|
||||
|
||||
`[sso_login_button]`
|
||||
|
||||
사용자가 이 버튼을 클릭하면 설정 페이지에서 구성한 URL로 팝업 창이 열립니다.
|
||||
@@ -1,37 +0,0 @@
|
||||
// This file will contain the JavaScript logic for the SSO login button.
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const ssoLoginBtn = document.getElementById('sso-login-btn');
|
||||
|
||||
if (ssoLoginBtn) {
|
||||
ssoLoginBtn.addEventListener('click', () => {
|
||||
// ssoSettings.ssoUrl will be passed from WordPress using wp_localize_script
|
||||
if (typeof ssoSettings === 'undefined' || !ssoSettings.ssoUrl) {
|
||||
alert('SSO URL is not configured.');
|
||||
return;
|
||||
}
|
||||
|
||||
const ssoUrl = ssoSettings.ssoUrl;
|
||||
|
||||
const popupWidth = 600;
|
||||
const popupHeight = 800;
|
||||
const screenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX;
|
||||
const screenTop = window.screenTop !== undefined ? window.screenTop : window.screenY;
|
||||
const screenWidth = window.innerWidth || document.documentElement.clientWidth || screen.width;
|
||||
const screenHeight = window.innerHeight || document.documentElement.clientHeight || screen.height;
|
||||
const left = ((screenWidth / 2) - (popupWidth / 2)) + screenLeft;
|
||||
const top = ((screenHeight / 2) - (popupHeight / 2)) + screenTop;
|
||||
|
||||
const popup = window.open(
|
||||
ssoUrl,
|
||||
'sso-login-popup',
|
||||
`width=${popupWidth},height=${popupHeight},left=${left},top=${top}`
|
||||
);
|
||||
|
||||
if (popup) {
|
||||
popup.focus();
|
||||
} else {
|
||||
alert('Popup blocked. Please allow popups for this site.');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,120 +1,465 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: SSO Login Button
|
||||
* Description: Adds a shortcode to display an SSO login button.
|
||||
* Version: 1.0.0
|
||||
* Plugin Name: Baron SSO Integration
|
||||
* Description: Integrates WordPress with Baron SSO for authentication via popup.
|
||||
* Version: 1.2.0
|
||||
* Author: Gemini
|
||||
* License: GPL-2.0-or-later
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
* Text Domain: sso-login-button
|
||||
* Text Domain: baron-sso
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
// Will add plugin code here.
|
||||
|
||||
|
||||
/**
|
||||
* Shortcode function to display the SSO login button.
|
||||
*
|
||||
* @return string The HTML for the login button.
|
||||
* Add custom CSS to the head for the fixed login/logout button.
|
||||
*/
|
||||
function sso_login_button_shortcode() {
|
||||
// The button HTML. The actual script will be enqueued separately.
|
||||
return '<button id="sso-login-btn">Login with SSO</button>';
|
||||
function sso_custom_styles() {
|
||||
echo '<style>
|
||||
/* 5. General Layout & Typography */
|
||||
body {
|
||||
font-size: 17px;
|
||||
line-height: 1.7;
|
||||
color: #333;
|
||||
}
|
||||
.sso-container {
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
/* Adjusting the main content area if a theme uses this common class */
|
||||
.site-content {
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
/* Remove extra top margin from the first section title after the hero */
|
||||
.entry-header + h2,
|
||||
.entry-header + .widget-title {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* 1. Hero Header Section */
|
||||
.entry-header {
|
||||
position: relative;
|
||||
padding: 72px 24px 56px;
|
||||
/* Negative margin to break out of a constrained container and go full-width */
|
||||
margin-left: calc(50% - 50vw);
|
||||
margin-right: calc(50% - 50vw);
|
||||
margin-bottom: 48px;
|
||||
background: linear-gradient(145deg, #1a2a3a, #3a4a5a); /* Dark blue-gray gradient */
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Clean up spacing if the header is inside the main content area */
|
||||
.site-content .entry-header {
|
||||
margin-top: 0;
|
||||
}
|
||||
body.home .entry-header {
|
||||
margin-top: -20px; /* Adjust as needed for specific themes */
|
||||
}
|
||||
|
||||
.page-title, .entry-title {
|
||||
margin: 0;
|
||||
font-size: clamp(44px, 5vw, 64px) !important;
|
||||
letter-spacing: -0.02em;
|
||||
color: #6868AC !important; /* Custom page title color */
|
||||
text-align: center;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.page-title::after, .entry-title::after {
|
||||
content: "This page demonstrates a seamless single sign-on integration for WordPress.";
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
text-align: center;
|
||||
opacity: 0.9;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
letter-spacing: normal;
|
||||
color: #fff; /* Ensure subtitle stays white */
|
||||
}
|
||||
|
||||
/* 4. Section Title Divider */
|
||||
h2, .widget-title {
|
||||
font-size: 2.5rem;
|
||||
margin-top: 64px;
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 2px solid #eee;
|
||||
}
|
||||
|
||||
/* 2. Login Status Badge & Button Container */
|
||||
#sso-fixed-button {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
z-index: 99999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
body.admin-bar #sso-fixed-button {
|
||||
top: 42px;
|
||||
}
|
||||
|
||||
.sso-status-badge {
|
||||
display: inline-block;
|
||||
padding: 6px 10px;
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
color: #333;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
body.admin-bar .sso-status-badge {
|
||||
background-color: rgba(255,255,255,0.8);
|
||||
}
|
||||
|
||||
/* 6. Product-style Button */
|
||||
#sso-fixed-button a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 42px;
|
||||
padding: 0 14px;
|
||||
background-color: #A19FE7; /* Custom button color */
|
||||
color: #fff !important;
|
||||
border: 1px solid rgba(0,0,0,0.1);
|
||||
border-radius: 6px;
|
||||
text-decoration: none !important;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
transition: transform 0.15s ease, filter 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
#sso-fixed-button a:hover {
|
||||
transform: translateY(-2px);
|
||||
filter: brightness(1.1);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
#sso-fixed-button a:active {
|
||||
transform: translateY(0);
|
||||
filter: brightness(1.0);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* 3. Post Card Styling */
|
||||
.post, .article {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
.post:hover, .article:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 7px 14px rgba(0,0,0,0.07);
|
||||
}
|
||||
|
||||
.post .entry-title, .article .entry-title {
|
||||
font-size: 24px !important;
|
||||
margin-bottom: 12px;
|
||||
color: #1a2a3a !important;
|
||||
}
|
||||
.post .entry-summary, .post .entry-content,
|
||||
.article .entry-summary, .article .entry-content {
|
||||
font-size: 16px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* Remove the old divider */
|
||||
.post + .post, .article + .article {
|
||||
border-top: none;
|
||||
padding-top: 24px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* 7. Call-to-Action Banner */
|
||||
.sso-cta-banner {
|
||||
background-color: #f0f7ff;
|
||||
border: 1px solid #b3d7ff;
|
||||
border-radius: 6px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 48px;
|
||||
text-align: center;
|
||||
color: #004085;
|
||||
}
|
||||
.sso-cta-banner p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>';
|
||||
}
|
||||
add_shortcode( 'sso_login_button', 'sso_login_button_shortcode' );
|
||||
add_action( 'wp_head', 'sso_custom_styles' );
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// 2. PUBLIC-FACING FEATURES (LOGIN/LOGOUT BUTTON)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Enqueue scripts and styles.
|
||||
* Injects a fixed login/logout button at the top of the body.
|
||||
*/
|
||||
function sso_enqueue_scripts() {
|
||||
// Only enqueue the script if the shortcode is present on the page.
|
||||
// Note: This is a simplified check. A more robust solution might be needed for complex cases.
|
||||
global $post;
|
||||
if ( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'sso_login_button' ) ) {
|
||||
wp_enqueue_script(
|
||||
'sso-login-script',
|
||||
plugin_dir_url( __FILE__ ) . 'js/sso-login.js',
|
||||
array(),
|
||||
'1.0.0',
|
||||
true
|
||||
);
|
||||
function sso_inject_header_button() {
|
||||
// Don't show on admin pages or the login page itself.
|
||||
if ( is_admin() || $GLOBALS['pagenow'] === 'wp-login.php' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass the SSO URL to the script.
|
||||
// We'll make this dynamic with a settings page later.
|
||||
$sso_url = get_option('sso_plugin_url', 'http://localhost:5000/');
|
||||
wp_localize_script(
|
||||
'sso-login-script',
|
||||
'ssoSettings',
|
||||
array(
|
||||
'ssoUrl' => esc_url( $sso_url ),
|
||||
)
|
||||
);
|
||||
echo '<div id="sso-fixed-button">';
|
||||
if ( is_user_logged_in() ) {
|
||||
$current_user = wp_get_current_user();
|
||||
echo '<span class="sso-status-badge">Logged in as: ' . esc_html( $current_user->user_email ) . '</span>';
|
||||
echo '<a href="' . wp_logout_url( home_url() ) . '">Logout</a>';
|
||||
} else {
|
||||
$sso_frontend_url = get_option( 'sso_plugin_frontend_url' );
|
||||
if ( ! empty( $sso_frontend_url ) ) {
|
||||
// Construct the redirect URL for SSO to return to WordPress after login.
|
||||
$redirect_after_sso = ( is_ssl() ? 'https' : 'http' ) . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
|
||||
$login_url = add_query_arg( 'redirect_url', urlencode( $redirect_after_sso ), $sso_frontend_url );
|
||||
// Now, the button opens a popup window.
|
||||
echo '<a href="#" onclick="openSsoPopup(\'' . esc_url( $login_url ) . '\'); return false;">Baron SSO Login</a>';
|
||||
}
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
add_action( 'wp_body_open', 'sso_inject_header_button' );
|
||||
|
||||
|
||||
/**
|
||||
* Injects a Call-to-Action banner before the main content loop.
|
||||
*/
|
||||
function sso_add_cta_banner() {
|
||||
// Show only on the main blog/posts page and when the user is not logged in.
|
||||
if ( is_home() && ! is_user_logged_in() ) {
|
||||
echo '<div class="sso-cta-banner">
|
||||
<p>SSO로 로그인하면 회원 전용 글을 확인할 수 있습니다.</p>
|
||||
</div>';
|
||||
}
|
||||
}
|
||||
add_action( 'wp_enqueue_scripts', 'sso_enqueue_scripts' );
|
||||
add_action( 'loop_start', 'sso_add_cta_banner' );
|
||||
|
||||
|
||||
/**
|
||||
* Admin Menu and Settings
|
||||
* Filters the document title to display a custom title.
|
||||
*
|
||||
* @param string $title The original title.
|
||||
* @return string The modified title.
|
||||
*/
|
||||
function sso_custom_document_title( $title ) {
|
||||
return 'PHP SSO Login Demo';
|
||||
}
|
||||
add_filter( 'pre_get_document_title', 'sso_custom_document_title', 9999 );
|
||||
|
||||
|
||||
/**
|
||||
* Adds the JavaScript for the SSO popup to the footer.
|
||||
*/
|
||||
function sso_add_popup_script() {
|
||||
?>
|
||||
<script>
|
||||
function openSsoPopup(url) {
|
||||
var width = 600, height = 700;
|
||||
var left = (screen.width / 2) - (width / 2);
|
||||
var top = (screen.height / 2) - (height / 2);
|
||||
var popup = window.open(url, 'ssoLoginPopup', 'width=' + width + ',height=' + height + ',top=' + top + ',left=' + left);
|
||||
}
|
||||
|
||||
// Listen for a message from the popup.
|
||||
window.addEventListener('message', function(event) {
|
||||
// For security, you should validate the origin here.
|
||||
// Example: if (event.origin !== "https://sso.hmac.kr") return;
|
||||
|
||||
// Check if the received data is an object with the expected structure.
|
||||
if (typeof event.data === 'object' && event.data !== null && event.data.type === 'LOGIN_SUCCESS' && event.data.token) {
|
||||
console.log('SSO login success message object received. Processing token...');
|
||||
|
||||
// Construct the URL to reload the page with the token.
|
||||
// This will trigger the PHP handler on the backend.
|
||||
const currentUrl = new URL(window.location.href);
|
||||
currentUrl.searchParams.set('token', event.data.token);
|
||||
|
||||
// Redirect to the new URL to process the login.
|
||||
window.location.href = currentUrl.toString();
|
||||
|
||||
} else {
|
||||
// This log can be removed in production.
|
||||
console.log('Received message, but data format is not the expected SSO login success object.', event.data);
|
||||
}
|
||||
}, false);
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
add_action( 'wp_footer', 'sso_add_popup_script' );
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// 3. SSO CALLBACK AND LOGIN PROCESSING
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Handles the JWT from the SSO provider when the main page is reloaded with a token.
|
||||
*/
|
||||
function sso_handle_jwt_login() {
|
||||
// This now runs on the main page, not the popup.
|
||||
if ( ! is_user_logged_in() && isset( $_GET['token'] ) ) {
|
||||
|
||||
$jwt = sanitize_text_field( $_GET['token'] );
|
||||
|
||||
// Process the JWT, create/find the user, and set the auth cookie.
|
||||
sso_process_jwt_and_login( $jwt );
|
||||
|
||||
// After processing, redirect to the same URL without the token to clean up the address bar.
|
||||
wp_redirect( remove_query_arg( 'token' ) );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
add_action( 'init', 'sso_handle_jwt_login' );
|
||||
|
||||
|
||||
/**
|
||||
* Core function to process JWT, find/create a user, and log them in.
|
||||
*/
|
||||
function sso_process_jwt_and_login( $jwt ) {
|
||||
// --- JWT Validation Logic ---
|
||||
// IMPORTANT: THIS IS A NON-SECURE PLACEHOLDER. Replace with proper validation.
|
||||
$jwt_parts = explode('.', $jwt);
|
||||
if (count($jwt_parts) !== 3) {
|
||||
wp_die('SSO Error: Invalid token format.');
|
||||
return;
|
||||
}
|
||||
$payload = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $jwt_parts[1])));
|
||||
|
||||
// --- DEBUGGING: Print the payload and stop ---
|
||||
// echo '<pre>';
|
||||
// print_r($payload);
|
||||
// echo '</pre>';
|
||||
// wp_die('JWT Payload Inspection. Check the subject field name.');
|
||||
// --- END DEBUGGING ---
|
||||
|
||||
if ( ! isset( $payload->sub ) || empty( $payload->sub ) ) {
|
||||
wp_die('SSO Error: User subject ID (sub) not found or is empty in token.');
|
||||
return;
|
||||
}
|
||||
$sso_user_id = sanitize_text_field($payload->sub);
|
||||
// --- End of Placeholder ---
|
||||
|
||||
// Find user by SSO subject ID meta field.
|
||||
$users = get_users([
|
||||
'meta_key' => 'sso_subject_id',
|
||||
'meta_value' => $sso_user_id,
|
||||
'number' => 1,
|
||||
'count_total' => false
|
||||
]);
|
||||
|
||||
$user = ! empty( $users ) ? $users[0] : null;
|
||||
|
||||
if ( ! $user ) {
|
||||
// User does not exist, so create them.
|
||||
// Use the SSO ID for the username, ensuring it's unique.
|
||||
$username = sanitize_user( $sso_user_id );
|
||||
$base_username = $username;
|
||||
$i = 1;
|
||||
while ( username_exists( $username ) ) {
|
||||
$username = $base_username . $i++;
|
||||
}
|
||||
|
||||
// Create a fake email, as it's required by WordPress but not by our SSO.
|
||||
$user_email = $username . '@sso.local';
|
||||
|
||||
$user_id = wp_create_user( $username, wp_generate_password(), $user_email );
|
||||
|
||||
if ( is_wp_error( $user_id ) ) {
|
||||
wp_die('Error creating SSO user: ' . $user_id->get_error_message());
|
||||
}
|
||||
|
||||
// Store the SSO user ID for future lookups.
|
||||
update_user_meta( $user_id, 'sso_subject_id', $sso_user_id );
|
||||
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
}
|
||||
|
||||
// Log the user in.
|
||||
if ( $user ) {
|
||||
wp_set_current_user( $user->ID );
|
||||
wp_set_auth_cookie( $user->ID, true );
|
||||
do_action( 'wp_login', $user->user_login, $user );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// 4. CONTENT ACCESS RESTRICTIONS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Redirects unauthenticated users from single posts to the SSO login page.
|
||||
*/
|
||||
function sso_restrict_single_posts() {
|
||||
if ( is_single() && ! is_user_logged_in() ) {
|
||||
$sso_frontend_url = get_option( 'sso_plugin_frontend_url' );
|
||||
if ( ! empty( $sso_frontend_url ) ) {
|
||||
$current_url = ( is_ssl() ? 'https' : 'http' ) . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
|
||||
// The redirect URL for SSO is now a dedicated popup callback URL
|
||||
$sso_callback_url = add_query_arg( 'sso_popup_callback', '1', $current_url );
|
||||
$login_url = add_query_arg( 'redirect_url', urlencode( $sso_callback_url ), $sso_frontend_url );
|
||||
wp_redirect( esc_url( $login_url ) );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Let's disable this for now to simplify testing the popup button first.
|
||||
// add_action( 'template_redirect', 'sso_restrict_single_posts' );
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// 5. ADMIN SETTINGS PAGE
|
||||
// =============================================================================
|
||||
|
||||
// Add the settings page to the admin menu
|
||||
function sso_plugin_add_settings_page() {
|
||||
add_options_page(
|
||||
'SSO Settings',
|
||||
'SSO Login',
|
||||
'manage_options',
|
||||
'sso-plugin-settings',
|
||||
'sso_plugin_render_settings_page'
|
||||
);
|
||||
add_options_page('Baron SSO Settings', 'Baron SSO Login', 'manage_options', 'baron-sso-settings', 'sso_plugin_render_settings_page');
|
||||
}
|
||||
add_action( 'admin_menu', 'sso_plugin_add_settings_page' );
|
||||
|
||||
// Render the settings page HTML
|
||||
function sso_plugin_render_settings_page() {
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>SSO Login Settings</h1>
|
||||
<h1>Baron SSO Login Settings</h1>
|
||||
<form action="options.php" method="post">
|
||||
<?php
|
||||
settings_fields( 'sso_plugin_options' );
|
||||
do_settings_sections( 'sso_plugin_settings' );
|
||||
?>
|
||||
<input
|
||||
type="text"
|
||||
name="sso_plugin_url"
|
||||
value="<?php echo esc_attr( get_option( 'sso_plugin_url' ) ); ?>"
|
||||
size="50"
|
||||
placeholder="Enter the full SSO URL"
|
||||
/>
|
||||
<table class="form-table">
|
||||
<tr valign="top">
|
||||
<th scope="row">Baron SSO Frontend URL</th>
|
||||
<td>
|
||||
<input type="text" name="sso_plugin_frontend_url"
|
||||
value="<?php echo esc_attr( get_option( 'sso_plugin_frontend_url' ) ); ?>"
|
||||
size="50" placeholder="Enter the Baron SSO Frontend Login URL" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php submit_button( 'Save Settings' ); ?>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Register the setting
|
||||
function sso_plugin_register_settings() {
|
||||
register_setting(
|
||||
'sso_plugin_options',
|
||||
'sso_plugin_url',
|
||||
[
|
||||
register_setting('sso_plugin_options', 'sso_plugin_frontend_url', [
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'esc_url_raw',
|
||||
'default' => '',
|
||||
]
|
||||
);
|
||||
|
||||
add_settings_section(
|
||||
'sso_plugin_main_section',
|
||||
'Main Settings',
|
||||
null,
|
||||
'sso_plugin_settings'
|
||||
);
|
||||
]);
|
||||
add_settings_section('sso_plugin_main_section', 'Main Settings', null, 'sso_plugin_settings');
|
||||
}
|
||||
add_action( 'admin_init', 'sso_plugin_register_settings' );
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user