카카오 로그인 - 회원가입
프로젝트 목표
Spring Security, OAuth2 Client 의존성을 사용하여, OAuth 인증 성공 시, 회원 가입 처리를 구현
Code
Preview
1.
2.
3.
작업 프로세스
1.
2.
3.
4.
5.
6.
7.
9.
Preview
메인 화면
로그인 화면
로그인
메인 화면 (로그인 완료)
작업 프로세스
1.
프로젝트 생성
•
build.gradle•
spring boot version : 2.x.x
•
spring security version : 5.x.x
•
의존성 설정
◦
Spring Web
◦
Spring Boot DevTools
◦
Spring Security
◦
OAuth2 Client
◦
Lombok
◦
Thymeleaf
◦
MySQL
◦
MyBatis Framework
2.
프로젝트 설정
•
application.properties◦
데이터 소스 설정
◦
MyBatis 설정
•
application-oauth.properties4.
테이블 정의
•
USER
•
USER_AUTH
•
USER_SOCIAL
5.
도메인
•
Users.java
•
UserAuth.java
•
UserSocial.java
•
CustomeUser.java
•
OAuthAttributes.java
6.
데이터
•
UserMapper.xml
•
UserMapper.java
7.
서비스
•
UserService.java
•
UserServiceImpl.java
•
OAuthService.java
•
OAuthServiceImpl.java
8.
스프링 시큐리티 설정
•
~/config/SecufityConfig.java9.
요청 경로 매핑
•
~/controller/HomController.java◦
메인 화면
▪
: /▪
index.html프로젝트 생성
1.
프로젝트 생성
2.
프로젝트 설정
3.
의존성 설정
4.
build.gradle
명령 팔레트 [ctrl + shift + P]
Create a gradle Project 입력
프로젝트 설정
1.
Spring Boot Version
2.
Language
3.
Group Id
4.
Artifact Id
5.
packaging type
6.
Java version
Spring Boot Version
Language
Group Id
Artifact Id
packaging type
Java version
의존성 설정
1.
Spring Web
2.
Spring boot devtools
3.
Lombok
4.
Thymeleaf
5.
Spring Security
6.
OAuth2 Client
7.
MySQL Driver
8.
Mybatis Framework
Spring Web
Spring boot devtools
Lombok
Thymeleaf
Spring Security
OAuth2 Client
MySQL Driver
Mybatis Framework
build.gradle
spring boot 2.x.x
spring security 5.x.xplugins {
id 'java'
id 'war'
id 'org.springframework.boot' version '2.7.17'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'com.aloha'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.3.1'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
Java
복사
프로젝트 설정
application.properties
•
데이터 소스 설정
•
MyBatis 설정
spring.application.name=kakao-join
# application-oauth.propeties 파일 포함하기
spring.profiles.include=oauth
# 데이터 소스 - MySQL
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/aloha?serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true&useSSL=false&autoReconnection=true&autoReconnection=true
spring.datasource.username=aloha
spring.datasource.password=123456
# Mybatis 설정
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.type-aliases-package=com.aloha.kakaojoin.domain
mybatis.mapper-locations=classpath:mybatis/mapper/**/**.xml
Markdown
복사
application-oauth.properties
#Kakao OAuth Settings
# client-id : REST API KEY
spring.security.oauth2.client.registration.kakao.client-id=[REST API KEY]
# client-secret : 내 애플리케이션 > 보안 > Client secret
spring.security.oauth2.client.registration.kakao.client-secret=[Client Secret]
# redirect-uri : 사용자가 카카오로 로그인 후 돌아올 서버의 URL
spring.security.oauth2.client.registration.kakao.redirect-uri=http://localhost:8080/login/oauth2/code/kakao
# authorization-grant-type : 액세스 토큰 요청 시, 유형을 지정하는 헤더 속성
spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code
# scope : 사용자 정보 요청 범위를 지정하는 속성
# ✅ biz 계정(사업자등록번호)일 경우에만 email 요청이 가능
# spring.security.oauth2.client.registration.kakao.scope=profile_nickname, account_email, profile_image
spring.security.oauth2.client.registration.kakao.scope=profile_nickname, profile_image
# client-name : 클라이언트를 구분하기 위한 이름
spring.security.oauth2.client.registration.kakao.client-name=Kakao
# client-authentication-method : 인증 서버로 요청 시, 지정할 요청 메소드
spring.security.oauth2.client.registration.kakao.client-authentication-method=POST
# authorization-uri : 인가 코드 요청 URI (인증 서버)
spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize
# token-uri : 액세스 토큰 요청 URI (인증 서버)
spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token
# user-info-uri : 사용자 정보 요청 URI (리소스 서버)
spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me
# user-name-attribute : 사용자 정보를 고유 식별가 키 속성
spring.security.oauth2.client.provider.kakao.user-name-attribute=id
Markdown
복사
ERD
테이블 정의
•
USER
•
USER_AUTH
•
USER_SOCIAL
USER
CREATE TABLE `user` (
-- `NO` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`ID` CHAR(36) PRIMARY KEY,
`USERNAME` varchar(100) NOT NULL UNIQUE,
`PASSWORD` varchar(200) NOT NULL,
`NAME` varchar(100) NOT NULL,
`EMAIL` varchar(200) DEFAULT NULL,
`PROFILE` text DEFAULT NULL,
`CREATED_AT` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`UPDATED_AT` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`ENABLED` int DEFAULT 1
) COMMENT='회원';
SQL
복사
USER_AUTH
CREATE TABLE `user_auth` (
-- `NO` int NOT NULL AUTO_INCREMENT PRIMARY KEY -- 권한번호
`ID` CHAR(36) PRIMARY KEY -- ID
, `USERNAME` varchar(100) NOT NULL -- 회원 아이디
, `AUTH` VARCHAR(100) NOT NULL -- 권한 (ROLE_USER, ROLE_ADMIN, ...)
);
SQL
복사
USER_SOCIAL
CREATE TABLE user_social (
`ID` CHAR(36) PRIMARY KEY,
`USERNAME` VARCHAR(100) NOT NULL,
`PROVIDER` VARCHAR(50) NOT NULL,
`SOCIAL_ID` VARCHAR(255) NOT NULL,
`NAME` VARCHAR(100) NOT NULL,
`EMAIL` VARCHAR(200) DEFAULT NULL,
`PICTURE` TEXT DEFAULT NULL,
`CREATED_AT` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`UPDATED_AT` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
SQL
복사
도메인
•
Users.java
•
UserAuth.java
•
UserSocial.java
•
CustomeUser.java
•
OAuthAttributes.java
Users.java
@Data
public class Users {
private String id;
private String username;
private String password;
private String name;
private String email;
private String profile;
private Date createdAt;
private Date updatedAt;
private int enabled;
private List<UserAuth> authList;
}
Java
복사
UserAuth.java
@Data
public class UserAuth {
private String id;
private String username;
private String auth;
}
Java
복사
UserSocial.java
@Data
public class UserSocial {
private String id;
private String username;
private String provider;
private String socialId;
private String name;
private String email;
private String picture;
private Date createdAt;
private Date updatedAt;
}
Java
복사
CustomeUser.java
@Getter
public class CustomUser extends DefaultOAuth2User {
private Users user;
public CustomUser(Users user, OAuthAttributes oAuthAttributes) {
super(user.getAuthList().stream().map(auth -> new SimpleGrantedAuthority(auth.getAuth())).collect(Collectors.toList())
,oAuthAttributes.getAttribute()
,oAuthAttributes.getNameAttributeKey() )
;
this.user = user;
}
public String getName() {
return user.getName();
}
public String getEmail() {
return user.getEmail();
}
public String profile() {
return user.getProfile();
}
}
Java
복사
OAuthAttributes.java
@Slf4j
@Getter
@ToString
public class OAuthAttributes {
private Map<String, Object> attribute; // OAuth 토큰 속성들
private String nameAttributeKey; // 사용자 이름 속성 키
private String name; // 이름(닉네임)
private String email; // 이메일
private String picture; // 프로필 사진 URL
private String id; // 사용자 정보 식별키
@Builder
public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey, String name, String email, String picture, String id) {
this.attribute = attributes;
this.nameAttributeKey = nameAttributeKey;
this.name = name;
this.email = email;
this.picture = picture;
this.id = id;
}
public static OAuthAttributes of(String registrationId, String userNameAttributeName, Map<String, Object> attributes ) {
if("kakao".equals(registrationId)) {
return ofKakao(userNameAttributeName, attributes);
}
return null;
}
private static OAuthAttributes ofKakao(String userNameAttributeName, Map<String, Object> attributes) {
System.out.println("ofKakao=="+attributes);
Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
log.info("kakaoAccount : " + kakaoAccount);
log.info("profile : " + profile);
log.info("thumbnail_image_url : " + profile.get("thumbnail_image_url"));
return OAuthAttributes.builder()
.name((String) profile.get("nickname"))
.email((String) kakaoAccount.get("email") )
.id( String.valueOf( attributes.get(userNameAttributeName) ) )
.picture((String) profile.get("thumbnail_image_url") )
.attributes(attributes)
.nameAttributeKey(userNameAttributeName)
.build();
}
}
Java
복사
데이터
•
UserMapper.xml
•
UserMapper.java
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.aloha.kakaocustom.mapper.UserMapper">
<resultMap type="Users" id="userMap">
<id property="id" column="id" />
<result property="id" column="id" />
<result property="username" column="username" />
<result property="password" column="password" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="profile" column="profile" />
<result property="enabled" column="enabled" />
<result property="createdAt" column="created_at" />
<result property="updatedAt" column="updated_at" />
<collection property="authList" resultMap="authMap"></collection>
</resultMap>
<resultMap type="UserAuth" id="authMap">
<result property="id" column="id" />
<result property="username" column="username" />
<result property="auth" column="auth" />
</resultMap>
<!-- 로그인 - username -->
<select id="login" resultMap="userMap">
SELECT u.id
,u.username
,password
,name
,email
,profile
,enabled
,created_at
,updated_at
,auth
FROM user u LEFT OUTER JOIN user_auth auth ON u.username = auth.username
WHERE u.username = #{username}
</select>
<!-- 회원 조회 - id -->
<select id="select" resultMap="userMap">
SELECT u.id
,u.username
,password
,name
,email
,profile
,enabled
,created_at
,updated_at
,auth
FROM user u LEFT OUTER JOIN user_auth auth ON u.username = auth.username
WHERE u.id = #{id}
</select>
<!-- 회원 가입 -->
<insert id="join">
INSERT INTO user ( id, username, password, name, email, profile )
VALUES ( UUID(), #{username}, #{password}, #{name}, #{email}, #{profile} )
</insert>
<!-- 회원 정보 수정 -->
<update id="update">
UPDATE user
SET name = #{name}
,email = #{email}
,profile = #{profile}
,updated_at = now()
WHERE username = #{username}
</update>
<!-- 회원 권한 등록 -->
<insert id="insertAuth">
INSERT INTO user_auth( id, username, auth )
VALUES ( UUID(), #{username}, #{auth} )
</insert>
<!-- 소셜 회원 가입 -->
<insert id="insertSocial">
INSERT INTO user_social ( id, username, provider, social_id, name, email, picture )
VALUES ( UUID(), #{username}, #{provider}, #{socialId}, #{name}, #{email}, #{picture} )
</insert>
<!-- 소셜 회원 조회 -->
<select id="selectSocial" resultType="UserSocial">
SELECT *
FROM user_social
WHERE provider = #{provider}
AND social_id = #{socialId}
</select>
<!-- 소셜 회원 수정 -->
<update id="updateSocial">
UPDATE user_social
SET username = #{username}
,provider = #{provider}
,social_id = #{socialId}
,name = #{name}
,email = #{email}
,picture = #{picture}
,updated_at = #{updatedAt}
WHERE provider = #{provider}
AND social_id = #{socialId}
</update>
<!-- 소셜정보로 회원 조회 -->
<select id="selectBySocial" resultMap="userMap">
SELECT u.id
,u.username
,password
,name
,email
,profile
,enabled
,created_at
,updated_at
,auth
FROM user u
LEFT OUTER JOIN user_auth auth ON u.username = auth.username
WHERE u.username = (
SELECT username
FROM user_social
WHERE provider = #{provider}
AND social_id = #{socialId}
)
</select>
</mapper>
XML
복사
UserMapper.java
@Mapper
public interface UserMapper {
// 로그인
public Users login(String username) throws Exception;
// 조회
public Users select(String id) throws Exception;
// 회원 가입
public int join(Users user) throws Exception;
// 회원 수정
public int update(Users user) throws Exception;
// 회원 권한 등록
public int insertAuth(UserAuth userAuth) throws Exception;
// 소셜 회원 가입
public int insertSocial(UserSocial userSocial) throws Exception;
// 소셜 회원 조회
public UserSocial selectSocial(UserSocial userSocial) throws Exception;
// 소셜 회원 수정
public int updateSocial(UserSocial userSocial) throws Exception;
// 소셜 정보로 회원 조회
public Users selectBySocial(UserSocial userSocial) throws Exception;
}
Java
복사
서비스
•
UserService.java
•
UserServiceImpl.java
•
OAuthService.java
•
OAuthServiceImpl.java
UserService.java
public interface UserService {
// 로그인
public boolean login(Users user) throws Exception;
// 조회
public Users select(String id) throws Exception;
// 회원 가입
public int join(Users user) throws Exception;
// 회원 수정
public int update(Users user) throws Exception;
// 회원 권한 등록
public int insertAuth(UserAuth userAuth) throws Exception;
// 소셜 회원 가입
public int insertSocial(UserSocial userSocial) throws Exception;
// 소셜 회원 조회
public UserSocial selectSocial(UserSocial userSocial) throws Exception;
// 소셜 회원 수정
public int updateSocial(UserSocial userSocial) throws Exception;
// 소셜 정보로 회원 조회
public Users selectBySocial(UserSocial userSocial) throws Exception;
}
Java
복사
UserServiceImpl.java
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public boolean login(Users user) throws Exception {
// // 💍 토큰 생성
String username = user.getUsername(); // 아이디
String password = user.getPassword(); // 암호화되지 않은 비밀번호
UsernamePasswordAuthenticationToken token
= new UsernamePasswordAuthenticationToken(username, password);
// 토큰에 요청 정보 등록
// token.setDetails( new WebAuthenticationDetails(request) );
// 토큰을 이용하여 인증
Authentication authentication = authenticationManager.authenticate(token);
// 인증된 사용자 확인
CustomUser loginUser = (CustomUser) authentication.getPrincipal();
log.info("인증된 사용자 아이디 : " + loginUser.getUser().getUsername());
boolean result = authentication.isAuthenticated();
// 시큐리티 컨텍스트에 등록
SecurityContextHolder.getContext().setAuthentication(authentication);
return result;
}
@Override
public Users select(String id) throws Exception {
Users user = userMapper.select(id);
return user;
}
@Override
public int join(Users user) throws Exception {
String username = user.getUsername();
String password = user.getPassword();
String encodedPassword = passwordEncoder.encode(password); // 🔒 비밀번호 암호화
user.setPassword(encodedPassword);
// 회원 등록
int result = userMapper.join(user);
if( result > 0 ) {
// 회원 기본 권한 등록
UserAuth userAuth = new UserAuth();
userAuth.setUsername(username);
userAuth.setAuth("ROLE_USER");
result = userMapper.insertAuth(userAuth);
}
return result;
}
@Override
public int update(Users user) throws Exception {
int result = userMapper.update(user);
return result;
}
@Override
public int insertAuth(UserAuth userAuth) throws Exception {
int result = userMapper.insertAuth(userAuth);
return result;
}
@Override
public int insertSocial(UserSocial userSocial) throws Exception {
int result = userMapper.insertSocial(userSocial);
return result;
}
@Override
public UserSocial selectSocial(UserSocial userSocial) throws Exception {
UserSocial selectedUserSocial = userMapper.selectSocial(userSocial);
return selectedUserSocial;
}
@Override
public int updateSocial(UserSocial userSocial) throws Exception {
int result = userMapper.updateSocial(userSocial);
return result;
}
@Override
public Users selectBySocial(UserSocial userSocial) throws Exception {
Users user = userMapper.selectBySocial(userSocial);
return user;
}
}
Java
복사
OAuthService.java
/**
* 📄 OAuth2UserService
* - OAuth 2.0 인증 흐름에서 사용자 정보를 처리를 위한 인터페이스
*
* 🎫 loadUser
* ✅ provider(공급자:카카오,네이버,구글)로부터 사용자 정보를 가져와서 OAuth2User 객체로 변환
*/
public interface OAuthService extends OAuth2UserService<OAuth2UserRequest, OAuth2User> {
// ✨👩💼 소셜 회원 가입
public int join(UserSocial userSocial, OAuthAttributes oAuthAttributes) throws Exception;
// 소셜 회원 수정
public int update(UserSocial userSocial, OAuthAttributes oAuthAttributes) throws Exception;
}
Java
복사
OAuthServiceImpl.java
① 최초 로그인
회원 가입
회원 가입② 로그인
정보 갱신
정보 갱신@Slf4j
@Service
public class OAuthServiceImpl implements OAuthService {
@Autowired
private UserMapper userMapper;
/**
* 🎫 loadUser
* ✅ provider(공급자:카카오,네이버,구글)로부터 사용자 정보(OAuth2UserRequest)를
* 가져와서 OAuth2User 객체로 변환하는 메소드
* ① 최초 로그인 ➡ 회원 가입
* ② 로그인 ➡ 정보 갱신
*
* ⭐ 주요 정보
* - 공급자 식별 키 (registrationId)
* - 사용자 식별 속성명 (userNameAttributeName)
* - OAuth 2.0 토큰 속성들 (attributes)
* ⭐ 프로세스
* : 각 provider(공급자)마다 인증 사용자 정보(OAuth2User)에 대한 속성명이 다르기 때문에
* 이를 일원화한 객체(OAuthAttribute)로 만들고, 최종적으로 OAuth2User 로 변환하여 반환
* 1️⃣ 주요 정보(registrationId, userNameAttributeName, attributes) 추출
* 2️⃣ 주요 정보를 인자로 OAuthAttribute 객체 생성
* 3️⃣ 회원 가입 또는 정보 갱신
* 4️⃣ Customuser(⬅OAuth2User) 객체 생성 후 반환
*/
@Transactional
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
log.info("::::::::::::::: OAuthServiceImpl - loadUser() :::::::::::::::");
log.info("OAuth 사용자 정보를 전달받아 OAuth2User 객체로 변환합니다.");
// 1️⃣ 주요 정보 추출
// DefaultOAuth2UserService의 인스턴스를 생성합니다.
OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService = new DefaultOAuth2UserService();
// - userRequest에 따라 OAuth2User 정보를 로드합니다.
// - UserInfo 엔드포인트로부터 사용자 속성 정보를 가져옵니다.
// 그런 다음, 이 정보를 기반으로 OAuth2User 객체를 생성하여 반환합니다.
OAuth2User oAuth2User = oAuth2UserService.loadUser(userRequest);
Map<String, Object> attributes = oAuth2User.getAttributes();
// 🧊 registrationId : 공급자 식별 키 (kakao, naver, google)
// 클라이언트 등록 ID를 가져옵니다.
// userRequest.등록정보가져오기().등록ID가져오기()
String registrationId = userRequest.getClientRegistration().getRegistrationId();
// 🧊 userNameAttributeName : 공급자가 관리하는 사용자 식별 속성명
// 사용자 정보 엔드포인트에서 사용자 이름 속성의 이름을 가져옵니다.
// userRequest.등록정보가져오기().공급자상세정보가져오기().사용자정보엔드포인트가져오기().아이디속성명가져오기()
// ❓ 엔드포인트
// : 서버의 끝점(데이터 접근 및 조작 > 서비스 로직 > 제어 > 끝점)
// ✅ 클라이언트가 서버에 요청을 보낼 수 있는 경로(URL, URI)
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
log.info("★★★★★ 주요 정보 ★★★★★");
log.info("****** registrationId : " + registrationId);
log.info("****** userNameAttributeName : " + userNameAttributeName);
log.info("****** attributes : " + attributes);
// 2️⃣ OAuthAttribute 객체 생성
OAuthAttributes oAuthAttributes = OAuthAttributes.of(registrationId, userNameAttributeName, attributes);
// 일원화된 정보 확인
log.info("****** oAuthAttributes : " + oAuthAttributes);
String nameAttributeKey = oAuthAttributes.getNameAttributeKey();
String name = oAuthAttributes.getName();
String email = oAuthAttributes.getEmail();
String picture = oAuthAttributes.getPicture();
String id = oAuthAttributes.getId();
String provider = "";
name = name == null ? "" : name;
email = email == null ? "" : email;
log.info("****** nameAttributeKey : " + nameAttributeKey);
log.info("****** name : " + name);
log.info("****** email : " + email);
log.info("****** picture : " + picture);
log.info("****** id : " + id);
if( "kakao".equals(registrationId) ) provider = "kakao";
if( "naver".equals(registrationId) ) provider = "naver";
if( "google".equals(registrationId) ) provider = "google";
log.info(":::::::::::::::::::::::::::::::::::::::::::::");
log.info(provider + "로 로그인 합니다.");
log.info(":::::::::::::::::::::::::::::::::::::::::::::");
// 3️⃣ 회원 가입 또는 정보 갱신
UserSocial userSocial = new UserSocial();
userSocial.setProvider(provider);
userSocial.setSocialId(id);
Users joinedUser = null;
try {
joinedUser = userMapper.selectBySocial(userSocial);
} catch (Exception e) {
e.printStackTrace();
}
// ✨👩💼 신규 회원
if( joinedUser == null ) {
log.info("***** 소셜 회원 가입 *****");
try {
join(userSocial, oAuthAttributes);
} catch (Exception e) {
e.printStackTrace();
}
}
// ✅👩💼 기존 회원
// - 기존 회원이면, 소셜 회원 정보 변경 여부 확인 후 소셜 회원 정보 수정
// 1️⃣ user_social 조회 [selectSocial]
// 2️⃣ 정보 변경 여부 확인
// 3️⃣ user_social 수정 [updateSocial]
else {
log.info("***** 소설 회원 정보 갱신 *****");
log.info("joinedUser : " + joinedUser);
// 1️⃣ user_social 조회 [selectSocial]
UserSocial joinedUserSocial = null;
try {
joinedUserSocial = userMapper.selectSocial(userSocial);
} catch (Exception e) {
e.printStackTrace();
}
if( joinedUserSocial != null ) {
try {
// 2️⃣ 정보 변경 여부 확인
// 3️⃣ user_social 수정 [updateSocial]
update(joinedUserSocial, oAuthAttributes);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 👩💻 회원
Users user = new Users();
try {
user = userMapper.selectBySocial(userSocial);
log.info("***** 가입된 소셜 사용자 정보 *****");
log.info(user.toString());
} catch (Exception e) {
e.printStackTrace();
}
return new CustomUser(user, oAuthAttributes);
}
/**
* 1. 가입 여부 확인
* - user (provider, user_social) 조회 [userMapper.selectBySocial]
* - ➡ Users (joinedUser)
* 2. joinedUserSocial 이 null 이면, 회원 가입
* 1️⃣ user 정보 등록
* 2️⃣ user_auth 권한 등록
* 3️⃣ user_social 등록
*/
@Override
public int join(UserSocial userSocial, OAuthAttributes oAuthAttributes) throws Exception {
// 1. 가입 여부 확인
Users joinedUser = userMapper.selectBySocial(userSocial);
// 2. joinedUser 이 null 이면, 회원 가입
int result = 0;
String username = userSocial.getProvider() + "_" + userSocial.getSocialId();
if( joinedUser == null ) {
Users user = new Users();
user.setUsername(username);
user.setName(oAuthAttributes.getName());
user.setEmail(oAuthAttributes.getEmail());
user.setProfile(oAuthAttributes.getPicture());
user.setPassword(UUID.randomUUID().toString());
// 1️⃣ user 정보 등록
result = userMapper.join(user);
// 2️⃣ user_auth 권한 등록
UserAuth userAuth = new UserAuth();
userAuth.setAuth("ROLE_USER");
userAuth.setUsername(username);
userMapper.insertAuth(userAuth);
}
if( result > 0 ) {
// 3️⃣ user_social 등록
UserSocial newUserSocial = new UserSocial();
newUserSocial.setProvider(userSocial.getProvider());
newUserSocial.setSocialId(userSocial.getSocialId());
newUserSocial.setUsername(username);
newUserSocial.setName(oAuthAttributes.getName());
newUserSocial.setEmail(oAuthAttributes.getEmail());
newUserSocial.setPicture(oAuthAttributes.getPicture());
result += userMapper.insertSocial(newUserSocial);
}
return result;
}
/**
* 소셜 회원 정보 수정
*/
@Override
public int update(UserSocial userSocial, OAuthAttributes oAuthAttributes) throws Exception {
int result = 0;
String name = userSocial.getName();
String email = userSocial.getEmail();
String picture = userSocial.getPicture();
if( !name.equals(oAuthAttributes.getName()) ) name = oAuthAttributes.getName();
if( !email.equals(oAuthAttributes.getEmail()) ) email = oAuthAttributes.getEmail();
if( !picture.equals(oAuthAttributes.getPicture()) ) picture = oAuthAttributes.getPicture();
userSocial.setName(name);
userSocial.setEmail(email);
userSocial.setPicture(picture);
result = userMapper.updateSocial(userSocial);
return result;
}
}
Java
복사
스프링 시큐리티 설정
•
~/config/SecurityConfig.java•
~/config/CommonConfig.java
~/config/SecurityConfig.java
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Autowired
private OAuthService oAuthService;
/**
* 🔐 스프링 시큐리티 설정 메소드
* @param http
* @return
* @throws Exception
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 👩💼 인가 설정
http.authorizeRequests(requests -> requests
.antMatchers("/").permitAll()
.anyRequest().authenticated());
// 👩💻🔐 OAuth2 로그인
// ✅ userInfoEndpoint() : 사용자 정보 설정 객체 가져오기
// ✅ userService(oAuthService) : 사용자 정보 설정 객체로, 로그인 후 처리할 구현 클래스 등록
http.oauth2Login(login -> login
.userInfoEndpoint()
.userService(oAuthService));
return http.build();
}
}
Java
복사
~/config/CommonConfig.java
@Configuration
public class CommonConfig {
/**
* 🍃 암호화 방식 빈 등록
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 🍃 AuthenticationManager 빈 등록
* @param authenticationConfiguration
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
Java
복사
요청 경로 매핑
•
~/controller/HomController.java◦
메인 화면
▪
: /▪
index.html
~/controller/HomController.java
@Slf4j
@Controller
public class HomeController {
/**
* 메인 화면
* 🔗 [GET] - /
* 📄 index.html
* @return
*/
@GetMapping("/")
public String home(@AuthenticationPrincipal OAuth2User oauth2User
,Model model) {
log.info(":::::::::: 메인 화면 ::::::::::");
CustomUser customUser = (CustomUser) oauth2User;
model.addAttribute("user", customUser);
return "/index";
}
}
Java
복사
index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OAuth</title>
<!-- bootstrap css -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container col-10 col-lg-4">
<div class="px-4 py-5 mt-5 text-center">
<h1 class="display-5 fw-bold text-body-emphasis">메인 화면</h1>
</div>
<!-- 비 로그인 시 -->
<th:block sec:authorize="isAnonymous()">
<div class="d-grid gap-2">
<a href="/login" class="btn btn-lg btn-primary">로그인</a>
</div>
</th:block>
<!-- 로그인 시 -->
<th:block sec:authorize="isAuthenticated()">
<div class="card">
<div class="inner p-4">
<div class="d-flex flex-column align-items-center">
<div class="item my-2">
<img th:src="${user.profile}" alt="프로필" class="rounded-circle">
</div>
<div class="item my-2">
<h3 th:text="${user.name}"></h3>
</div>
<div class="item my-2">
<h5 th:text="${user.email}"></h5>
</div>
<div class="item my-2 w-100">
<span sec:authentication="principal">인증된 사용자</span>
</div>
</div>
</div>
</div>
<form action="/logout" method="post">
<!-- CSRF TOKEN -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
<div class="d-grid gap-2">
<button type="submit" class="btn btn-lg btn-primary">로그아웃</button>
</div>
</form>
</th:block>
</div>
<!-- bootstrap js -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
HTML
복사


























