[실습] 스프링 시큐리티 OAuth2 Login 1
목차
- [이론] 스프링 시큐리티 1
- [OAuth] 스프링 시큐리티 OAuth
- [실습] 스프링 시큐리티 OAuth2 Login 1
- [실습] 스프링 시큐리티 OAuth2 Login 2
- [추가] 스프링 시큐리티 OAuth 로그인 처리 방법 1
- [추가] 스프링 시큐리티 OAuth 로그인 처리 방법 2
사전작업
Dependency 추가
dependencies {
// 의존성 추가
implementation 'org.springframework.security:spring-security-oauth2-client'
}
OAuth 서비스 등록
google, naver, kakao, facebook 등 여러개가 있지만 naver를 기준으로 설명을 할 예정이다
위 링크에서 사용할 애플리케이션 등록을 해야한다
- 애플리케이션 이름, 필요정보 등록
제공정보 선택에서는 DB에 저장하기 원하는 정보를 선택하면 된다 지금은 이름과 이메일만 저장을 할 예정이기에 두개만 선택을 하였다
- 서비스 주소 추가
첫번째 서비스 URL은 내가 운영중인 서비스 정보이다 현재는 local에서 돌리기에 위와 같은 주소지만 AWS와 같이 서버를 띄운다면 해당 주소를 작성하면 된다
두번째는 로그인 후 인가 코드를 받아올 URL이다 우선은 이렇게 설정 후 추후 자세히 알아보자
- Client Id, Client Secret
등록을 마무리하게 되면 두가지 정보를 알 수 있는데 뒤에서 사용해야 하기에 따로 적어두기로 하자
application-auth.yml
spring:
security:
oauth2:
client:
registration:
google:
client-id:
client-secret:
scope: email, profile
naver:
client-id:
client-secret:
client-name: naver
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8080/login/oauth2/code/naver
provider:
naver:
authorization-uri: https://nid.naver.com/oauth2.0/authorize
token-uri: https://nid.naver.com/oauth2.0/token
user-info-uri: https://openapi.naver.com/v1/nid/me
user-name-attribute: response
# 출력결과를 보면 정보는 response하위로 나타나는 것을 볼수 있다
docs를 보게되면 4개의 링크를 볼수 있게 된다 위 yml 파일과 비교해서 보면 아래와 같다
- 네이버 로그인 연동 URL 생성하기 (authorization-uri)
- 네이버 로그인 연동 결과 Callback 정보 (redirect-uri)
- 접근 토큰 발급 요청 (token-uri)
- 접근 토큰을 이용하여 프로필 API 호출하기 (user-info-uri)
client-id/secret은 등록 시 발급받은 정보를 넣으면 된다
나머지 정보는 동일하게 작성하자
위 파일에 구글관련해서도 작성을 해두었는데 구글은 네이버와 달리 Spring Security에서 Provider를 제공하기 때문에 registration만 작성을 해주면 된다
Spring Security는 구글 외에도 깃헙, 페이스북, 옥타를 추가로 제공한다 - 이 부분에 대해서는 추후 자세히 알아볼 예정이다
(org.springframework.security.config.oauth2.client#CommonOAuth2Provider)
- 추가 구글로그인여기서 구글에 대한 정보를 등록하면 된다 네이버와 같이 애플리케이션 등록 및 정보를 작성하면 된다
이로써 사전 작업은 모두 끝났다
설정작업을 하고 로그인한 사용자에 대해서 DB에 저장하는 로직을 작성해보자
Security Config
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//추가
http.oauth2Login() //OAuth2 Login Config 시작
.userInfoEndpoint() // OAuth2 Login 성공시 사용자 정보 가져올 설정
.userService(customOauth2UserService); // OAuth2 Login 성공시 후 작업
return http.build();
}
OAuthService
우선 우리가 만든 CustomUser에 OAuth2User도 관리할 수 있게 추가를 해주자
public class CustomUser implements UserDetails, OAuth2User {
private final User user;
//추가
private OAuth2UserInfo oauth2Oauth2UserInfo;
public CustomUser(User user) {
this.user = user;
}
//추가
public CustomUser(User user, OAuth2UserInfo oauth2Oauth2UserInfo) {
this.user = user;
this.oauth2Oauth2UserInfo = oauth2Oauth2UserInfo;
}
@Override
public Map<String, Object> getAttributes() {
return oauth2Oauth2UserInfo.getAttributes();
}
@Override
public String getName() {
return oauth2Oauth2UserInfo.getName();
}
}
@Component
@RequiredArgsConstructor
public class CustomOauth2UserService extends DefaultOAuth2UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private static final String PASSWORD = "password";
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
String registrationId = userRequest.getClientRegistration().getRegistrationId();
// userRequest를 통해 사용자 정보 가져오기
OAuth2UserInfo oAuth2UserInfo = OAuthAttributes.of(registrationId, super.loadUser(userRequest).getAttributes());
User user = saveUser(oAuth2UserInfo);
return new CustomUser(user, oAuth2UserInfo);
}
private User saveUser(OAuth2UserInfo oauth2Oauth2UserInfo) {
String email = oauth2Oauth2UserInfo.getEmail();
String name = oauth2Oauth2UserInfo.getName();
String password = passwordEncoder.encode(PASSWORD + UUID.randomUUID().toString().substring(0, 8));
User user = new User(email, name, password);
// 유저정보가 없으면 저장 있으면 혹시라도 변경된 정보 업데이트
userRepository.findUserByEmail(email)
.ifPresentOrElse(
entity -> entity.update(user.getEmail(), user.getName()),
() -> userRepository.save(user)
);
return user;
}
}
아래 코드들은 로그인 한 유저 정보 매핑을 위한 클래스들이다
@Getter
@RequiredArgsConstructor
public class OAuth2UserInfo {
private final Map<String, Object> attributes;
private final String providerId;
private final String name;
private final String email;
}
public enum OAuthAttributes {
GOOGLE("google", attributes -> new OAuth2UserInfo(
attributes,
attributes.get("sub").toString(),
attributes.get("name").toString(),
attributes.get("email").toString()
)),
NAVER("naver", attributes -> {
Map<String, Object> response = (Map<String, Object>) attributes.get("response");
return new OAuth2UserInfo(
response,
response.get("id").toString(),
response.get("name").toString(),
response.get("email").toString()
);
}),
;
private final String registrationId;
private final Function<Map<String, Object>, OAuth2UserInfo> of;
OAuthAttributes(String registrationId, Function<Map<String, Object>, OAuth2UserInfo> of) {
this.registrationId = registrationId;
this.of = of;
}
public static OAuth2UserInfo of(String providerId, Map<String, Object> attributes) {
return Arrays.stream(values())
.filter(provider -> provider.registrationId.equals(providerId))
.findFirst()
.orElseThrow(IllegalArgumentException::new)
.of.apply(attributes);
}
}
이것으로 OAuth2로그인 까지 알아보았다
하지만 이것만 하기에는 앞서 JWT 발행 후 Front로 전달하는 과정이 OAuth Login에는 없어 Front와 통신간에 문제가 생긴다
다음 포스트에서는 OAuth Login 후 JWT 발행에 대해서 알아보자