> For the complete documentation index, see [llms.txt](https://malgn.gitbook.io/cloud/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://malgn.gitbook.io/cloud/request-forms/addon-services/sso-integration-guide.md).

# SSO 로그인 연동 가이드

### 1. 소개

* 맑은이러닝 클라우드 서비스에 로그인 연동을 위해, (1) 로그인아이디 등의 정보를 암호화하고, (2) 데이터 정합성 확인을 위한 HASH 값을 생성한 뒤, (3) 생성된 값들을 HTML FORM POST 전송을 통해 전달하는 내용을 설명합니다.<br>

### 2. 기본정보

* 사이트 URL, 로그인 URL, AES 암호화 키는 메일을 통해 *별도 제공*합니다.
* 인코딩: UTF-8<br>

### 3. 유의사항

* 사이트 URL이 `malgnlms.com`으로 끝나는 경우, 임시 도메인을 사용 중인 상태입니다. 오픈 후에는 임시 도메인 사용이 불가하므로, 프로그램 상에서도 반드시 정식 도메인으로 변경이 필요합니다.
* 작성하신 로그인 연동 프로그램이 위치한 URL을 담당자에게 반드시 알려주셔야 합니다.<br>

### 4. 암호화 방식

#### 4.1. SHA-256

* 데이터 정합성 확인을 위한 HASH 값 생성에 사용합니다.
* 문자열은 UTF-8 인코딩을 기본으로 하며, 최종 인코딩된 문자열의 길이는 64 bytes입니다.
* 코드 예시

  ```java
  public String sha256(String str) throws Exception {
  	String algorithm = "SHA-256";
  	String charset = "UTF-8";
  	StringBuffer sb = new StringBuffer();

  	MessageDigest di = MessageDigest.getInstance(algorithm.toUpperCase());
  	di.update(new String(str).getBytes(charset));
  	byte[] md5Code = di.digest();
  	for (int i=0;i<md5Code.length;i++) {
  		String md5Char = String.format("%02x", 0xff&(char)md5Code[i]);
  		sb.append(md5Char);
  	}

  	return sb.toString();
  }
  ```

#### 4.2. AES-128

* 회원정보 암호화에 사용하며, 상세 암호화 방식은 다음과 같습니다.

  | Mode     | ECB             |
  | -------- | --------------- |
  | Encoding | Base64          |
  | Padding  | PKCS5Padding    |
  | IV       | 사용 안 함 (ECB 모드) |
  | Charset  | UTF-8           |
  | Key      | *\[별도 제공]*      |
* 코드 예시

  ```java
  public String encrypt(String value, String key) throws Exception {
  	if (value == null) value = "";
  	if (key == null || key.equals(""))
  		throw new Exception("The key can not be null or an empty string.");

  	Cipher cipher = Cipher.getInstance("AES");
  	SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
  	cipher.init(Cipher.ENCRYPT_MODE, keySpec);
  	byte[] encValue = cipher.doFinal(value.getBytes("UTF-8"));

  	return new String(new BASE64Encoder().encode(encValue));
  }
  ```
* 아래 툴을 이용해 암호화된 값과 복호화된 값을 테스트하실 수 있습니다. (기본설정 사용)\
  <https://www.devglan.com/online-tools/aes-encryption-decryption><br>

### 5. 데이터 정합성 확인용 HASH 값 생성 규칙

* 원문은 \[로그인아이디(문자열) + 암호화 키(문자열) + 오늘날짜(yyyyMMdd)]로 구성합니다.
* 구성한 원문을 SHA-256으로 암호화 해 HASH 값을 생성합니다.
* 예시) 암호화 키가 abcdefg123456789인 사이트로, 2021년 1월 14일에 로그인아이디가 12345인 회원

  | 원문     | 12345abcdefg12345678920210114                                    |
  | ------ | ---------------------------------------------------------------- |
  | HASH 값 | b2fbca7f3bb4995402269c14cf3d07fd82b1501ae195d55af60263220f3a4c55 |

### 6. 폼 데이터

