Search
Duplicate
moon
sun

카카오 로그인 - 회원가입

카카오 로그인 - 회원가입

📕 카카오 로그인 - INDEX

프로젝트 목표

👩🏼‍💻
Spring Security, OAuth2 Client 의존성을 사용하여, OAuth 인증 성공 시, 회원 가입 처리를 구현

Code

Preview

작업 프로세스

3.

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.properties
4.
테이블 정의
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.java
9.
요청 경로 매핑
📄 ~/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.x
plugins { 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
복사