Skip to content

Commit

Permalink
Merge pull request #1 from urinaner/develop
Browse files Browse the repository at this point in the history
sejong-auth v1
  • Loading branch information
urinaner authored Nov 5, 2024
2 parents e243ebf + 8a53824 commit 5894430
Show file tree
Hide file tree
Showing 10 changed files with 423 additions and 7 deletions.
117 changes: 117 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# SejongAuth

SejongAuth는 세종대학교 통합 로그인 페이지에 접근하여 사용자 인증과 프로필 정보를 가져오는 Java 기반의 패키지입니다. `Sj`를 통해 세종대학교 포털에 간편하게 로그인하고 사용자의 학적 정보를 조회할 수 있습니다.

## 주요 기능

- **로그인 기능**: `LoginReq`를 사용하여 세종대학교 포털에 로그인하고 `JSESSIONID`를 획득합니다.
- **프로필 조회 기능**: 로그인 성공 시 `ProfileService`를 통해 사용자의 학적 정보를 가져옵니다.

---

## 설치 방법

gradle
```properties
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.urinaner:sejong-auth:Tag'
}
```
maven
```yaml
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>

<dependency>
<groupId>com.github.urinaner</groupId>
<artifactId>sejong-auth</artifactId>
<version>Tag</version>
</dependency>
```


---

## 프로젝트 구조

```
SejongAuth/
├── src/
│ ├── main/
│ │ ├── java/org/yj/sejongauth/controller/Sj.java
│ │ ├── java/org/yj/sejongauth/domain/AuthService.java
│ │ ├── java/org/yj/sejongauth/domain/LoginReq.java
│ │ ├── java/org/yj/sejongauth/domain/ProfileRes.java
│ │ └── java/org/yj/sejongauth/domain/ProfileService.java
│ └── test/
│ └── java/org/yj/sejongauth/controller/AuthControllerTest.java
└── build.gradle
```

---

## 사용법

### 1. `LoginRequestDto` 생성

로그인 요청 객체를 생성합니다.

```java
LoginRequestDto loginRequestDto = new LoginRequestDto("userId", "password");
```

### 2. 로그인 및 프로필 조회

`Sj.login()` 메서드를 사용하여 로그인 및 프로필 정보를 조회할 수 있습니다.

```java
ProfileRes profile = Sj.login(loginReq);
System.out.println("User profile: " + profile);
```

---

## 예제

아래는 `Sj.login` 메서드를 호출하여 간단히 로그인과 프로필 조회를 수행하는 예제입니다.

```java

public class Main {
public static void main(String[] args) {
LoginReq loginReq = new LoginReq("testUser", "testPassword");

try {
ProfileRes profile = Sj.login(loginReq);
System.out.println("Login successful. User profile: " + profile);
} catch (RuntimeException e) {
System.err.println("Login failed: " + e.getMessage());
}
}
}
```

---

## 테스트

`AuthService` 클래스에서 로그인에 대한 테스트를 실행할 수 있습니다.


---

## 주의사항

- **네트워크 연결**: 이 프로그램은 네트워크 연결이 필요합니다.
- **예외 처리**: 네트워크 오류, 로그인 실패 등의 상황에서 `RuntimeException`이 발생할 수 있습니다.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencyManagement {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.2'
implementation 'org.jsoup:jsoup:1.14.3'
compileOnly 'org.projectlombok:lombok:1.18.26'
annotationProcessor 'org.projectlombok:lombok:1.18.26'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
7 changes: 0 additions & 7 deletions src/main/java/org/yj/sejongauth/Test.java

This file was deleted.

21 changes: 21 additions & 0 deletions src/main/java/org/yj/sejongauth/controller/Sj.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.yj.sejongauth.controller;

import org.yj.sejongauth.domain.AuthService;
import org.yj.sejongauth.domain.LoginReq;
import org.yj.sejongauth.domain.ProfileRes;
import org.yj.sejongauth.domain.ProfileService;

public class Sj {

private static final AuthService authService = new AuthService();
private static final ProfileService PROFILE_SERVICE = new ProfileService();

public static ProfileRes login(LoginReq loginReq) {
if (authService.authenticate(loginReq)) {
String jsessionId = authService.getJsessionId();
return PROFILE_SERVICE.fetchUserProfile(jsessionId);
} else {
throw new RuntimeException("인증에 실패하였습니다.");
}
}
}
97 changes: 97 additions & 0 deletions src/main/java/org/yj/sejongauth/domain/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.yj.sejongauth.domain;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;

public class AuthService {
private String jsessionId;
private final String SJ_LOGIN_URL = "https://classic.sejong.ac.kr/userLogin.do";
private final String SJ_POTAL_URL = "https://classic.sejong.ac.kr";
private final String INVALID_AUTH = "인증이 실패하였습니다.";
private final String INVALID_SESSION = "SESSION_ID 가져오는 것을 실패하였습니다.";
private final String INVALID_URL = "URL이 유효하지 않습니다.";
private final String CONTAINS_HTML = "로그인 정보가 올바르지 않습니다.";

public boolean authenticate(LoginReq loginReq) {
try {
fetchJsessionId();
return attemptLogin(loginReq);
} catch (IOException e) {
throw new RuntimeException(INVALID_AUTH);
}
}

void fetchJsessionId() throws IOException {
try {
URI uri = new URI(SJ_POTAL_URL);
HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
connection.setRequestMethod("GET");
connection.connect();

Map<String, List<String>> headers = connection.getHeaderFields();
List<String> cookies = headers.get("Set-Cookie");

if (cookies != null) {
for (String cookie : cookies) {
if (cookie.startsWith("JSESSIONID")) {
jsessionId = cookie.split(";")[0].split("=")[1];
break;
}
}
}

if (jsessionId == null) {
throw new RuntimeException(INVALID_SESSION);
}
} catch (URISyntaxException e) {
throw new RuntimeException(INVALID_URL);
}
}

private boolean attemptLogin(LoginReq loginReq) throws IOException {
try {
URI uri = new URI(SJ_LOGIN_URL);
HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Cookie", "JSESSIONID=" + jsessionId);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setDoOutput(true);

String postData = "userId=" + loginReq.getUserId() + "&password=" + loginReq.getPassword();
try (OutputStream os = connection.getOutputStream()) {
byte[] input = postData.getBytes("utf-8");
os.write(input, 0, input.length);
}

int responseCode = connection.getResponseCode();
System.out.println(responseCode);
String responseMessage = readResponse(connection);
System.out.println(responseMessage);
return responseCode == 302 && !responseMessage.contains(CONTAINS_HTML);
} catch (URISyntaxException e) {
throw new RuntimeException(INVALID_URL);
}
}

private String readResponse(HttpURLConnection connection) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
}
}

