반응형
이 포스팅은 아래의 강의를 참고하였으니 여기에서 공부하시는 것을 추천드립니다.
1. 구글 로그인 준비
더보기
1) Google에서 '구글 api 콘솔' 검색 후 APIs Console... 클릭
2) 프로젝트 선택 - 새 프로젝트 클릭 후 이름 아무렇게나 짓고 만들기
3) 방금 생성한 프로젝트로 선택
4) OAuth 동의 화면 - 외부 - 만들기 에서 앱 이름과 사용자 지정 이메일, 개발자 연락처 정보
를 기입 후 동의 후 저장 계속 눌러서 완료한다.
5) 사용자 인증 정보 - OAuth 클라이언트 ID 만들기
6) 아래 캡쳐처럼 애플리케이션 유형, 이름, URI를 기재한다. (필자는 공부목적으로 아래의 주소처럼 입력함)
2. Spring Boot 설정
1) pom.xml에 아래의 의존성(OAuth2 Client) 추가 및 application.yml에 아래처럼 설정
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
- application.yml
spring:
security:
oauth2:
client:
registration:
google:
client-id: ID입력
client-secret: 시크릿코드입력
scope:
- email
- profile
2) SecurityConfig 설정 클래스 파일의 configure 메소드를 아래 표시된 소스 추가
@Override
protected void configure(HttpSecurity http) throws Exception{ // Spring Security 설정
http.csrf().disable(); // csrf 비활성화
// URL에 따른 접근 제한 처리
http.authorizeRequests()
.antMatchers("/user/**").authenticated() // URL user : 인증이 되어야 함
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')") // URL manager : 권한 'ROLE_ADMIN', 'ROLE_MANAGER'가 있어야함
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')") // URL admin : 권한 'ROLE_ADMIN'이 있어야함
.anyRequest().permitAll() // 위에 명시되지 않은 URL 은 로그인 및 권한 검사 X
.and()
.formLogin()
.loginPage("/loginForm") // formLogin이 필요한 경우, /loginForm 으로 보낸다.
.loginProcessingUrl("/login") // login 주소가 호출이 되면 시큐리티가 낚이채서 대신 로그인을 진행
.defaultSuccessUrl("/") // login 성공 시, 보내줄 기본 url
// OAuth2 로그인 설정(시작)
.and()
.oauth2Login()
.loginPage("/loginForm") // OAuth2의 로그인 페이지 URL.
.userInfoEndpoint()
.userService(principalOauth2UserSerivce)// 구글 로그인이 완료된 이후에 후 처리가 필요. (코드가 아닌 액세스토큰+사용자프로필의정보)를 가져옴
// OAuth2 로그인 설정(끝)
;
}
3) PrincipalDetails 클래스를 OAuth2User를 상속받는다.
* 상속받는 이유?
1. Spring Security를 이용한 일반 로그인 시, 유저 정보가 UserDetail 클래스에 담기며,
2. OAuth2 로그인 시, 유저 정보가 OAuth2User 클래스에 담겨온다.
3. 그리하여 하나의 클래스가 이 두 가지를 모두 상속받아서 혼용하여 사용할 수 있도록 하기 위함이다.
package com.cos.security1.config.auth;
// 시큐리티가 /login url을 낚아채서 로그인을 진행시킴.
// 로그인을 하면 security 전용 session을 만들어준다. (Security ContextHolder)
// 오브젝트 타입 => Authentication 타입 객체
// Authentication 안에 User 정보가 있어야 한다.
// User 오브젝트 타입 => UserDetails 타입 객체
import com.cos.security1.model.User;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
// Security Session 안에 들어갈 수 있는 객체: Authentication
// Authentication 안에 들어갈 수 있는 객체: UserDetails, OAuth2User
@Data
public class PrincipalDetails implements UserDetails, OAuth2User {
private User user;
private Map<String, Object> attibutes;
// 일반 로그인
public PrincipalDetails(User user){
this.user = user;
}
// OAuth2 로그인
public PrincipalDetails(User user, Map<String, Object> attibutes){
this.user = user;
this.attibutes = attibutes;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
// 해당 유저의 권한을 리턴하는 method
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
String role = user.getRole();
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return role;
}
});
return collect;
}
@Override
// 만료 여부
public boolean isAccountNonExpired() {
return true;
}
@Override
// 잠금 여부
public boolean isAccountNonLocked() {
return true;
}
@Override
// 만료 여부
public boolean isCredentialsNonExpired() {
return true;
}
@Override
// 회원 사용 여부
public boolean isEnabled() {
return true;
}
@Override
public Map<String, Object> getAttributes() {
return attibutes;
}
@Override
public <A> A getAttribute(String name) {
return OAuth2User.super.getAttribute(name);
}
@Override
public String getName() {
return null;
}
}
4) oauth2라는 패키지 명을 생성 후, PrincipalOauth2UserService 클래스 파일 생성
OAuth2를 통해 로그인을 하면, 해당 정보를 이용해서 아이디가 있는 지 체크 후 아이디가 없으면 자동가입을 진행한다.
package com.cos.security1.config.oauth2;
import com.cos.security1.Repository.UserRepository;
import com.cos.security1.config.auth.PrincipalDetails;
import com.cos.security1.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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 PrincipalOauth2UserSerivce extends DefaultOAuth2UserService {
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private UserRepository userRepository;
// 구글 로그인 작동 방식
// 구글로그인버튼 클릭 > 구글로그인 > code 리턴(OAuth-Client Library) > AccessToken 요청
// userRequest 정보 > loadUser함수 호출 > 구글로부터 회원 프로필을 받아줌
// 구글로부터 받은 userRequest 데이터에 대한 후처리 함수
// 함수 종료 시, @AuthenticationPrincipal 어노테이션이 만들어진다.
// OAuth2 로그인 시 사용
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException{
System.out.println("getClientRegistration: "+userRequest.getClientRegistration()); // registration: 어떤 매체에서 로그인했는지 확인 가능
System.out.println("getAccessToken: "+userRequest.getAccessToken().getTokenValue()); // access token
System.out.println("getAttributes: "+super.loadUser(userRequest).getAttributes()); // 프로필 정보
OAuth2User oAuth2User = super.loadUser(userRequest);
System.out.println("getAttributes: "+oAuth2User.getAttributes());
String provider = userRequest.getClientRegistration().getRegistrationId(); // google, facebook, etc
String providerId = oAuth2User.getAttribute("sub");
// OAuth2 로그인 시, username과 password는 필요없지만 형식상 넣어줌
String username = provider + "_" + providerId;
System.out.println("username: "+username);
String password = bCryptPasswordEncoder.encode("provider");
String role = "ROLE_USER";
String email = oAuth2User.getAttribute("email");
User findUser = userRepository.findByUsername(username);
if(findUser == null){
findUser = User.builder()
.username(username)
.password(password)
.email(email)
.role(role)
.provider(provider)
.providerId(providerId)
.build();
userRepository.save(findUser);
}
return new PrincipalDetails(findUser, oAuth2User.getAttributes());
}
}
5) IndexController 클래스에서 아래와 같이 PrincipalDetails를 이용하여 유저 정보를 받을 수 있다.
일반 로그인, OAuth2 로그인 상관없이 혼용 가능
package com.cos.security1.controller;
import com.cos.security1.Repository.UserRepository;
import com.cos.security1.config.auth.PrincipalDetails;
import com.cos.security1.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class IndexController {
@Autowired
UserRepository userRepository;
@Autowired
BCryptPasswordEncoder encoder;
@GetMapping({"", "/"})
public String index(){
// mustache default folder : src/main/resources/
// view resolver setting : templates (prefix), .mustache (suffix)
return "index"; // src/main/resources/templates/index.mustache
}
// 일반로그인을 하던, OAuth2로 로그인을 하던 PrincipalDetails로 받기 때문에 혼용 가능
@GetMapping({"/user"})
@ResponseBody
public String user(@AuthenticationPrincipal PrincipalDetails principalDetails)
{
System.out.println("principalDetails: "+principalDetails.getUser());
return "user";
}
@GetMapping({"/admin"})
@ResponseBody
public String admin(){
return "admin";
}
@GetMapping({"/manager"})
@ResponseBody
public String manager(){
return "manager";
}
// /login SpringSecurity 에서 사용하지만, SecurityConfig 에서 설정 가능
// 로그인 폼으로 이동
@GetMapping({"/loginForm"})
public String loginForm(){
return "loginForm";
}
// 회원가입 폼으로 이동
@GetMapping({"/joinForm"})
public String joinForm(){
return "joinForm";
}
// 회원가입 로직
@PostMapping({"/join"})
public String join(User user){
user.setRole("ROLE_ADMIN");
// 패스워드 암호화를 진행하지 않으면 시큐리티 로그인이 불가능
String encPw = encoder.encode(user.getPassword());
user.setPassword(encPw);
userRepository.save(user);
return "redirect:/loginForm";
}
@GetMapping({"/joinProc"})
@ResponseBody
public String joinProc(){
return "회원가입 완료";
}
@Secured("ROLE_ADMIN") // 이 메소드에 대해서만 특정 권한이 필요할 때 사용 가능
@GetMapping("/info")
@ResponseBody
public String info(){
return "개인정보";
}
@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") // 메소드 접근 이전에 확인하는 로직
//@PostAuthorize() // 메소드 접근 이후에 확인하는 로직
@GetMapping("/data")
@ResponseBody
public String data(){
return "데이터정보";
}
// Authentication 사용방법
// 일반 로그인의 경우: PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal(); 쓰면 되지만
// OAuth2의 경우: OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();을 써야함.
// @AuthenticationPrincipal 사용방법
// 일반 로그인의 경우: @AuthenticationPrincipal UserDetails userDetails
// OAuth2의 경우: @AuthenticationPrincipal OAuth2User oAuth2
// 즉, Authentication 에 UserDetails or OAuth2User가 있음.
@GetMapping("/test/login")
@ResponseBody
public String testLogin(Authentication authentication, @AuthenticationPrincipal UserDetails userDetails){
System.out.println("/test/login ================");
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
System.out.println("authentication: "+principalDetails);
System.out.println("userDetails: "+userDetails.getUsername());
return "세션정보확인";
}
@GetMapping("/test/oauthlogin")
@ResponseBody
public String oauthlogin(Authentication authentication, @AuthenticationPrincipal OAuth2User oAuth2){
System.out.println("/test/oauthlogin ================");
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
System.out.println("authenticaiton: "+oAuth2User.getAttributes());
System.out.println("oauth2User: "+oAuth2.getAttributes());
return "oAuth2 세션정보확인";
}
}
반응형
'BE > Spring' 카테고리의 다른 글
[Spring Security] 네이버 로그인하기 (0) | 2022.06.06 |
---|---|
[Spring Security] 페이스북 로그인하기 (0) | 2022.06.03 |
[Spring Security] 계정 생성, 로그인 및 권한 처리 (0) | 2022.05.30 |
[스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술] - 1일차 (0) | 2022.05.29 |
[Spring Boot] 이메일 전송하는 방법 (0) | 2022.05.16 |