์นด์นด์ค ๋ก๊ทธ์ธ - ํ์๊ฐ์
ํ๋ก์ ํธ ๋ชฉํ
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
๋ณต์ฌ