Spring - Security - oAuth
Spring - Security - oAuth
oAuth
oAuth๋?
OAuth๋ ์ธํฐ๋ท ์ฌ์ฉ์๋ค์ด ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๊ณตํ์ง ์๊ณ ๋ค๋ฅธ ์น์ฌ์ดํธ ์์ ์์ ๋ค์ ์ ๋ณด์ ๋ํด ์น์ฌ์ดํธ๋ ์ ํ๋ฆฌ์ผ์ด์
์ ์ ๊ทผ ๊ถํ์ ๋ถ์ฌํ ์ ์๋ ๊ณตํต์ ์ธ ์๋จ์ผ๋ก์ ์ฌ์ฉ๋๋, ์ ๊ทผ ์์์ ์ํ ๊ฐ๋ฐฉํ ํ์ค์ด๋ค. (์ํค๋ฐฑ๊ณผ)
oAuth๋ฅผ ํตํด google , kakao , facebook ๊ณผ ๊ฐ์ ์์ ๋ก๊ทธ์ธ ์ ๊ตฌํ ํ ์ ์๋ค.
Dependency ์ถ๊ฐ
gradle ๋น๋ ๊ธฐ์ค
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
build.gradle ์ ์์ ๊ฐ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ๋๋ ํ๋ก์ ํธ ์ ํ ์ ์ํ๋ฆฌํฐ ์ฒดํฌ๋ฅผ ํด์ค๋ค.
google ๋ก๊ทธ์ธ
๋ํ์ ์ธ ๊ตฌ๊ธ ๋ก๊ทธ์ธ์ ๊ตฌํํ๋ ค๊ณ ํ๋ค.
๊ตฌ๊ธ ํค ๋ฐ๊ธ
application ์ถ๊ฐ
yml
server:
port: 8080
servlet:
context-path: /
encoding:
charset: UTF-8
enabled: true
force: true
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Seoul
username: cos
password: cos1234
mvc:
view:
prefix: /templates/
suffix: .html
jpa:
hibernate:
ddl-auto: update #create update none
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
security:
oauth2:
client:
registration:
google:
client-id: [google-client-id]
client-secret: [google-client-secret]
scope:
- email
- profile
# kakao:
# client-id: [kakao-client-id]
# client-secret: [kakao-client-secret]
# scope:
# - email
# - profile
.yml ๋๋ .properties์ security.oauth2.client.registration.~~ ๋ฅผ ์์ฑํ์ฌ ํ์ํ ํค๊ฐ์ ์์ฑํด์ค๋ค.
SecurityConfig
์ผ๋ฐ ์ํ๋ฆฌํฐ ๋ก๊ทธ์ธ์์ ์์ฑํ๋ Config์ ์ถ๊ฐ๋ก ์ฝ๋๋ฅผ ์ ๋ ฅํ์ฌ ์ค๋ค.
@Autowired
private PrincipalOauth2UserService principalOauth2UserService;
.....
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/user/**").authenticated() // ์ธ์ฆ๋ง ๋๋ฉด ๋ค์ด๊ฐ ์ ์๋ ์ฃผ์!!
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll()
.and()
.formLogin() // ๊ธฐ๋ณธ ์ธ์ฆ ๋ก๊ทธ์ธ ํ์ด์ง
.loginPage("/login")
.usernameParameter("username") // ์์์ ์ ์ ํ๋ผ๋ฉํฐ๊ฐ
.passwordParameter("password") // ์์์ ์ ์ ํจ์ค์๋๊ฐ
.loginProcessingUrl("/login") // ์ํ๋ฆฌํฐ๊ฐ ์ฃผ์๋ฅผ ๋์์ฑ ์ํ๋ฆฌํฐ ๋ก๊ทธ์ธ์ ์งํํด์ค๋๋ค.
.defaultSuccessUrl("/")
.and()
.oauth2Login() // OAuth ๋ก๊ทธ์ธ
.loginPage("/login")
.userInfoEndpoint()
.userService(principalOauth2UserService);
// ๊ตฌ๊ธ ๋ก๊ทธ์ธ์ด ์๋ฃ๋ ๋ค์ ํ์ฒ๋ฆฌ๊ฐ ํ์ํจ. Tip.์ฝ๋ X , (์์ธ์คํ ํฐ+์ฌ์ฉ์ํ๋กํ์ ๋ณด O )
}
์์ ๊ฐ์ด oauth2Login() ์ ์์ฑํ์ฌ ์ฝ๊ฒ OAuth2 ๋ก๊ทธ์ธ ์ฝ๋๋ฅผ ์์ฑ ํ ์ ์๋ค.
ํ์ง๋ง ๋ก๊ทธ์ธ์ด ์๋ฃ๋ํ ์ผ๋ฐ ๋ก๊ทธ์ธ๊ณผ๋ ๋ค๋ฅด๊ฒ ํ์ฒ๋ฆฌ๊ฐ ํ์ํ๋ค.
PrincipalOauth2UserService
์ผ๋ฐ ๋ก๊ทธ์ธ์ ์ ์ ๊ฐ์ฒด์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ํ์๊ฐ์ ์ ํ๊ณ ๊ฐ์ ์ ์ ๊ฐ์ฒด๋ฅผ ์ํ๋ฆฌํฐ ์ธ์ ์ ๋ฃ์ด์ค ๋ก๊ทธ์ธ์ ํ๊ฒ ๋๋ค.
ํ์ง๋ง OAuth ๋ก๊ทธ์ธ ๊ฐ์ ๊ฒฝ์ฐ ํ์ฌ ์ฌ์ดํธ์์ ์ฌ์ฉํ๊ณ ์๋ ๋ฐ์ดํฐ์ ๋ง์ง ์๊ณ ๋ ์ถ๊ฐ์ ์ธ ๋ฐ์ดํฐ๊ฐ ํ์ ํ ์ ์๋ค.
์ด๋ฌํ ํ์ฒ๋ฆฌ ์์ ์ PrincipalOauth2UserService ์์ ์ํํ๊ฒ ๋๋ฉฐ DefaultOAuth2UserService ์ ์์๋ฐ์ loadUser๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉํ์ฌ ์ฌ์ฉํ๋ค.
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService{
@Autowired
private UserRepository userRepository;
// ๊ตฌ๊ธ๋ก ๋ถํฐ ๋ฐ์ userRequest ๋ฐ์ดํฐ์ ๋ํ ํ์ฒ๋ฆฌ๋๋ ํจ์
// ํจ์ ์ข
๋ฃ์ @AuthenticaitonPrincipal ์ด๋
ธํ
์ด์
์ด ๋ง๋ค์ด์ง๋ค
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
System.out.println("getClientRegistration : "+userRequest.getClientRegistration()); // registrationId๋ก ์ด๋ค OAuth๋ก ๋ก๊ทธ์ธํ๋์ง ํ์ธ
System.out.println("getAccessToken : "+userRequest.getAccessToken().getTokenValue());
OAuth2User oauth2User = super.loadUser(userRequest);
// ๊ตฌ๊ธ๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ -> ๊ตฌ๊ธ๋ก๊ทธ์ธ์ฐฝ -> ๋ก๊ทธ์ธ ์๋ฃ -> code๋ฅผ ๋ฆฌํด(OAuth-client๋ผ์ด๋ธ๋ฌ๋ฆฌ) -> AccessToken ์
// userRequest ์ ๋ณด -> loadUserํจ์ ํธ์ถ -> ๊ตฌ๊ธ๋ก๋ถํฐ ํ์ํ๋กํ ๋ฐ์์ค๋ค.
System.out.println("getAttributes : "+oauth2User.getAttributes());
// User Entity ๋ฐ์ดํฐ ํ์ฒ๋ฆฌ ์์
=> ํ์๊ฐ์
String provider = userRequest.getClientRegistration().getRegistrationId(); // Google
String providerId = oauth2User.getAttribute("sub");
String username = provider+"_"+providerId; // Google_123123213231231231213213
String password = new BCryptPasswordEncoder().encode("๊ฒ์ธ๋ฐ์ด");
String email = oauth2User.getAttribute("email");
String role = "ROLE_USER";
User userEntity = userRepository.findByUsername(username);
if(userEntity == null) {
// ํ์๊ฐ์
์งํ
userEntity = User.builder()
.username(username)
.password(password)
.email(email)
.role(role)
.provider(provider)
.providerId(providerId)
.build();
userRepository.save(userEntity);
System.out.println("์ต์ด ๊ตฌ๊ธ ๋ก๊ทธ์ธ์ ํ์
จ์ต๋๋ค.");
}else{
System.out.println("๊ตฌ๊ธ ๋ก๊ทธ์ธ์ ํ์ ์ ์ด ์์ต๋๋ค.");
}
return new PrincipalDetails(userEntity, oauth2User.getAttributes());
}
}
OAuth ๋ก๊ทธ์ธ์ User ๊ฐ์ด ์กด์ฌํ์ง์๊ธฐ์ ๋์ด์ค๋ ๋ฐ์ดํฐ๋ก User ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค์ด ํ์๊ฐ์ ๊ณผ ๋ก๊ทธ์ธ์ ํ ์ ์๋ค.
PrincipalDetails
์ด์ ๋ก๊ทธ์ธ์ด ์ปจํธ๋กค๋ฌ์์ ์ธ์ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ ์ฌ์ฉ ํ ํ ๋ฐ ์ฌ๊ธฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ฒ๋๋ค.
๋ก๊ทธ์ธ์ ์ํ๋ฆฌํฐ ์ธ์ ์๋ Authentication ๊ฐ์ฒด๊ฐ ๋ค์ด๊ฐ๊ฒ๋๋๋ฐ ๊ฑฐ๊ธฐ์ ๋ฃ์์ ์๋ ๋ฐ์ดํฐ๋ก UserDetails์ OAuth2User 2๊ฐ์ง๋ง ๋ค์ด ๊ฐ ์ ์๋ค.
UserDetails์ ์ผ๋ฐ ๋ก๊ทธ์ธ ์ , OAuth2User๋ OAuth2 ๋ก๊ทธ์ธ ์ ์์ฑ๋๋๋ฐ ์๋ ์ฝ๋์ ๊ฐ์ด ์ปจํธ๋กค๋ฌ์์ ๋ฐ์ ์ฌ์ฉ ํ ์ ์๋ค.
ํ์ง๋ง ์ ๋ ๊ฒ 2๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ์ธ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์ค๋ณต์ฝ๋๋ฅผ ์์ฑํด์ผํ๋ฉฐ ๋นํจ์จ์ ์ธ ์ฝ๋๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ค.
@GetMapping("test/login")
@ResponseBody
public String loginTest(Authentication authentication, @AuthenticationPrincipal UserDetails userDetail) { // DI(์์กด์ฑ ์ฃผ์
)
System.out.println((UserDetails)authentication.getPrincipal()); // Authentication
System.out.println(userDetail); // @AuthenticationPrincipal
return "์ธ์
์ ๋ณด ํ์ธํ๊ธฐ";
}
@GetMapping("test/oauth/login")
@ResponseBody
public String oauthLoginTest(Authentication authentication, @AuthenticationPrincipal OAuth2User oauth) { // DI(์์กด์ฑ ์ฃผ์
)
System.out.println((OAuth2User)authentication.getPrincipal()); // Authentication
System.out.println(oauth); // @AuthenticationPrincipal
return "OAuth ์ธ์
์ ๋ณด ํ์ธํ๊ธฐ";
}
์ด๋ฌํ ๋ฐฉ๋ฒ์ ๊ฐ์ฒด์งํฅ์ ํตํด ํด๊ฒฐํ ์ ์๋๋ฐ class๋ ์์๊ณผ ๊ตฌํ์ ํตํด ์ค๋ฒ๋ผ์ด๋ฉ๊ณผ ์ค๋ฒ๋ก๋ฉ์ ํ ์ ์๋ค.
์ฌ๊ธฐ์ implements๋ก UserDetails๊ณผ OAuth2User๋ฅผ ๊ตฌํํ์ฌ ์ฌ์ฉํ๋ค๋ฉด ํ๋์ ํด๋์ค๋ก ๋๊ฐ์ง ์ธํฐํ์ด์ค๋ฅผ ๊ฐ์ง ์ ์๊ฒ๋๋ค.
์ด๋ ๊ฒ ์์ฑ๋ ํด๋์ค๋ ์ธํฐํ์ด์ค ๊ธฐ๋ฐ์ผ๋ก Authentication ์ธ์ ์ ๋ค์ด๊ฐ ์ ์๋ ๊ฐ์ฒด๊ฐ ๋๋ค.
@ToString
@Setter
@Getter
public class PrincipalDetails implements UserDetails , OAuth2User{
private User user;
private Map<String , Object> attributes;
// ์ผ๋ฐ ๋ก๊ทธ์ธ
public PrincipalDetails(User user) {
this.user = user;
};
//OAuth ๋ก๊ทธ์ธ
public PrincipalDetails(User user , Map<String , Object> attributes) {
this.user = user;
this.attributes = attributes;
};
.....
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public String getName() {
return null;
}
์์ ๊ฐ์ด 2๊ฐ์ง ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ์ฌ ํด๋์ค๋ฅผ ์์ฑํ๊ณ ์์ฑ์๋ฅผ ํตํด ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์๊ฒ๋๋ค.
ํ๋ ํด๋์ค๋ก ๊ตฌํ๋ Authentication ๊ฐ์ฒด๋ ์๋์ ๊ฐ์ด PrincipalDetailsํ์ ์ผ๋ก ๋ฐ์ ์ฌ์ฉ ํ ์ ์๋ค.
@GetMapping("user")
public String user(Model model , @AuthenticationPrincipal PrincipalDetails principalDetails) {
System.out.println("principalDetails : "+principalDetails.getUser());
model.addAttribute("success", "user");
return "index";
}
Leave a comment