Compare commits
6 Commits
spring-sec
...
spring-sec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e55eeb462 | ||
|
|
5891775b15 | ||
|
|
a62fbb7f71 | ||
|
|
35a89db2d5 | ||
|
|
3f64ef2977 | ||
|
|
b07abb8703 |
@@ -30,6 +30,20 @@ import org.springframework.stereotype.Service
|
|||||||
* 구글과 페이스북 측에서 우리에게 보내는 Request에 액세스 토큰과 사용자 정보등의 OAUth2 정보가 모두 포함되어 있다.
|
* 구글과 페이스북 측에서 우리에게 보내는 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 // 스프링 시큐리티 필터가 스프링 필터체인에 등록되도록 해준다.
|
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록되도록 해준다.
|
||||||
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) // 스프링 시큐리티 관련 특정 어노테이션에 대한 활성화 설정을 할 수 있다.
|
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) // 스프링 시큐리티 관련 특정 어노테이션에 대한 활성화 설정을 할 수 있다.
|
||||||
@@ -172,8 +186,8 @@ class PrincipalDetails(
|
|||||||
* 이때 UserDetailsService 타입으로 등록되어 있는 빈을 찾아서 해당 빈에 정의된 loadUserByUsername() 을 실행한다.
|
* 이때 UserDetailsService 타입으로 등록되어 있는 빈을 찾아서 해당 빈에 정의된 loadUserByUsername() 을 실행한다.
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @see DaoAuthenticationProvider
|
* @see org.springframework.security.authentication.dao.DaoAuthenticationProvider
|
||||||
* @see AbstractUserDetailsAuthenticationProvider
|
* @see org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
class PrincipalDetailService(private val userRepository: UserRepository) : UserDetailsService {
|
class PrincipalDetailService(private val userRepository: UserRepository) : UserDetailsService {
|
||||||
@@ -200,9 +214,9 @@ class PrincipalDetailService(private val userRepository: UserRepository) : UserD
|
|||||||
* OAuth2UserRequest 정보를 이용해서 loadUser 함수 호출 -> 구글로부터 회원프로필을 받아준다.
|
* OAuth2UserRequest 정보를 이용해서 loadUser 함수 호출 -> 구글로부터 회원프로필을 받아준다.
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @see OAuth2UserService
|
* @see org.springframework.security.oauth2.client.userinfo.OAuth2UserService
|
||||||
* @see DefaultOAuth2UserService
|
* @see org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService
|
||||||
* @see OAuth2UserRequest
|
* @see org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
class PrincipalOAuth2UserService(
|
class PrincipalOAuth2UserService(
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ enum class OAuth2Type(
|
|||||||
private val createUserInfo: (attributes: Map<String, Any?>) -> OAuth2UserInfo
|
private val createUserInfo: (attributes: Map<String, Any?>) -> OAuth2UserInfo
|
||||||
) {
|
) {
|
||||||
GOOGLE("google", { attributes -> GoogleUserInfo(attributes) }),
|
GOOGLE("google", { attributes -> GoogleUserInfo(attributes) }),
|
||||||
FACEBOOK("facebook", { attributes -> FacebookUserInfo(attributes) });
|
FACEBOOK("facebook", { attributes -> FacebookUserInfo(attributes) }),
|
||||||
|
NAVER("naver", { attributes -> NaverUserInfo(attributes) });
|
||||||
|
|
||||||
fun createOAuth2UserInfo(attributes: Map<String, Any?>): OAuth2UserInfo {
|
fun createOAuth2UserInfo(attributes: Map<String, Any?>): OAuth2UserInfo {
|
||||||
return createUserInfo(attributes)
|
return createUserInfo(attributes)
|
||||||
|
|||||||
@@ -53,5 +53,33 @@ class FacebookUserInfo(
|
|||||||
override fun getName(): String {
|
override fun getName(): String {
|
||||||
return attributes["name"] as 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<String, Any?>
|
||||||
|
): 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,16 +64,13 @@ class UserController {
|
|||||||
* @see PrincipalDetailsService
|
* @see PrincipalDetailsService
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@GetMapping("/login") // OAuth2 로그인 및 일반 로그인 모두 principalDetails 로 세션 정보를 얻어올 수 있다.
|
@GetMapping("/login") // OAuth2 로그인 및 일반 로그인 모두 principalDetails 로 세션 정보를 얻어올 수 있다(다운 캐스팅을 하지 않아도 된다!).
|
||||||
fun login(@AuthenticationPrincipal principalDetails: PrincipalDetails) { // DI(의존성 주입)
|
fun login(@AuthenticationPrincipal principalDetails: PrincipalDetails) { // DI(의존성 주입)
|
||||||
println("principalDetailsUser : ${principalDetails.user}")
|
println("principalDetailsUser : ${principalDetails.user}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/test/login")
|
@GetMapping("/test/login")
|
||||||
fun testLogin(
|
fun testLogin(authentication: Authentication, @AuthenticationPrincipal userDetails: UserDetails) { // DI(의존성 주입)
|
||||||
authentication: Authentication,
|
|
||||||
@AuthenticationPrincipal userDetails: UserDetails
|
|
||||||
) { // DI(의존성 주입)
|
|
||||||
val principalDetailsFromAuthentication = authentication.principal as PrincipalDetails // 다운 캐스팅
|
val principalDetailsFromAuthentication = authentication.principal as PrincipalDetails // 다운 캐스팅
|
||||||
println("principalDetailsFromAuthentication : ${principalDetailsFromAuthentication.user}")
|
println("principalDetailsFromAuthentication : ${principalDetailsFromAuthentication.user}")
|
||||||
println("principalDetailsFromAuthentication : ${principalDetailsFromAuthentication.username}")
|
println("principalDetailsFromAuthentication : ${principalDetailsFromAuthentication.username}")
|
||||||
@@ -83,10 +80,7 @@ class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/test/oauth2/login")
|
@GetMapping("/test/oauth2/login")
|
||||||
fun testOAuth2Login(
|
fun testOAuth2Login(authentication: Authentication, @AuthenticationPrincipal oauth: OAuth2User) { // DI(의존성 주입)
|
||||||
authentication: Authentication,
|
|
||||||
@AuthenticationPrincipal oauth: OAuth2User
|
|
||||||
) { // DI(의존성 주입)
|
|
||||||
val oAuth2User = authentication.principal as OAuth2User // 다운 캐스팅
|
val oAuth2User = authentication.principal as OAuth2User // 다운 캐스팅
|
||||||
println("authentication : ${oAuth2User.attributes}") // OAuth2Service 의 super.loadUser(userRequest).attributes 와 같다.
|
println("authentication : ${oAuth2User.attributes}") // OAuth2Service 의 super.loadUser(userRequest).attributes 와 같다.
|
||||||
println("oAuth2User : ${oauth.attributes}")
|
println("oAuth2User : ${oauth.attributes}")
|
||||||
|
|||||||
@@ -16,3 +16,20 @@ spring:
|
|||||||
scope:
|
scope:
|
||||||
- email
|
- email
|
||||||
- public_profile
|
- 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라는 키값으로 네이버가 리턴해준다.
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<head lang="ko">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>로그인 페이지</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>로그인 페이지</h1>
|
||||||
|
<hr/>
|
||||||
|
<form action="/login" method="post">
|
||||||
|
<input type="text" name="username" placeholder="Username"/><br/>
|
||||||
|
<input type="password" name="password" placeholder="Password"/><br/>
|
||||||
|
<button>로그인</button>
|
||||||
|
</form>
|
||||||
|
<a href="/oauth2/authorization/google">구글 로그인</a>
|
||||||
|
<a href="/oauth2/authorization/facebook">페이스북 로그인</a>
|
||||||
|
<a href="/oauth2/authorization/naver">네이버 로그인</a>
|
||||||
|
<a href="/joinForm">회원가입을 아직 하지 않으셨나요?</a>
|
||||||
|
</body>
|
||||||
Reference in New Issue
Block a user