public String getJsessionId() {
return jsessionId;
}
}
19 changes: 19 additions & 0 deletions src/main/java/org/yj/sejongauth/domain/LoginReq.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.yj.sejongauth.domain;

public class LoginReq {
private final String userId;
private final String password;

public LoginReq(String userId, String password) {
this.userId = userId;
this.password = password;
}

public String getUserId() {
return userId;
}

public String getPassword() {
return password;
}
}
31 changes: 31 additions & 0 deletions src/main/java/org/yj/sejongauth/domain/ProfileRes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.yj.sejongauth.domain;

public class ProfileRes {
private final String major;
private final String studentCode;
private final String name;
private final int gradeLevel;
private final String userStatus;
private final int completedSemesters;
private final int verifiedSemesters;

public ProfileRes(String major, String studentCode, String name,
int gradeLevel, String userStatus,
int completedSemesters, int verifiedSemesters) {
this.major = major;
this.studentCode = studentCode;
this.name = name;
this.gradeLevel = gradeLevel;
this.userStatus = userStatus;
this.completedSemesters = completedSemesters;
this.verifiedSemesters = verifiedSemesters;
}

public String getMajor() { return major; }
public String getStudentCode() { return studentCode; }
public String getName() { return name; }
public int getGradeLevel() { return gradeLevel; }
public String getUserStatus() { return userStatus; }
public int getCompletedSemesters() { return completedSemesters; }
public int getVerifiedSemesters() { return verifiedSemesters; }
}
51 changes: 51 additions & 0 deletions src/main/java/org/yj/sejongauth/domain/ProfileService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.yj.sejongauth.domain;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public class ProfileService {
private final String PROFILE_URL = "http://classic.sejong.ac.kr/userCertStatus.do?menuInfoId=MAIN_02_05";
private final String FAIDED_PROFILE = "정보 조회에 실패하였습니다.";

public ProfileRes fetchUserProfile(String jsessionId) {
try {
URL url = new URL(PROFILE_URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Cookie", "JSESSIONID=" + jsessionId);

Document doc = Jsoup.parse(readResponse(connection));
return parseProfileFromHtml(doc);
} catch (IOException e) {
throw new RuntimeException(FAIDED_PROFILE);
}
}

private String readResponse(HttpURLConnection connection) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
}
}

private ProfileRes parseProfileFromHtml(Document document) {
String major = document.select("div.contentWrap li dl dd").get(0).text();
String studentCode = document.select("div.contentWrap li dl dd").get(1).text();
String name = document.select("div.contentWrap li dl dd").get(2).text();
int gradeLevel = Integer.parseInt(document.select("div.contentWrap li dl dd").get(3).text().split(" ")[0]);
String userStatus = document.select("div.contentWrap li dl dd").get(4).text();
int completedSemesters = Integer.parseInt(document.select("div.contentWrap li dl dd").get(5).text().split(" ")[0]);
int verifiedSemesters = Integer.parseInt(document.select("div.contentWrap li dl dd").get(6).text().split(" ")[0]);

return new ProfileRes(major, studentCode, name, gradeLevel, userStatus, completedSemesters, verifiedSemesters);
}
}
Loading

0 comments on commit 5894430

Please sign in to comment.