스프링부트 [ 구글 소셜로그인 ]
1. spring.start.io에서 project 생성
2) setting -> Build -> Gradle -> JDK 17로 설정 / Project Structure에서 Project와 Modules 셋팅 설정
3) application.properties -> applicationproperties.yml로 변경
server:
port: 8080
# JDBC
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/yujung
username: root
password: 00000000
# JPA
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
properties:
hibernate:
format_sql: true
hibernate:
ddl-auto: create
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql : true
#OAuth2 ?? - google ? ???? ????( ???? ?? ?? id, pwd)
security:
oauth2:
client:
registration:
google:
client-id:
client-secret:
scope: email,profile
4) 구글 클라우드 -> ouath2 프로젝트 생성 -> API 및 서비스 -> OAuth 동의 화면 -> 사용자 외부 선택 후 만들기 -> 저장후 계속
5) 사용자 인증 정보 -> 사용자 인증 정보 만들기 -> OAuth 만들기 클릭 -> 애플리케이션 유형 -> 웹 애플리케이션
6) 승인된 리디렉션 URI에 링크 추가 -> http://localhost:8080/login/oauth2/code/google -> 만들기
-> 클라이언트 ID와 클라이언트 보안 비밀번호를 project로 돌아가서 application.yml 파일에 추가 (client-id: client-secret: )
(contextPath설정할거면 8080/login사이에 설정, google까진 정해진 링크, port번호는 본인것 사용)
7) OAuth 동의 화면 -> PUBLISH APP -> 확인
1) SecurityConfig -> Oauth2Controller 생성 후 코드 작성
package com.example.ouath2.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authz->authz.requestMatchers("/","home").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2->oauth2
.loginPage("/")
.defaultSuccessUrl("/home")
.permitAll()
)
.logout(logout->logout
.logoutUrl("/logout")
.logoutSuccessUrl("/home")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
);
return http.build();
}
}
package com.example.ouath2.controller;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class Oauth2Controller {
@GetMapping("/")
public String index() {
return "index";
}
}
2) resources -> templates -> index.html 생성 후 코드 작성
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Bootstrap Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
<div class="container mt-3">
<h2>Spring</h2>
<div class="card">
<div class="card-header">
<form class="form-inline" th:action="@{/login}" method="post">
<label for="username">아이디:</label>
<input type="text" class="form-control" placeholder="Enter username" id="username" name="username">
<label for="password">Password:</label>
<input type="password" class="form-control" placeholder="Enter password" id="password" name="password">
<button type="submit" class="btn btn-primary btn-sm">로그인</button>
<a class="btn btn-sm btn-primary" href="/oauth2/authorization/google">구글 로그인</a>
</form>
</div>
<div class="card-body">Content</div>
<div class="card-footer">Footer</div>
</div>
</div>
</body>
</html>
3) (구글 로그아웃 후) 서버 실행 -> 구글로그인 버튼 클릭 -> 로그인을 하게 되면 oauth2 서비스로 로그인 화면 뜸 -> 계속 누르면 오류 -> SecurityConfig에서 .oauth2Login에 성공시 /home으로 이동 해놨는데 아직 /home을 만들지 않아서
4) 구글 로그인 된 상태로 서버 끄고 브라우저 끄고(세션 끊어야해서) 다시 http://localhost:8080/manager로 들어가면
.anyRequest().authenticated()로 인해 초기 로그인 화면 뜸 -> 그리고 구글 로그인 된 상태로 구글 로그인 버튼을 누르면
이전에 입력했던 경로까지 뜨면서 리다이렉트 됨
5) 구글 로그인한 상태라면 -> 로그인한 사용자의 정보를 가져올 수 있음
-> PrincipalOauth2Service 생성 후 extends DefaultOauth2UserService
-> 로그아웃 상태에서 구글 로그인할 때(계속버튼누르면) 콘솔창에 사진처럼 출력됨(=user객체의 정보)
package com.example.ouath2.service;
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 PrincipalOauth2Service extends DefaultOAuth2UserService {
//구글 로그인한 사용자 정보 가져오기
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
System.out.println(userRequest.getClientRegistration());
System.out.println(userRequest.getAccessToken().getTokenValue());
System.out.println(super.loadUser(userRequest).getAttributes());
System.out.println(userRequest);
return super.loadUser(userRequest);
}
}
6) 강제로 회원가입 해보기(디비에 값을 넣어서) / Member와 Role Entity 생성 -> 7번
package com.example.ouath2.entity;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import java.sql.Timestamp;
import java.util.Set;
@Entity
@Data
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 50, unique = true, nullable = false)
private String username; //google_561238...
private String password; //암호화
private String uname; //구글로그인했을 떄의 이름
private String email;
private String provider;
private String providerId; //sub=561238...
@CreationTimestamp
private Timestamp createdDate;
@ManyToMany
@JoinTable(name = "member_roles", joinColumns = @JoinColumn(name="member_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles;
}
package com.example.ouath2.entity;
import jakarta.persistence.*;
import lombok.Data;
@Entity
@Data
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String name;
}
7) PrincipalOauth2Service로 돌아와서 디비에 값 집어 넣기
package com.example.ouath2.service;
import com.example.ouath2.entity.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
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;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
@Service
public class PrincipalOauth2Service extends DefaultOAuth2UserService {
@Autowired
private PasswordEncoder passwordEncoder;
//구글 로그인한 사용자 정보 디비에 값 집어넣기
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String provider=userRequest.getClientRegistration().getRegistrationId();
String providerId=oAuth2User.getAttribute("sub"); //10713626148...
String username=provider + "-" + providerId;
String password=passwordEncoder.encode("임의값");
String uname=(String)oAuth2User.getAttribute("name");
String email=(String)oAuth2User.getAttribute("email");
Set<Role> roles=new HashSet<>();
return super.loadUser(userRequest);
}
}
8) RoleRepository Interface 생성 후 extends JpaRepository<Role, Long>
package com.example.ouath2.repository;
import com.example.ouath2.entity.Role;
import org.springframework.data.jpa.repository.JpaRepository;
public interface RoleRepository extends JpaRepository<Role, Long> {
public Role findByName(String name);
}
9) PrincipalOauth2Service에서 Set에 userRole정보를 가져와서 담기 -> Set<Role> roles=new HashSet<>();
-> Role userRole = rolseRepository.findByName("USER"); roles.add(userRole);
package com.example.ouath2.service;
import com.example.ouath2.entity.Role;
import com.example.ouath2.repository.RoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
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;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
@Service
public class PrincipalOauth2Service extends DefaultOAuth2UserService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private RoleRepository roleRepository;
//구글 로그인한 사용자 정보 디비에 값 집어넣기
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String provider=userRequest.getClientRegistration().getRegistrationId();
String providerId=oAuth2User.getAttribute("sub"); //10713626148...
String username=provider + "-" + providerId;
String password=passwordEncoder.encode("임의값");
String uname=(String)oAuth2User.getAttribute("name");
String email=(String)oAuth2User.getAttribute("email");
Set<Role> roles=new HashSet<>();
Role userRole = roleRepository.findByName("USER");
roles.add(userRole);
return super.loadUser(userRequest);
}
}
10) DB에 사용자 데이터를 저장하기 전에 회원 유무 확인하기
-> MemberRepository Interface 생성 후 extends JpaRepository<Member, Long>
-> Optional Member값이 있으면 true, 아니면 false
package com.example.ouath2.repository;
import com.example.ouath2.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member, Long> {
public Optional<Member> findByUsername(String username); //있냐 없냐 확인하는 Optional
}
11) PrincipalOauth2Service에서 사용자가 로그인을 한적이 있냐 없냐 즉, DB에 값이 있냐 없냐 확인
package com.example.ouath2.service;
import com.example.ouath2.entity.Member;
import com.example.ouath2.entity.Role;
import com.example.ouath2.repository.MemberRepository;
import com.example.ouath2.repository.RoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
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;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@Service
public class PrincipalOauth2Service extends DefaultOAuth2UserService {
// 1. 구글 로그인한 사용자 정보 가져오기 -> 회원가입 -> 디비에 삽입
// 2. 로그인을 한적이 있냐 없냐 즉, DB에 값이 있냐 없냐
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private RoleRepository roleRepository;
@Autowired
private MemberRepository memberRepository;
//구글 로그인한 사용자 정보 디비에 값 집어넣기
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String provider = userRequest.getClientRegistration().getRegistrationId();
String providerId = oAuth2User.getAttribute("sub"); //10713626148...
String username = provider + "-" + providerId;
Optional<Member> optional = memberRepository.findByUsername(username);
Member member = null;
if (optional.isPresent()) {
System.out.println("already login");
member = optional.get();
} else {
System.out.println("first oauth login");
String password = passwordEncoder.encode("임의값");
String uname = (String) oAuth2User.getAttribute("name");
String email = (String) oAuth2User.getAttribute("email");
Set<Role> roles = new HashSet<>();
Role userRole = roleRepository.findByName("USER");
roles.add(userRole);
member = new Member();
member.setUsername(username);
member.setPassword(password);
member.setUname(uname);
member.setProvider(provider);
member.setProviderId(providerId);
member.setEmail(email);
member.setRoles(roles);
memberRepository.save(member);
}
return null;
}
}
12) ddl-auto: update로 바꾸기