* AES-128 암호화는 회원정보 및 returl에 적용하고, 암호화 여부 및 ek HASH에는 적용하지 않습니다.
* 회원소속을 배정하는 dept\_id 또는 dept\_cd를 사용하려면 ‘회원소속관리’에 소속정보를 미리 등록해야 합니다.
* dept\_id는 맑은이러닝에서 발급한 숫자로 된 소속아이디를 사용하며, dept\_cd는 임의의 문자열로 된 코드를 맑은이러닝에 등록한 후 사용할 수 있습니다.
* 등록되지 않은 소속아이디 또는 소속코드가 전달 될 경우, 회원이 신규로 가입되는 경우 ‘사이트정보’에 설정 된 ‘기본 회원소속’으로 가입 후 로그인이 진행되며, 기존에 가입된 회원일 경우 정보수정 없이 로그인이 진행됩니다.
* 예시

<table><thead><tr><th width="118">Parameter</th><th width="107" align="center">필수</th><th width="121" align="center">AES 암호화</th><th>설명</th></tr></thead><tbody><tr><td>encrypted</td><td align="center">Required</td><td align="center">No</td><td>회원정보에 대한 AES-128 암호화 적용 여부 (<code>Y</code> 또는 <code>N</code>)<br>• <code>Y</code>: 암호화 적용<br>• <code>N</code>: 암호화 미적용<br>※ 테스트 시 <code>N</code> 사용 가능하나, 보안을 위해 실제 서비스에서는 가급적 <code>Y</code>로 고정합니다.</td></tr><tr><td>ek</td><td align="center">Required</td><td align="center">No</td><td>64바이트 정합성 검증 HASH 값</td></tr><tr><td>login_id</td><td align="center">Required</td><td align="center">Yes</td><td>로그인 아이디</td></tr><tr><td>user_nm</td><td align="center">Required</td><td align="center">Yes</td><td>회원명</td></tr><tr><td>returl</td><td align="center">Optional</td><td align="center">Yes</td><td>로그인 연동 이후 이동할 페이지 URL (보안 정책상 사이트 외부 URL은 무시되며, 미지정 시 메인페이지로 이동)</td></tr><tr><td>email</td><td align="center">Optional</td><td align="center">Yes</td><td>이메일 주소</td></tr><tr><td>mobile</td><td align="center">Optional</td><td align="center">Yes</td><td>휴대전화번호 (<code>000-0000-0000</code> 형식)</td></tr><tr><td>zipcode</td><td align="center">Optional</td><td align="center">Yes</td><td>우편번호</td></tr><tr><td>new_addr</td><td align="center">Optional</td><td align="center">Yes</td><td>도로명 주소</td></tr><tr><td>addr_dtl</td><td align="center">Optional</td><td align="center">Yes</td><td>상세 주소</td></tr><tr><td>dept_id</td><td align="center">Optional</td><td align="center">Yes</td><td>회원소속관리에 등록되어 있는 소속의 아이디</td></tr><tr><td>dept_cd</td><td align="center">Optional</td><td align="center">Yes</td><td>회원소속관리에 등록되어 있는 소속의 소속코드</td></tr><tr><td>gender</td><td align="center">Optional</td><td align="center">Yes</td><td>성별 (<code>1</code>: 남자 / <code>2</code>: 여자, 미지정 시 기본값 <code>1</code>)</td></tr><tr><td>birthday</td><td align="center">Optional</td><td align="center">Yes</td><td>생년월일 (<code>yyyyMMdd</code> 형식)</td></tr><tr><td>etc1 ~ etc5</td><td align="center">Optional</td><td align="center">Yes</td><td>기타 필드 값 (최대 5개까지 지정 가능)</td></tr></tbody></table>

* 예시

