diff --git a/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/config/security/SecurityConfiguration.kt b/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/config/security/SecurityConfiguration.kt index e5f4e26..07adeb3 100644 --- a/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/config/security/SecurityConfiguration.kt +++ b/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/config/security/SecurityConfiguration.kt @@ -30,6 +30,20 @@ import org.springframework.stereotype.Service * 구글과 페이스북 측에서 우리에게 보내는 Request에 액세스 토큰과 사용자 정보등의 OAUth2 정보가 모두 포함되어 있다. * * 하지만 네이버, 카카오는 스프링 부트에서 기본적인 정보를 제공하지 않기 때문에 따로 해당 정보를 제공하는 클래스를 작성해야 한다. + * + * 우리는 OAuth2-Client 라는 라이브러리를 사용하고 있다. + * + * OAuth2-Client 라이브러리는 구글, 페이스북, 깃허브 등의 Provider를 기본적으로 제공해주지만, 네이버 카카오는 제공해주지 않는다. + * + * 이는 각 나라별로 OAuth2 를 지원해주는 서드 파티가 제공하는 attribute 가 모두 다르기 때문이다. 그래서 현실적으로 모든 곳을 지원해줄 수가 없다. + * + * OAuth2-Client 는 OAuth2ClientProperties 라는 클래스를 통한 자동 설정을 지원해주고 있다. + * + * OAuth2는 여러가지 방식이 있다. Authorization Code Grant Type 방식 등등.. + * + * @see org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties + * @see org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter + * @see org.springframework.security.config.oauth2.client.CommonOAuth2Provider */ @EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록되도록 해준다. @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) // 스프링 시큐리티 관련 특정 어노테이션에 대한 활성화 설정을 할 수 있다. @@ -172,8 +186,8 @@ class PrincipalDetails( * 이때 UserDetailsService 타입으로 등록되어 있는 빈을 찾아서 해당 빈에 정의된 loadUserByUsername() 을 실행한다. * ``` * - * @see DaoAuthenticationProvider - * @see AbstractUserDetailsAuthenticationProvider + * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider + * @see org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider */ @Service class PrincipalDetailService(private val userRepository: UserRepository) : UserDetailsService { @@ -200,9 +214,9 @@ class PrincipalDetailService(private val userRepository: UserRepository) : UserD * OAuth2UserRequest 정보를 이용해서 loadUser 함수 호출 -> 구글로부터 회원프로필을 받아준다. * ``` * - * @see OAuth2UserService - * @see DefaultOAuth2UserService - * @see OAuth2UserRequest + * @see org.springframework.security.oauth2.client.userinfo.OAuth2UserService + * @see org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService + * @see org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest */ @Service class PrincipalOAuth2UserService( diff --git a/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/domain/auth/OAuth2Type.kt b/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/domain/auth/OAuth2Type.kt index c19f85c..f3017a1 100644 --- a/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/domain/auth/OAuth2Type.kt +++ b/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/domain/auth/OAuth2Type.kt @@ -5,7 +5,8 @@ enum class OAuth2Type( private val createUserInfo: (attributes: Map) -> OAuth2UserInfo ) { GOOGLE("google", { attributes -> GoogleUserInfo(attributes) }), - FACEBOOK("facebook", { attributes -> FacebookUserInfo(attributes) }); + FACEBOOK("facebook", { attributes -> FacebookUserInfo(attributes) }), + NAVER("naver", { attributes -> NaverUserInfo(attributes) }); fun createOAuth2UserInfo(attributes: Map): OAuth2UserInfo { return createUserInfo(attributes) diff --git a/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/domain/auth/OAuth2UserInfos.kt b/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/domain/auth/OAuth2UserInfos.kt index c145b7f..5455bd3 100644 --- a/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/domain/auth/OAuth2UserInfos.kt +++ b/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/domain/auth/OAuth2UserInfos.kt @@ -53,5 +53,33 @@ class FacebookUserInfo( override fun getName(): String { return attributes["name"] as String } - +} + +class NaverUserInfo( + /** + * DefaultOAuth2Service#loadUser(OAuth2UserRequest) + * ```kotlin + * val oAuth2User = super.loadUser(userRequest) + * val attributes = oAuth2User.attributes + * ``` + */ + private val attributes: Map +): OAuth2UserInfo { + private val response = attributes["response"] as Map<*, *> + + override fun getProviderId(): String { + return response["id"] as String + } + + override fun getProvider(): String { + return "naver" + } + + override fun getEmail(): String { + return response["email"] as String + } + + override fun getName(): String { + return response["name"] as String + } } diff --git a/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/domain/user/UserController.kt b/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/domain/user/UserController.kt index f10fd19..b4dd53d 100644 --- a/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/domain/user/UserController.kt +++ b/놀이터(예제 코드 작성)/spring-security/src/main/kotlin/com/banjjoknim/playground/domain/user/UserController.kt @@ -64,16 +64,13 @@ class UserController { * @see PrincipalDetailsService * */ - @GetMapping("/login") // OAuth2 로그인 및 일반 로그인 모두 principalDetails 로 세션 정보를 얻어올 수 있다. + @GetMapping("/login") // OAuth2 로그인 및 일반 로그인 모두 principalDetails 로 세션 정보를 얻어올 수 있다(다운 캐스팅을 하지 않아도 된다!). fun login(@AuthenticationPrincipal principalDetails: PrincipalDetails) { // DI(의존성 주입) println("principalDetailsUser : ${principalDetails.user}") } @GetMapping("/test/login") - fun testLogin( - authentication: Authentication, - @AuthenticationPrincipal userDetails: UserDetails - ) { // DI(의존성 주입) + fun testLogin(authentication: Authentication, @AuthenticationPrincipal userDetails: UserDetails) { // DI(의존성 주입) val principalDetailsFromAuthentication = authentication.principal as PrincipalDetails // 다운 캐스팅 println("principalDetailsFromAuthentication : ${principalDetailsFromAuthentication.user}") println("principalDetailsFromAuthentication : ${principalDetailsFromAuthentication.username}") @@ -83,10 +80,7 @@ class UserController { } @GetMapping("/test/oauth2/login") - fun testOAuth2Login( - authentication: Authentication, - @AuthenticationPrincipal oauth: OAuth2User - ) { // DI(의존성 주입) + fun testOAuth2Login(authentication: Authentication, @AuthenticationPrincipal oauth: OAuth2User) { // DI(의존성 주입) val oAuth2User = authentication.principal as OAuth2User // 다운 캐스팅 println("authentication : ${oAuth2User.attributes}") // OAuth2Service 의 super.loadUser(userRequest).attributes 와 같다. println("oAuth2User : ${oauth.attributes}") diff --git a/놀이터(예제 코드 작성)/spring-security/src/main/resources/application.yml b/놀이터(예제 코드 작성)/spring-security/src/main/resources/application.yml index 79ecb78..d6de8fb 100644 --- a/놀이터(예제 코드 작성)/spring-security/src/main/resources/application.yml +++ b/놀이터(예제 코드 작성)/spring-security/src/main/resources/application.yml @@ -16,3 +16,20 @@ spring: scope: - email - public_profile + + naver: + client-id: my-naver-client-id + client-secret: my-naver-client-secret + scope: + - name + - email + client-name: Naver + authorization-grant-type: authorization_code + redirect-uri: http://localhost:8080/login/oauth2/code # 구글이나 페이스북은 기본적으로 설정되어 있기 때문에 작성하지 않아도 된다. 반면, 구글이나 페이스북은 주소가 고정되어 있으니 함부로 변경하면 안된다. + + provider: # provider를 직접 등록해준다. + naver: # /oauth2/authorization/naver 라는 uri를 타고 이동하면 아래의 authorization-uri 로 이동된다. + 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 # 회원정보를 json으로 받는데 response라는 키값으로 네이버가 리턴해준다. diff --git a/놀이터(예제 코드 작성)/spring-security/src/main/resources/static/index.html b/놀이터(예제 코드 작성)/spring-security/src/main/resources/static/index.html new file mode 100644 index 0000000..6633b5a --- /dev/null +++ b/놀이터(예제 코드 작성)/spring-security/src/main/resources/static/index.html @@ -0,0 +1,17 @@ + + + 로그인 페이지 + + +

로그인 페이지

+
+
+
+
+ +
+구글 로그인 +페이스북 로그인 +네이버 로그인 +회원가입을 아직 하지 않으셨나요? +