개요
지난 글에서 OAuth2를 이용해서 로그인하는 방법을 알아보았습니다. 이번에는 OpenID Connect 를 이용해서 로그인을 구현해보도록 하겠습니다.
OIDC란?
OpenID Connect(OIDC)는 OAuth 2.0 프로토콜 위에 구축된 인증 레이어입니다. OAuth 2.0이 권한 부여(Authorization)에 중점을 둔다면, OIDC는 여기에 인증(Authentication) 기능을 추가하여 보다 완벽한 ID 관리 솔루션을 제공합니다.
OIDC의 흐름입니다. 여기서 주목해야할 점은 ID token입니다. OAuth2 방식과 다른점은 유저 정보를 포함하는지 여부라고 할 수 있습니다. 유저정보를 포함하는 것만으로 요청회수를 반으로 줄일 수 있습니다.
OAuth2 | OIDC |
Access Token 발급 요청 | Access Token, ID Token 발급 요청 |
Access Token 발급 | Access Token, ID Token 발급 |
유저 프로필 발급 요청 | |
유저 프로필 발급 |
ID token 예시
{
"iss": "https://auth.example.com",
"sub": "user123",
"aud": "client_id",
"exp": 1516239022,
"iat": 1516235422,
"auth_time": 1516235422,
"nonce": "n-0S6_WzA2Mj",
"name": "John Doe",
"email": "johndoe@example.com"
}
- iss : 토큰 발급자
- sub : 리소스 내 유저 식별자
- aud : Client Id
- exp : 토큰 만료시간
- lat : 토큰 발급 시간
구현
먼저 카카오와 구글에서 OpenID Connect를 사용할 수 있도록 해줍시다.
카카오 로그인을 클릭하고 OpenID Connect 활성화 상태를 ON으로 바꿔줍니다.
구글 범위 설정에서 openid를 추가해주고
application.yaml scope에도 openid를 추가해줍시다.
OidcUserDetails
public class OidcUserDetails extends DefaultOidcUser {
private final User user;
public OidcUserDetails(User user, Collection<? extends GrantedAuthority> authorities, OidcIdToken idToken, OidcUserInfo userInfo, String nameAttributeKey) {
super(authorities, idToken, userInfo, nameAttributeKey);
this.user = user;
}
}
@Service
@RequiredArgsConstructor
public class CustomOidcUserService extends OidcUserService {
private final UserDB db;
@Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
Provider provider = Provider.findByProvider(userRequest.getClientRegistration().getClientName());
DefaultOAuth2User convert = provider.convert(oAuth2User);
String username = provider + "_" + convert.getProviderId();
OidcUser oidcUser = super.loadUser(userRequest);
String userNameAttributeName = userRequest
.getClientRegistration()
.getProviderDetails()
.getUserInfoEndpoint()
.getUserNameAttributeName();
User user = db.findByUsername(username)
.orElseGet(() -> db.save(User.builder()
.username(username)
.role(Role.USER)
.name(convert.getName())
.provider(provider)
.providerId(convert.getProviderId())
.build())
);
return new OidcUserDetails(
user,
Set.of(new SimpleGrantedAuthority(Role.USER.getRoleName())),
oidcUser.getIdToken(),
oidcUser.getUserInfo(),
userNameAttributeName);
}
}
이전 OAuth2UserService와 매우 흡사합니다.
SecurityConfig
.oauth2Login(login -> login
.defaultSuccessUrl("/user?social")
.userInfoEndpoint(userInfo -> userInfo
.oidcUserService(customOidcUserService)
)
.failureHandler((request, response, exception) -> {
exception.printStackTrace();
response.sendRedirect("/login?error");
})
)
.userService() 를 지우고 .oidcUserService에 OidcUserService를 넣어줍시다.
OAuth2AuthorizedClient
@Entity
@Table(name = "oauth2_authorized_client")
@IdClass(OAuth2AuthorizedClientId.class)
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OAuth2AuthorizedClient {
@Id
private String clientRegistrationId;
@Id
private String principalName;
private String accessTokenType;
@Lob
private byte[] accessTokenValue;
private LocalDateTime accessTokenIssuedAt;
private LocalDateTime accessTokenExpiresAt;
private String accessTokenScopes;
@Lob
private byte[] refreshTokenValue;
private LocalDateTime refreshTokenIssuedAt;
@Column(columnDefinition = "timestamp default current_timestamp")
private LocalDateTime createdAt;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OAuth2AuthorizedClientId implements Serializable {
private String clientRegistrationId;
private String principalName;
}
실제로 잘 작동하는지 확인해보도록하겠습니다. 위와 같이 테이블을 만들어줍니다. 이 스키마는 공식문서에서 제공하는 데이터를 기반으로 작성되었습니다.
Security Database Schema :: Spring Security
The standard JDBC implementation of the UserDetailsService (JdbcDaoImpl) requires tables to load the password, account status (enabled or disabled) and a list of authorities (roles) for the user. You can use these as a guideline for defining the schema for
docs.spring.io
@Configuration
public class OAuth2Config {
@Bean
OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository, JdbcTemplate jdbcTemplate) {
return new JdbcOAuth2AuthorizedClientService(jdbcTemplate, clientRegistrationRepository);
}
}
OAuth2AuthorizedCLientService를 JdbcOAuth2AuthorizedClientService로 넣어줍니다.
결과
이제 로그인을 해주면 잘 뜹니다! 만약 Google 로그인 시에 RefreshToken 값이 비어있다면
provider:
google:
authorization-uri: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent
access_type=offline, prompt=consent를 추가하면 refreshToken도 보내줄겁니다.
결론
OAuth2 | OIDC | |
중점 | 인가(Authorization) | 인증(Authentication) |
내용 | AccessToken | AccessToken + ID Token |
용도 | API 이용 권한 | SSO(Single Sign On) 통합 로그인 |
'FrameWork > Spring' 카테고리의 다른 글
[Spring Security][OAuth2][Resource Server] JWT 인증 (0) | 2025.01.13 |
---|---|
[Spring Security] HttpSecurity 메소드를 파보자 (0) | 2025.01.07 |
[Spring Security][OAuth2] OAuth2 Log In 소셜로그인 구현 (1) (0) | 2024.12.19 |
[Spring Security][OAuth2] OAuth2 준비하기 - 카카오로그인 설정 (0) | 2024.12.19 |
[Spring Security][OAuth2] OAuth2 준비하기 - 구글로그인 설정 (1) | 2024.12.18 |