<table><thead><tr><th width="129">Parameter</th><th width="286">원문</th><th>실제 전송할 Form Data</th></tr></thead><tbody><tr><td><strong>encrypted</strong></td><td>Y</td><td>Y</td></tr><tr><td><strong>ek</strong></td><td>12345abcdefg12345678920210114</td><td>b2fbca7f3bb4995402269c14cf3d07fd82b1501ae195d55af60263220f3a4c55</td></tr><tr><td><strong>returl</strong></td><td>/main/index.jsp</td><td>p60tYVtdHTU+xtY9Aba0bg==</td></tr><tr><td><strong>login_id</strong></td><td>12345</td><td>Np/cqbbEZ0MN3MHaiS3Fig==</td></tr><tr><td><strong>user_nm</strong></td><td>김맑은</td><td>+GWG63YY+DAITXR1BFTTGQ==</td></tr><tr><td><strong>email</strong></td><td>kim.malgn@example.com</td><td>1VKAVFLFmVKnRz73ZWUW4tHtiPRfUjSQQo/szdSzuAl=</td></tr><tr><td><strong>mobile</strong></td><td>010-0000-1234</td><td>0Myc3qtoly6ccCB5V/Oihw==</td></tr><tr><td><strong>zipcode</strong></td><td>08793</td><td>q2IKR4RL1XKr9H14PsbfnQ==</td></tr><tr><td><strong>new_addr</strong></td><td>서울 관악구 인헌길 30</td><td>dgH4YjRhzKAUlyplDr4RNvlbYsdK1bpguxOje/R4dvo=</td></tr><tr><td><strong>addr_dtl</strong></td><td>2층(봉천동)</td><td>b0c28rKlyfb6uHE98Bg+KVOX7pz0JcPAWXIZuHwDac4=</td></tr><tr><td><strong>dept_id</strong></td><td>2</td><td>+b9d3ca3zsUOcHdV1B1bAg==</td></tr><tr><td><strong>gender</strong></td><td>1</td><td>8IUt03aZMTXCiiElikLVTQ==</td></tr></tbody></table>

```html
<form name="form1" method="POST" action="<https://www.example.com/member/slogin.jsp>">
<input type="hidden" name="encrypted" value="Y">
<input type="hidden" name="ek" value="b2fbca7f3bb4995402269c14cf3d07fd82b1501ae195d55af60263220f3a4c55">
<input type="hidden" name="returl" value="p6OtYVtdHTU+xtY9Aba0bg==">
<input type="hidden" name="login_id" value="Np/cqbbEZ0MN3MHaiS3Fig==">
<input type="hidden" name="user_nm" value="+GWG63YY+DAlTXR1BFTTGQ==">
<input type="hidden" name="email" value="1VKAvFLFmVKnRz73ZWUW4tHtiPRfUjSQQo/szdSzuAI=">
<input type="hidden" name="mobile" value="0Myc3qtoIy6ccCB5V/Oihw==">
<input type="hidden" name="zipcode" value="q2IKR4RL1XKr9H14PsbfnQ==">
<input type="hidden" name="new_addr" value="dgH4YjRhzKAUlyplDr4RNvlbYsdK1bpguxOje/R4dvo=">
<input type="hidden" name="addr_dtl" value="b0c28rKlyfb6uHE98Bg+KVOX7pz0JcPAWXIZuHwDac4=">
<input type="hidden" name="dept_id" value="+b9d3ca3zsUOcHdV1B1bAg==">
<input type="hidden" name="gender" value="8lUt03aZMTXCiiElikLVTQ==">
</form>

```

### 7. 샘플 예제 코드

#### 7.1. JSP

