Search

์นด์นด์˜ค ๋กœ๊ทธ์ธ - ํšŒ์›๊ฐ€์ž…

์นด์นด์˜ค ๋กœ๊ทธ์ธ - ํšŒ์›๊ฐ€์ž…

์นด์นด์˜ค ๋กœ๊ทธ์ธ - 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
๋ณต์‚ฌ