1. 기본 환경 세팅
https://coding-mid-life.tistory.com/71 을 기본으로 시작한다
2. OAuth2에 필요한 추가 코드
1) build.gradle 파일에 코드를 추가한다
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // 추가
2) application.yml 파일을 변경한다
- clientID와 clientSecret은 구글 API console에서 발급받은 API Key를 입력한다
spring:
security:
oauth2:
client:
registration:
google:
clientId:
clientSecret:
scope:
- email
- profile
3) loginForm.html 파일에 코드를 추가한다
</form>
<a href="/oauth2/authorization/google">구글 로그인</a> <!-- 추가 -->
<a href="join">회원가입</a>
4) SecurityConfig 클래스에 코드를 추가한다
.defaultSuccessUrl("/") //추가
.and() // 추가
.oauth2Login() // 추가
.loginPage("/login"); // 추가
return http.build();
3. 애플리케이션 실행
1) 코드를 모두 수정한 후 애플리케이션을 실행한다
- localhost:8080/login 으로 접속하여 '구글로그인'을 선택한다
![]() |
![]() |
계정을 클릭하여 로그인을 진행하면
4. 구글 회원 프로필 정보 받기
1) 로그인 후 필요한 후처리 작업을 해 준다
- 코드 받기(인증)
- 액세스 토큰(권한)
- 사용자 프로필 정보를 가져온다
- 3번에서 가져온 정보를 토대로 자동으로 회원가입 할 수 있다
- 위 정보만으로 부족할 경우 추가적인 구성 정보를 기입할 수 있다
2) config 패키지에 oauth 패키지를 만들고 PrincipalOauth2UserService 클래스를 생성한다
- 구글 로그인 버튼 클릭 → 구글 로그인 창 → 로그인 완료 → code 리턴(OAuth-Client 라이브러리) → AccessToken 요청 userRequest 정보 → loadUser 함수 호출 → 구글로부터 회원프로필 정보를 받아 온다
- 로그인 후에 필요한 후처리 작업을 담당한다
- loadUser 메서드를 Override하는데 이 메서드는 구글로부터 받은 userRequest 데이터에 대한 후처리 함수이다
- userRequest에 담긴 정보를 확인할 수 있는 메서드
- userRequest.getClientRegistration()
- userRequest.getAccessToken().getTokenValue()
- super.loadUser(userRequest).getAttributest()
package com.memberlogin.loginjoin.config.oauth;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
System.out.println("userRequest : " + userRequest);
return super.loadUser(userRequest);
}
}
3) SecurityConfig에 위 코드들을 적용해 주는 코드를 추가한다
@Autowired
private PrincipalOauth2UserService principalOauth2UserService; // 추가
......
.userInfoEndpoint() // 추가
.userService(principalOauth2UserService); // 추가
4) Member 클래스에 코드를 추가한다
private String provider;
private String providerId;
5. authentication 정보 확인
1) PrincipalDetails 클래스에 @Data 애너테이션을 추가한다
@Data // 추가
2) IndexController 클래스에 코드를 추가한다
- Authentication 객체를 의존성 주입을 통해 다운 캐스팅하여 정보를 가져올 수 있다.
- 회원가입 → 로그인 후 /loginTest url로 접속해서 로그에 Authentication 정보를 확인합니다.
// 추가
@GetMapping("/loginTest")
public @ResponseBody String loginTest(Authentication authentication) {
System.out.println("============/loginTest===========");
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
System.out.println("authentication : " + principalDetails.getMember());
return "세션 정보 확인";
}
- 위와 같은 동작을 애노테이션으로 할 수 있도록 코드를 추가한다
- 애너테이션을 통해 회원정보를 가져올 수 있다.
// 추가
@GetMapping("/loginTest2")
public @ResponseBody String loginTest2(@AuthenticationPrincipal PrincipalDetails principalDetails) {
System.out.println("============/loginTest2===========");
System.out.println("userDetails : " + principalDetails.getMember());
return "세션 정보 확인2";
}
- OAuth2 로그인 정보를 가져오기 위한 코드를 추가한다
// 추가
@GetMapping("/loginTest3")
public @ResponseBody String loginOAuthTest(
Authentication authentication,
@AuthenticationPrincipal OAuth2User oauth) {
System.out.println("============/loginOAuthTest===========");
OAuth2User oauth2User = (OAuth2User) authentication.getPrincipal();
System.out.println("authenticaion : " + oauth2User.getAttributes());
System.out.println("oauth2User : " + oauth.getAttributes());
return "세션 정보 확인3";
}
6. 스프링 시큐리티 (세션)
1) Authentication 객체만 가질 수 있다
- 일반 로그인 → PrincipalDetails( implements UserDeatils)
- OAuth 로그인 → OAuth2User
- 위와 같이 진행될 시 로그인 User 처리가 불편하게 된다.
- OAuth 유저도 PrincipalDetails로 묶으면 된다.
2) PrincipalDetails 클래스에 코드를 추가한다
- PrincipalDetails 클래스에 OAuth2User를 추가로 implements 해 준다
- getAttributes() 와 getName() 메서드를 오버라이드 해 준다
@Data
public class PrincipalDetails implements UserDetails, OAuth2User {
...
@Override
public Map<String, Object> getAttributes() {
return null;
}
@Override
public String getName() {
return null;
}
}
3) 애플리케이션 재실행 후 localhost:8080/login 접속 후 구글 로그인 출력화면이다
7. PrincipalDetails
1) PrincipalDetails
- UserDetails를 구현하는 객체로 사용되고 있다
2) 스프링 시큐리티 세션
- 시프링 시큐리티 세션 정보는 단 1가지 타입인 Authentication 객체만 가지고 있을 수 있다
3) Authentication 객체를 담는 2가지 필드
- OAuth2User 와 UserDetails가 있다
- 일반적으로 회원가입을 하면 UserDetails를 통해 처리를 하게 된다
- OAuth2로 회원가입 및 로그인을 하면 OAuth2User를 통해 처리를 하게 된다 - 여기서 문제가 발생한다
- OAuth2User와 UserDetails에는 Member 객체를 포함하고 있지 않다
4) 해결 방법
- UserDetails를 PrincipalDetails에 implements 한 후에 Member 객체를 담게 한다
- Authentication 객체를 갖는 PrincipalDetails가 만들어진다
- PrincipalDetails 객체에는 Member가 포함되어 있다 - OAuth2User 또한 같은 문제점을 가지고 있고 일반 회원가입 & 로그인 처리와 OAuth2 처리가 별도로 되어 문제가 발생한다
- PrincipalDetails implements UserDetails, OAuth2User를 통해 문제를 해결한다
8. 회원가입
1) PrincipalDetails 클래스에 코드를 추가 및 수정한다
@Data
public class PrincipalDetails implements UserDetails, OAuth2User {
private Member member;
private Map<String, Object> attributes; // 추가
// 일반 로그인
public PrincipalDetails(Member member) {
this.member = member;
}
// 추가 & OAuth 로그인
public PrincipalDetails(Member member, Map<String, Object> attributes) {
this.member = member;
this.attributes = attributes;
}
...
@Override
public Map<String, Object> getAttributes() {
return attributes; // 수정
}
}
- Member 클래스에 코드를 추가한다
@Entity
@Data
@NoArgsConstructor // 추가
public class Member {
@Builder // 추가
public Member(String username, String email, String role, String provider, String providerId) {
this.username = username;
this.email = email;
this.role = role;
this.provider = provider;
this.providerId = providerId;
}
...
}
- 후처리 작업이 있는 PrincipalOauth2UserService 클래스의 코드를 수정한다
- memberEntity
: null 값일 때는 oauth로 처음 로그인 한 것이므로 회원가입 처리를 해 준다
: null이 아닌 경우에는 기존에 1번이라도 로그인 한 이력이 있기 때문에 별도 처리를 하지 않는다
package com.memberlogin.loginjoin.config.oauth;
import com.memberlogin.loginjoin.model.Member;
import com.memberlogin.loginjoin.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
//코드 추가
@Autowired
private MemberRepository memberRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
//코드 추가
OAuth2User oauth2User = super.loadUser(userRequest);
String provider = userRequest.getClientRegistration().getClientId();
String providerId = oauth2User.getAttribute("sub");
String username = oauth2User.getAttribute("name");
String email = oauth2User.getAttribute("email");
String role = "ROSE_USER";
Member memberEntity = memberRepository.findByUsername(username);
if(memberEntity == null) {
// OAuth로 처음 로그인한 유저 - 회원가입 처리
memberEntity = Member.builder()
.username(username)
.email(email)
.role(role)
.provider(provider)
.providerId(providerId)
.build();
memberRepository.save(memberEntity);
}
return new PrincipalDetails(memberEntity, oauth2User.getAttributes());
}
// System.out.println("userRequest : " + userRequest);
// return super.loadUser(userRequest);
// }
}
2) 로그인하여 Member 객체 정보 확인
- IndexController 클래스에서 /user url을 수정한다
- 일반 회원가입 & 로그인 → /user url 접속
- 로그아웃 후 구글 로그인 → /user url 접속
public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
System.out.println(principalDetails.getMember());
return "user";
}
- 일반 회원가입 & 로그인 → /user url 접속
- 로그아웃 후 구글 로그인 → /user url 접속
- 일반 회원가입과 OAuth2 로그인으로 회원가입 처리가 정상적으로 되는 것을 확인할 수 있다
9. Index 페이지 통한 로그인 정보 확인
- 지금까지는 로그인이 정상적으로 되었는지 확인하려면 /user (권한이 있는 경우에만 접근 가능)로 접속해서 확인했다
- 지금부터는 Index 페이지(/)에서 로그인 되었는지 확인할 수 있도록 코드를 수정, 추가해 본다
1) IndexController 클래스의 코드를 수정 한다
@GetMapping("/")
public String index(@AuthenticationPrincipal PrincipalDetails principalDetails, Model model) {
try {
if(principalDetails.getUsername() != null) {
model.addAttribute("username", principalDetails.getUsername());
}
} catch (NullPointerException e) {}
return "index";
}
2) src > main > resources > templates 패키지에 index.html 파일을 생성한다
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Index Page 입니다.</title>
</head>
<body>
<h1>Index 페이지 입니다.</h1>
{{#username}}
<h1>{{username}} 사용자입니다.</h1>
<a href="/user">유저</a>
<a href="/logout">로그아웃</a>
{{/username}}
{{^username}}
<h3>로그인되지 않았습니다.</h3>
<a href="/login">로그인 페이지로 이동</a>
<a href="/join">회원가입 페이지로 이동</a>
{{/username}}
</body>
</html>
3) 프로젝트를 재실행 후 애플리케이션에 접속하면 화면 출력이 변경되었음을 확인할 수 있다
로그인하지 않았을 때 | 로그인 했을 때 |
![]() |
![]() |
'Spring Security' 카테고리의 다른 글
Spring Security - OAuth2 인증(Authentication) (0) | 2022.07.31 |
---|---|
Spring Security - Filter & FilterChain (0) | 2022.07.30 |
Spring Security - JWT 로그인 (0) | 2022.07.28 |
Spring Security - JWT Bearer 인증 (0) | 2022.07.27 |
Spring Security - JWT 인증(Authentication) (0) | 2022.07.27 |