```java
<%@ page contentType="text/html; charset=utf-8" %><%@ page import="java.text.SimpleDateFormat,java.util.GregorianCalendar,java.security.MessageDigest,javax.crypto.Cipher,javax.crypto.spec.SecretKeySpec,sun.misc.BASE64Encoder" %><%!

public String sha256(String str) throws Exception {
	String algorithm = "SHA-256";
	String charset = "UTF-8";
	StringBuffer sb = new StringBuffer();

	MessageDigest di = MessageDigest.getInstance(algorithm.toUpperCase());
	di.update(new String(str).getBytes(charset));
	byte[] md5Code = di.digest();
	for (int i=0;i<md5Code.length;i++) {
		String md5Char = String.format("%02x", 0xff&(char)md5Code[i]);
		sb.append(md5Char);
	}

	return sb.toString();
}

public String encrypt(String value, String key) throws Exception {
	if (value == null) value = "";
	if (key == null || key.equals(""))
		throw new Exception("The key can not be null or an empty string.");

	Cipher cipher = Cipher.getInstance("AES");
	SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
	cipher.init(Cipher.ENCRYPT_MODE, keySpec);
	byte[] encValue = cipher.doFinal(value.getBytes("UTF-8"));

	return new String(new BASE64Encoder().encode(encValue));
}

%><%

//변수
String ekey = "***[암호화 키]***"
String siteUrl = "***[사이트 URL]***";
String ssoUrl = siteUrl + "/member/slogin.jsp";
String retUrl = request.getParameter("returl");
String mode = request.getParameter("mode");

//각 모드별 페이지로 내부 이동해주세요. 고객사별 상황에 따라 사용유무를 결정할 수 있습니다.
if("logout".equals(mode)) {
	//내부 로그아웃 페이지로 이동시켜주세요.
	response.sendRedirect("***[내부 로그아웃 페이지 URL]***");
	return;

} else if("join".equals(mode)) {
	//내부 회원가입 페이지로 이동시켜주세요.
	response.sendRedirect("***[내부 회원가입 페이지 URL]***");
	return;

} else if("find".equals(mode)) {
	//내부 아이디비밀번호찾기 페이지로 이동시켜주세요.
	response.sendRedirect("***[내부 아이디비밀번호찾기 페이지 URL]***");
	return;

}

//로그인 상태일 때 아이디, 이름, 이메일등의 개인정보를 세션이나 DB에서 가져옵니다.
HttpSession sessions = request.getSession();
String userId = (String)session.getAttribute("userId"); //회원로그인아이디
String userName = (String)session.getAttribute("userName"); //회원명

//로그인이 안되어 있을 경우 내부 로그인 페이지로 이동해주세요.
if("".equals(userId)) {

	//내부 로그인 페이지로 이동시켜주세요. 
	//단, 로그인후에는 반드시 다시 이 페이지로 돌아오도록 설정 부탁드립니다.
	response.sendRedirect("***[내부 로그인 페이지 URL]***"); 
	return;
}

//해쉬값(ek)는 사번 + 암호화키 + 날짜(yyyyMMdd)의 조합을 SHA-256으로 암호화
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String today = sdf.format((new GregorianCalendar()).getTime());
String ek = sha256(userId + ekey + today);

%><!DOCTYPE html>
<html>
<meta charset="utf-8">
<body onload="document.forms['form1'].submit();">
<form name="form1" method="POST" action="<%= ssoUrl %>">
<input type="hidden" name="encrypted" value="Y">
<input type="hidden" name="ek" value="<%= ek %>">
<input type="hidden" name="returl" value="<%= encrypt(retUrl, ekey) %>">
<input type="hidden" name="login_id" value="<%= encrypt(userId, ekey) %>">
<input type="hidden" name="user_nm" value="<%= encrypt(userName, ekey) %>">
</form>
</body>
</html>

```

#### 7.2. PHP

