[OAuth] 슬랙 로그인 구현하기
목차
1. OpenID vs OAuth vs OIDC
2. OAuth 인증 과정
3. 로그인 동의 화면
4. 로그인 API 구현하기
`줍줍` 프로젝트는 슬랙을 이용해 로그인하는 기능을 제공할 예정이다. 해당 기능을 개발하는 과정을 이야기 해보려 한다.
1. OpenID vs OAuth 📚
OpenID, OAuth는 인증, 인가에서 사용되는 HTTP 기반 프로토콜이다.
- OpenID -> 인증; authentication: 사용자의 신원을 확인
- OAuth -> 인가; authorization: (신원이 확인된 사용자에게) 자원에 접근할 수 있는 권한 부여
더 자세한 내용 👉 https://yeonyeon.tistory.com/264
🤔 어떤 것을 사용할까?
현재 슬랙에서는 OIDC를 권장하고 있다. (슬랙을 포함한 많은 곳에서 OpenID라고 표기하는데 현재 대부분의 OpenID는 OpenID Connect의 표준을 따르고 있다.) 그럼에도 `줍줍`에서는 OAuth를 사용하기로 했다. 그 이유는 액세스 접근 권한의 요청 내용 때문이었다.
좌측은 OIDC, 우측은 OAuth에서 액세스 권한을 요청하는 화면이다. 둘 다 최대 스코프로 지정해둔 것인데 OAuth가 액세스 권한의 범위를 더 크게 지정할 수 있다. `줍줍`에서는 사용자 정보의 일부와 메시지 내용을 저장하고 있는데 권한을 크게 동의받는 것이 옳다고 판단했다.
2. OAuth 인증 과정 🔑
이 포스팅에서는 Slack의 OAuth를 기준으로 작성해보려 한다.
Slack이 아닌 카카오나 네이버, 구글이어도 OAuth 절차는 비슷하다.
- 사용자: Slack에게 Authorization 요청
- Slack: redirect url로 code 전송 (access token 발급 시 사용)
- Server: 발급받은 code로 access token 발급 요청
- Slack: access token 발급
- Server: 자원(ex: 사용자 정보) 요청
- Slack: 자원 전송
- Server: 6에서 받은 자원을 이용해 로그인 로직 실행 후 User에게 응답
3. 로그인 동의 화면 😡
사용자가 슬랙으로 로그인 버튼을 누르면 위와 같이 사용자에게 동의를 구하는 화면으로 넘어간다. 헌데 여기서 한 가지 의문점이 생겼다. 슬랙에서는 로그인 시도를 할 때마다 동의 화면을 거쳐야했다. 하지만 카카오나 네이버, 구글 같은데는 한 번 동의하면 이후로 로그인을 시도할 때 두번 세번 동의를 구하지 않는다.
이걸 우리가 컨트롤할 수 있는 부분인가? 자료를 찾다가 카카오톡이나 네이버, 구글에서 연결된 서비스를 관리하는 메뉴를 찾았다. 구글로 예를 들면 `내 계정 > 보안 > 타사 액세스 관리 > Google 계정을 통한 로그인`에서 연결된 서비스의 권한을 해제할 수 있다. 이 권한을 해제하면 해당 사이트에서 구글로 다시 로그인하는 경우 동의 화면을 다시 거쳐야한다.
예상 원인은 두 가지가 있다.
1. 슬랙에서 지원하지 않는 기능이다.
카카오, 네이버, 구글처럼 액세스 권한을 관리하는 화면을 슬랙에서는 발견하지 못했다. 어쩌면 슬랙에서는 해당 권한을 저장하지 않을 수도 있겠다는 생각이 들었다.
2. 줍줍은 아직 개발 중인 팀 프로젝트다.
줍줍은 아직 개발 진행중이고 실제로 서비스 되지는 않는다. 로그인 API를 얼마 전에 겨우 개발했을 뿐 실제로 배포된 기능은 아니다. 실제로 서비스되고 있다 또는 사업자 등록을 마쳤다 등 어떤 제약 조건을 통과한 서비스들에 한해서만 권한을 관리할 수도 있겠다는 생각이 든다.
결국은 슬랙에서 지원해주지 않는 기능이라 생각하고 넘어가기로 했다.
이후 변동 사항이 있으면 포스팅에 내용을 추가하겠다.
4. 로그인 API 구현하기 💻
Slack Java 라이브러리를 사용했습니다
1. Slack api에서 Redirect URLs 설정
- https가 적용된 url만 설정 가능
- https 적용하기 👉 https://yeonyeon.tistory.com/253
2. API 생성
- controller 생성
@RestController
@RequestMapping("/api/slack-login")
public class AuthController {
private final AuthService authService;
public AuthController(final AuthService authService) {
this.authService = authService;
}
@GetMapping
public void login(@RequestParam @NotEmpty final String code) {
authService.login(code);
}
}
- service 생성
- clientId, clientSecret은 https://api.slack.com/의 app 정보에서 확인
- redirectUri은 1에서 생성했던, 현재 구현하고 있는 API의 URI
@Service
public class AuthService {
private final String clientId;
private final String clientSecret;
private final String redirectUrl;
private final MethodsClient slackClient;
public AuthService(@Value("${slack.client-id}") final String clientId,
@Value("${slack.client-secret}") final String clientSecret,
@Value("${slack.redirect-url}") final String redirectUrl,
final MethodsClient slackClient) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.redirectUrl = redirectUrl;
this.slackClient = slackClient;
}
public void login(final String code) {
try {
// 1. token 발급 요청
OAuthV2AccessRequest request = OAuthV2AccessRequest.builder()
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(redirectUrl)
.code(code)
.build();
String token = slackClient.oauthV2Access(request)
.getAuthedUser()
.getAccessToken();
// 2. Slack에 사용자 정보 조회 요청
UsersIdentityRequest request = UsersIdentityRequest.builder()
.token(token)
.build();
User user = slackClient.usersIdentity(request)
.getUser();
// 시스템 자체 로그인 로직 수행 (ex: JWT 토큰 또는 세션ID 발급 등)
} catch (IOException | SlackApiException e) {
e.printStackTrace();
}
}
}
참고
- https://www.securew2.com/blog/oauth-vs-openid-which-is-better
- https://stackoverflow.com/questions/1087031/whats-the-difference-between-openid-and-oauth
- https://api.slack.com/authentication/sign-in-with-slack
- https://api.slack.com/legacy/sign-in-with-slack