```php
<?php

function encrypt($text, $key) {
	$pad = 16 - (strlen($text) % 16);
	$text = $text . str_repeat(chr($pad), $pad);
	return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_ECB));
}

session_start();
header("Content-Type: text/html; charset=UTF-8");

//변수
$ekey = "***[암호화 키]***";
$siteUrl = "***[사이트 URL]***";
$ssoUrl = $siteUrl . "/member/slogin.jsp";
$retUrl = $_GET["returl"];
$mode = $_GET["mode"];

//각 모드별 페이지로 내부 이동해주세요. 고객사별 상황에 따라 사용유무를 결정할 수 있습니다.
if("logout" == $mode) {
	//내부 로그아웃 페이지로 이동시켜주세요.
	header("Location: ***[내부 로그아웃 페이지 URL]***");
	exit;

} else if("join" == $mode) {
	//내부 회원가입 페이지로 이동시켜주세요.
	header("Location: ***[내부 회원가입 페이지 URL]***");
	exit;

} else if("find" == $mode) {
	//내부 아이디비밀번호찾기 페이지로 이동시켜주세요.
	header("Location: ***[내부 아이디비밀번호찾기 페이지 URL]***");
	exit;

}

//로그인 상태일 때 아이디, 이름, 이메일등의 개인정보를 세션이나 DB에서 가져옵니다.
$loginId = "12345";
$userNm = "김맑은";

//로그인이 안되어 있을 경우 내부 로그인 페이지로 이동해주세요.
if("" == $loginId) {

	//내부 로그인 페이지로 이동시켜주세요. 
	//단, 로그인후에는 반드시 다시 이 페이지로 돌아오도록 설정 부탁드립니다.
	header("Location: ***[내부 로그인 페이지]***");
	exit;
}

//해쉬값(ek)는 사용자아이디 + 비밀키 + 날짜(yyyyMMdd)의 조합을 SHA256 으로 암호화 해주시기 바랍니다.
$today = date("Ymd");
$ek = hash("sha256", $loginId . $ekey . $today);

?><!DOCTYPE html>
<html>
<meta charset="utf-8">
<body onload="document.forms['form1'].submit();">
<form name="form1" method="POST" action="<?php echo($ssoUrl) ?>">
<input type="hidden" name="encrypted" value="Y">
<input type="hidden" name="ek" value="<?php echo($ek) ?>">
<input type="hidden" name="returl" value="<?php echo(encrypt($retUrl, $ekey)) ?>">
<input type="hidden" name="login_id" value="<?php echo(encrypt($loginId, $ekey)) ?>">
<input type="hidden" name="user_nm" value="<?php echo(encrypt($userNm, $ekey)) ?>">
</form>
</body>
</html>

```

#### 7.3. ASP

* **ASP는 서버 환경에 따라 AES 암호화 함수가 다르거나, AES 암호화가 불가능할 수 있습니다.**

```csharp
<%

'변수
Dim ekey, site_url, sso_url, ret_url, mode, login_id, user_nm, today

ekey = "***[암호화 키]***"
site_url = "***[사이트 URL]***"
sso_url = site_url & "/member/slogin.jsp"
ret_url = request("returl")
mode = request("mode")

'각 모드별 페이지로 내부 이동해주세요. 고객사별 상황에 따라 사용유무를 결정할 수 있습니다.
If StrComp("logout", mode) = 0 Then

	'내부 로그아웃 페이지로 이동시켜주세요.
	Response.Redirect "***[내부 로그아웃 페이지 URL]***"
	Response.End

Else If StrComp("join", mode) = 0 Then

	'내부 회원가입 페이지로 이동시켜주세요.
	Response.Redirect "***[내부 회원가입 페이지 URL]***"
	Response.End

Else If StrComp("find", mode) = 0 Then

	'내부 아이디비밀번호찾기 페이지로 이동시켜주세요.
	Response.Redirect "***[내부 아이디비밀번호찾기 페이지 URL]***"
	Response.End

End If

'로그인 상태일 때 아이디, 이름, 이메일등의 개인정보를 세션이나 DB에서 가져옵니다.
login_id = Session("login_id")
user_nm = Session("user_nm")

'로그인이 안되어 있을 경우 내부 로그인 페이지로 이동해주세요.
If login_id == "" Then

	'내부 로그인 페이지로 이동시켜주세요. 
	'단, 로그인후에는 반드시 다시 이 페이지로 돌아오도록 설정 부탁드립니다.
	Response.Redirect "***[내부 로그인 페이지]***"
	Response.End

End If

'해쉬값(ek)는 사용자아이디 + 비밀키 + 날짜(yyyyMMdd)의 조합을 SHA256 으로 암호화 해주시기 바랍니다.
Set SHA = New CryptSHA256
today = Replace(Date(), "-", "")
ek = SHA.SHA256(login_id & ekey & today)

%><!DOCTYPE html>
<html>
<meta charset="utf-8">
<body onload="document.forms['form1'].submit();">
<form name="form1" method="POST" action="<%= sso_url %>">
<input type="hidden" name="encrypted" value="N">
<input type="hidden" name="ek" value="<%= ek %>">
<input type="hidden" name="returl" value="<%= ret_url %>">
<input type="hidden" name="login_id" value="<%= login_id %>">
<input type="hidden" name="user_nm" value="<%= user_nm %>">
</form>

```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://malgn.gitbook.io/cloud/request-forms/addon-services/sso-integration-guide.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
