๐Ÿ’ Spring/Spring Security

[Spring Security] ์Šคํ”„๋ง ๋ถ€ํŠธ OAuth2-client๋ฅผ ์ด์šฉํ•œ ์†Œ์…œ(๊ตฌ๊ธ€, ๋„ค์ด๋ฒ„, ์นด์นด์˜ค) ๋กœ๊ทธ์ธ ํ•˜๊ธฐ

iseunghan 2021. 5. 6. 00:20
๋ฐ˜์‘ํ˜•

์ €๋ฒˆ ์‹œ๊ฐ„์—๋Š” ์ง์ ‘ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์š”์ฒญ์„ ๊ตฌํ˜„ํ•˜์—ฌ์„œ OAuth2 ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•ด๋ดค์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ์‹œ๊ฐ„์—๋Š” OAuth2-client ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•ด์„œ, ์†Œ์…œ ๋กœ๊ทธ์ธ API๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๊ฐœ๋ฐœ ํ™˜๊ฒฝ

  • IntelliJ IDEA
  • Spring Boot 2.4.4
  • Java 11
  • Spring JPA
  • Maven 3.6.3

 

Maven ์˜์กด์„ฑ ์ถ”๊ฐ€

spring-boot-starter-oauth2-client๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๊ตฌ๊ธ€,ํŽ˜์ด์Šค๋ถ ๊ฐ™์€ ๋กœ๊ทธ์ธ์„ ํ†ตํ•œ ์ธ์ฆ๊ณผ ๊ถŒํ•œ ์ฒ˜๋ฆฌ๋ฅผ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค. 

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

spring security ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋ฉด, ์Šคํ”„๋ง ๋ถ€ํŠธ๋Š” ๊ทธ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์ž๋™์„ค์ •์„ ์ ์šฉ์„ ํ•ด์ค˜์„œ, ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด์„œ ์ธ์ฆ์„ ํ•„์š”๋กœ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. (์ผ๋ถ€ url์„ ํ—ˆ์šฉํ•˜๋ ค๋ฉด, WebSecurityConfigurerAdaptre๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์„ค์ •์„ ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์€ ์•„๋ž˜์—์„œ ์ž์„ธํ•˜๊ฒŒ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.)

 

application-oauth.properties ์ž‘์„ฑ + .gitignore ๋“ฑ๋กํ•˜๊ธฐ

application.properties ์™€ ๊ฐ™์€ ์œ„์น˜์— ์ƒˆ๋กœ์šด ํŒŒ์ผ์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

application-oauth.properties๋ผ๋Š” ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ , ์ด์ „์— ๋ฐœ๊ธ‰๋ฐ›์€ ํด๋ผ์ด์–ธํŠธ ID์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค.

 

application-oauth.properties

๋Œ€ํ‘œ์ ์œผ๋กœ spring security์—์„œ ์ œ๊ณตํ•˜๋Š” oauth2-client ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—๋Š” ์œ ๋ช…ํ•œ google, facebook, twitter ๋“ฑ ์›น์‚ฌ์ดํŠธ์— ๋Œ€ํ•œ provider๋“ค์€ ์ œ๊ณต์„ ํ•ด์ฃผ์ง€๋งŒ, ์šฐ๋ฆฌ๋‚˜๋ผ์—์„œ๋งŒ ํ•œ์ •์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋„ค์ด๋ฒ„๋‚˜, ์นด์นด์˜ค ๊ฐ™์€ ์„œ๋น„์Šค์— ๋Œ€ํ•œ ์ •๋ณด๋“ค์„ ์ œ๊ณตํ•ด์ฃผ์ง€ ๋ชปํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ๋“ฑ๋ก์„ ํ•ด์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค!

# GOOGLE
spring.security.oauth2.client.registration.google.client-id = [ํด๋ผ์ด์–ธํŠธ id]
spring.security.oauth2.client.registration.google.client-secret = [ํด๋ผ์ด์–ธํŠธ pw]
spring.security.oauth2.client.registration.google.scope = profile, email

# ๊ตฌ๊ธ€์ด๋‚˜ ํŽ˜์ด์Šค๋ถ์€ ์•ˆ์ ์–ด๋„ ๋˜๋Š”๋ฐ, ๋„ค์ด๋ฒ„๋‚˜ ์นด์นด์˜ค๋Š” ์ ์–ด์ค˜์•ผํ•จ(๊ธฐ๋ณธ ์ œ๊ณต provider๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์—)
spring.security.oauth2.client.registration.naver.client-id = [ํด๋ผ์ด์–ธํŠธ id]
spring.security.oauth2.client.registration.naver.client-secret= [ํด๋ผ์ด์–ธํŠธ pw]
spring.security.oauth2.client.registration.naver.client-name=Naver
spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.naver.redirect-uri=http://localhost:8080/login/oauth2/code/naver

# Naver Provider ๋“ฑ๋ก!
spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user-name-attribute=response # ๋„ค์ด๋ฒ„๊ฐ€ ํšŒ์›์ •๋ณด๋ฅผ json์œผ๋กœ ๋„˜๊ฒจ์ฃผ๋Š”๋ฐ, response๋ผ๋Š” ํ‚ค๊ฐ’์œผ๋กœ ๋ฆฌํ„ดํ•ด์ค€๋‹ค.

# KAKAO
spring.security.oauth2.client.registration.kakao.client-id = [ํด๋ผ์ด์–ธํŠธ id]
spring.security.oauth2.client.registration.kakao.client-secret = [ํด๋ผ์ด์–ธํŠธ pw]
spring.security.oauth2.client.registration.kakao.redirect-uri=http://localhost:8080/login/oauth2/code/kakao
spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.kakao.scope=profile,account_email
spring.security.oauth2.client.registration.kakao.client-name=kakao
spring.security.oauth2.client.registration.kakao.client-authentication-method=POST

## kAKAO Provider ๋“ฑ๋ก!
spring.security.oauth2.client.provider.kakao.authorization-uri= https://kauth.kakao.com/oauth/authorize
spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token
spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me
spring.security.oauth2.client.provider.kakao.user-name-attribute=id # ์นด์นด์˜ค๊ฐ€ ํšŒ์›์ •๋ณด๋ฅผ json์œผ๋กœ ๋„˜๊ฒจ์ฃผ๋Š”๋ฐ, id๋ผ๋Š” ํ‚ค๊ฐ’์œผ๋กœ ๋ฆฌํ„ดํ•ด์ค€๋‹ค.

 

application.properties ๋“ฑ๋ก

ํŒŒ์ผ๋ช…์„ application-XXX.properties ๋กœ ์ง€์œผ๋ฉด, XXX ๋ถ€๋ถ„์„ ์•„๋ž˜์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

# application-oauth.properties
spring.profiles.includes = oauth

 

.gitignore

๋ฐœ๊ธ‰ ๋ฐ›์€ ํด๋ผ์ด์–ธํŠธ id, pw๊ฐ€ ๋“ค์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— .gitignore์— ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

 

์†Œ์…œ ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‹ด์„ ๋ณ„๋„์˜ User ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false)
    private String email;
    private String picture;
    private String role = "ROLE_USER";

    public User(String name, String email, String picture) {
        this.name = name;
        this.email = email;
        this.picture = picture;
    }

    public User update(String name, String picture) {
        this.name = name;
        this.picture = picture;

        return this;
    }

   // .. getter, setter ์ƒ๋žต
}

 

UserRepository๋„ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.

email์„ ์ด์šฉํ•ด์„œ ์ฐพ์„ ์ˆ˜ ์žˆ๋„๋ก findByEmail ๋„ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

 

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์„ค์ •

@Configuration
@EnableWebSecurity  // ํ•ด๋‹น ์• ๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ธ ํ•„ํ„ฐ(ํ˜„์žฌ ํด๋ž˜์Šค)๋ฅผ ์Šคํ”„๋ง ํ•„ํ„ฐ์ฒด์ธ์— ๋“ฑ๋ก.
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	// ์ปค์Šคํ…€ํ•œ OAuth2UserService DI.
    @Autowired
    private CustomOAuth2UserService customOAuth2UserService;

	// encoder๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋ก.
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }


    // WebSecurity์— ํ•„ํ„ฐ๋ฅผ ๊ฑฐ๋Š” ๊ฒŒ ํ›จ์”ฌ ๋น ๋ฆ„. HttpSecrity์— ํ•„ํ„ฐ๋ฅผ ๊ฑธ๋ฉด, ์ด๋ฏธ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ๋‚ด๋ถ€์— ๋“ค์–ด์˜จ ์ƒํƒœ๊ธฐ ๋•Œ๋ฌธ์—..
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().mvcMatchers("/members/**","/image/**");    // /image/** ์žˆ๋Š” ๋ชจ๋“  ํŒŒ์ผ๋“ค์€ ์‹œํ๋ฆฌํ‹ฐ ์ ์šฉ์„ ๋ฌด์‹œํ•œ๋‹ค.
        web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());    // ์ •์ ์ธ ๋ฆฌ์†Œ์Šค๋“ค์— ๋Œ€ํ•ด์„œ ์‹œํ๋ฆฌํ‹ฐ ์ ์šฉ ๋ฌด์‹œ.
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                    .anyRequest()	// ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด์„œ ํ—ˆ์šฉํ•˜๋ผ.
                    .permitAll()
                .and()
                    .logout()
                    .logoutSuccessUrl("/")	// ๋กœ๊ทธ์•„์›ƒ์— ๋Œ€ํ•ด์„œ ์„ฑ๊ณตํ•˜๋ฉด "/"๋กœ ์ด๋™
                .and()
                    .oauth2Login()
                    .defaultSuccessUrl("/login-success")
                    .userInfoEndpoint()
                    .userService(customOAuth2UserService);	// oauth2 ๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ•˜๋ฉด, ์œ ์ € ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์šฐ๋ฆฌ๊ฐ€ ์ƒ์„ฑํ•œ
             	       						// customOAuth2UserService์—์„œ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ฒ ๋‹ค. ๊ทธ๋ฆฌ๊ณ  "/login-success"๋กœ ์ด๋™ํ•˜๋ผ.
    }
}
  • @EnableWebSecurity
    • @Configuration ์• ๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ธ ํด๋ž˜์Šค์— ๋ถ™์ด๊ณ ,  WebSecurityConfigurerAdapter๋ฅผ ์ƒ์†๋ฐ›์œผ๋ฉด, Spring Security ์„ค์ • ํด๋ž˜์Šค๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
  • BCryptPasswordEncoder๋Š” Spring Security์—์„œ ์ œ๊ณตํ•˜๋Š” ์•”ํ˜ธํ™” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
  • configure(WebSecurity web)
    • websecurity๋Š” FilterChainProxy๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•„ํ„ฐ์ž…๋‹ˆ๋‹ค.
    • web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
      • /css/** , /js/** ๊ฐ™์€ ์ •์  ๋ฆฌ์†Œ์Šค ํŒŒ์ผ๋“ค์€ Spring Security๊ฐ€ ๋ฌด์‹œํ•  ์ˆ˜ ์žˆ๋„๋ก, ํ†ต๊ณผ์‹œ์ผœ์ฃผ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • configure(HttpSecurity http)
    • httpSecurity๋ฅผ ํ†ตํ•ด HTTP ์š”์ฒญ์— ๋Œ€ํ•œ ์›น ๊ธฐ๋ฐ˜ ๋ณด์•ˆ์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • autorizeRequests()
      • .antMatchers("/**").permitAll().hasRole("ADMIN"); ์ด๋ ‡๊ฒŒ ํ•ด์ฃผ๋ฉด, "/**" ๋กœ ๋“ค์–ด์˜จ ์š”์ฒญ์— ๋Œ€ํ•ด์„œ ๊ถŒํ•œ์ด ADMIN์ผ ๋•Œ, ํ—ˆ์šฉ์„ ํ•ด์ฃผ๊ฒ ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค.
      • .anyRequest().permitAll() -> ๋ชจ๋“  ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด์„œ ํ—ˆ์šฉํ•˜๊ฒ ๋‹ค.
      • .anyRequest().authenticated() -> ๋ชจ๋“  ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด์„œ ์ธ์ฆ ์š”์ฒญํ•˜๊ฒ ๋‹ค.
    • formlogin()
      • HttpSession์„ ์ด์šฉํ•œ, form ๋กœ๊ทธ์ธ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค.
      • "/login"์— ์ ‘๊ทผํ•˜๋ฉด, Spring security๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋กœ๊ทธ์ธ์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      • .loginPage("/customLogin")
        • ์ปค์Šคํ…€ ๋กœ๊ทธ์ธ ํผ์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ํ•ด๋‹น ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
      • .defaultSuccessUrl("/{success-url}")
        • ๋กœ๊ทธ์ธ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ, ์ด๋™๋˜๋Š” ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค.
    •  logout()
      • ๋กœ๊ทธ์•„์›ƒ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค.
      • "/logout"์— ์ ‘๊ทผํ•˜๋ฉด, Spring Security๊ฐ€ Http ์„ธ์…˜์„ ์ œ๊ฑฐํ•ด์ค๋‹ˆ๋‹ค.
      • .logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
        • ๋กœ๊ทธ์•„์›ƒ์˜ url์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      • .exceptionHandling().accessDeniedPage("/logout/denied")
        • ๋กœ๊ทธ์•„์›ƒ ์‹œ ์˜ˆ์™ธ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ํ•ด๋‹น ๋ฉ”์†Œ๋“œ๋กœ ํ•ธ๋“ค๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • oauth2Login()
      • ouath2 ๋กœ๊ทธ์ธ์„ ํ•  ๋•Œ, ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค.
      • .defaultSuccessUrl("/{login-success-url}")
        • oauth2 ์ธ์ฆ์ด ์„ฑ๊ณตํ–ˆ์„ ๋•Œ, ์ด๋™๋˜๋Š” url์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      • userInfoEndPoint().userService(customOAuth2UserService);
        • ๋กœ๊ทธ์ธ์ด ์„ฑ๊ณตํ•˜๋ฉด, ํ•ด๋‹น ์œ ์ €์˜ ์ •๋ณด๋ฅผ ๋“ค๊ณ  customOAuth2UserService์—์„œ ํ›„์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ๊ฒ ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค.

 

OAuth2UserService ๊ตฌํ˜„ํ•˜๊ธฐ

package me.isunghan.loginspring.security;

import me.isunghan.loginspring.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpSession;
import java.util.Collections;

@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private HttpSession httpSession;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
        OAuth2UserService oAuth2UserService = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = oAuth2UserService.loadUser(oAuth2UserRequest);

        // ํ˜„์žฌ ์ง„ํ–‰์ค‘์ธ ์„œ๋น„์Šค๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ๋ฌธ์ž์—ด๋กœ ๋ฐ›์Œ. oAuth2UserRequest.getClientRegistration().getRegistrationId()์— ๊ฐ’์ด ๋“ค์–ด์žˆ๋‹ค. {registrationId='naver'} ์ด๋Ÿฐ์‹์œผ๋กœ
        String registrationId = oAuth2UserRequest.getClientRegistration().getRegistrationId();

        // OAuth2 ๋กœ๊ทธ์ธ ์‹œ ํ‚ค ๊ฐ’์ด ๋œ๋‹ค. ๊ตฌ๊ธ€์€ ํ‚ค ๊ฐ’์ด "sub"์ด๊ณ , ๋„ค์ด๋ฒ„๋Š” "response"์ด๊ณ , ์นด์นด์˜ค๋Š” "id"์ด๋‹ค. ๊ฐ๊ฐ ๋‹ค๋ฅด๋ฏ€๋กœ ์ด๋ ‡๊ฒŒ ๋”ฐ๋กœ ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„์„œ ๋„ฃ์–ด์ค˜์•ผํ•จ.
        String userNameAttributeName = oAuth2UserRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();

	// OAuth2 ๋กœ๊ทธ์ธ์„ ํ†ตํ•ด ๊ฐ€์ ธ์˜จ OAuth2User์˜ attribute๋ฅผ ๋‹ด์•„์ฃผ๋Š” of ๋ฉ”์†Œ๋“œ.
        OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());

        User user = saveOrUpdate(attributes);
        httpSession.setAttribute("user", new SessionUser(user));

        System.out.println(attributes.getAttributes());
        return new DefaultOAuth2User(Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))
                , attributes.getAttributes()
                , attributes.getNameAttributeKey());
    }

    // ํ˜น์‹œ ์ด๋ฏธ ์ €์žฅ๋œ ์ •๋ณด๋ผ๋ฉด, update ์ฒ˜๋ฆฌ
    private User saveOrUpdate(OAuthAttributes attributes) {
        User user = userRepository.findByEmail(attributes.getEmail())
                .map(entity -> entity.update(attributes.getName(), attributes.getPicture()))
                .orElse(attributes.toEntity());

        return userRepository.save(user);
    }
}

 

OAuth2Attributes ํด๋ž˜์Šค ์ƒ์„ฑ

OAuth2 ๋กœ๊ทธ์ธ์„ ํ†ตํ•ด์„œ ๊ฐ€์ ธ์˜จ OAuth2User์˜ ์ •๋ณด๋ฅผ ๋‹ด์•„์ฃผ๊ธฐ ์œ„ํ•œ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

public class OAuthAttributes {
    private Map<String, Object> attributes;
    private String nameAttributeKey;
    private String name;
    private String email;
    private String picture;

    public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey, String name, String email, String picture) {
        this.attributes = attributes;
        this.nameAttributeKey = nameAttributeKey;
        this.name = name;
        this.email = email;
        this.picture = picture;
    }

    public OAuthAttributes() {
    }

    // ํ•ด๋‹น ๋กœ๊ทธ์ธ์ธ ์„œ๋น„์Šค๊ฐ€ kakao์ธ์ง€ google์ธ์ง€ ๊ตฌ๋ถ„ํ•˜์—ฌ, ์•Œ๋งž๊ฒŒ ๋งคํ•‘์„ ํ•ด์ฃผ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    // ์—ฌ๊ธฐ์„œ registrationId๋Š” OAuth2 ๋กœ๊ทธ์ธ์„ ์ฒ˜๋ฆฌํ•œ ์„œ๋น„์Šค ๋ช…("google","kakao","naver"..)์ด ๋˜๊ณ ,
    // userNameAttributeName์€ ํ•ด๋‹น ์„œ๋น„์Šค์˜ map์˜ ํ‚ค๊ฐ’์ด ๋˜๋Š” ๊ฐ’์ด๋ฉ๋‹ˆ๋‹ค. {google="sub", kakao="id", naver="response"}
    public static OAuthAttributes of(String registrationId, String userNameAttributeName, Map<String, Object> attributes) {
        if (registrationId.equals("kakao")) {
            return ofKakao(userNameAttributeName, attributes);
        } else if (registrationId.equals("naver")) {
            return ofNaver(userNameAttributeName,attributes);
        }
        return ofGoogle(userNameAttributeName, attributes);
    }

    private static OAuthAttributes ofKakao(String userNameAttributeName, Map<String, Object> attributes) {
        Map<String, Object> kakao_account = (Map<String, Object>) attributes.get("kakao_account");  // ์นด์นด์˜ค๋กœ ๋ฐ›์€ ๋ฐ์ดํ„ฐ์—์„œ ๊ณ„์ • ์ •๋ณด๊ฐ€ ๋‹ด๊ธด kakao_account ๊ฐ’์„ ๊บผ๋‚ธ๋‹ค.
        Map<String, Object> profile = (Map<String, Object>) kakao_account.get("profile");   // ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ profile(nickname, image_url.. ๋“ฑ) ์ •๋ณด๊ฐ€ ๋‹ด๊ธด ๊ฐ’์„ ๊บผ๋‚ธ๋‹ค.

        return new OAuthAttributes(attributes,
                userNameAttributeName,
                (String) profile.get("nickname"),
                (String) kakao_account.get("email"),
                (String) profile.get("profile_image_url"));
    }

    private static OAuthAttributes ofNaver(String userNameAttributeName, Map<String, Object> attributes) {
        Map<String, Object> response = (Map<String, Object>) attributes.get("response");    // ๋„ค์ด๋ฒ„์—์„œ ๋ฐ›์€ ๋ฐ์ดํ„ฐ์—์„œ ํ”„๋กœํ•„ ์ •๋ณด๋‹ค ๋‹ด๊ธด response ๊ฐ’์„ ๊บผ๋‚ธ๋‹ค.
        
        return new OAuthAttributes(attributes,
                userNameAttributeName,
                (String) response.get("name"),
                (String) response.get("email"),
                (String) response.get("profile_image"));
    }

    private static OAuthAttributes ofGoogle(String userNameAttributeName, Map<String, Object> attributes) {
        
        return new OAuthAttributes(attributes,
                userNameAttributeName,
                (String) attributes.get("name"),
                (String) attributes.get("email"),
                (String) attributes.get("picture"));
    }

    // .. getter/setter ์ƒ๋žต
    
    public User toEntity() {
        return new User(name, email, picture);
    }
}

์†Œ์…œ ์„œ๋น„์Šค๋งˆ๋‹ค ์ •๋ณด๋“ค์˜ ๊ฐ’์ด ๋‹ค ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ์ง์ ‘ ๊ฐ’์„ print๋กœ ์ฐ์–ด๋ณด๋ฉด์„œ ์„ค์ •ํ•ด์•ผ ๋ฉ๋‹ˆ๋‹ค.

๋„ค์ด๋ฒ„์˜ ๊ฒฝ์šฐ response๊ฐ’ ์•ˆ์— ํ”„๋กœํ•„ ์ •๋ณด๊ฐ€ ๋“ค์–ด์žˆ์–ด์„œ map์—์„œ "response" ํ‚ค ๊ฐ’์„ ์ด์šฉํ•ด ๊ฐ’์„ ๊บผ๋ƒˆ๊ณ ,

๊ฐ ์„œ๋น„์Šค์—์„œ ๋ณด๋‚ด์ค€ ํ”„๋กœํ•„ ์ •๋ณด๋“ค์˜ key๊ฐ’์ด naver์˜ ํ”„๋กœํ•„์‚ฌ์ง„ url์€ profile_image์ด๊ณ , google์€ picture ๋“ฑ๋“ฑ.. ์‚ด์ง ์‚ด์ง ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ’์„ ์ž˜ ์„ค์ •ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

SessionUser ํด๋ž˜์Šค ์ƒ์„ฑ

SessionUser๋Š” ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” Dto ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. HttpSession์— ๋„ฃ์„ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ง๋ ฌํ™”๋ฅผ ํ•ด์ค๋‹ˆ๋‹ค.

import java.io.Serializable;

public class SessionUser implements Serializable {
    private String name;
    private String email;
    private String picture;

    public SessionUser(User user) {
        this.name = user.getName();
        this.email = user.getEmail();
        this.picture = user.getPicture();
    }

    public SessionUser() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPicture() {
        return picture;
    }

    public void setPicture(String picture) {
        this.picture = picture;
    }
}

 

๋กœ๊ทธ์ธ ๋ฒ„ํŠผ ์ƒ์„ฑ

  •  "/oauth2/authorization/google"
    • Spring security์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” url์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
<a href="/oauth2/authorization/google">๊ตฌ๊ธ€ ์•„์ด๋””๋กœ ๋กœ๊ทธ์ธ</a>
<a href="/oauth2/authorization/naver">๋„ค์ด๋ฒ„ ์•„์ด๋””๋กœ ๋กœ๊ทธ์ธ</a>
<a href="/oauth2/authorization/kakao">์นด์นด์˜ค ์•„์ด๋””๋กœ ๋กœ๊ทธ์ธ</a>

 

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!


REFERENCES

 

 

Spring Boot ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ํ•˜๊ธฐ(with security & oauth2.0)

1. ๊ตฌ๊ธ€ ๊ณ„์ • ๋งŒ๋“ค๊ธฐ ๋จผ์ € OAuth ๋™์˜ ํ™”๋ฉด์—์„œ ์ด๋ฆ„ ์ง“๊ณ  ๋ฐ‘์— 3๊ฐœ ์ž˜ ๋“ฑ๋ก๋˜์—ˆ๋Š”์ง€๋งŒ ํ™•์ธํ•˜๊ณ  ์ƒ์„ฑ ์ œ์ผ ์ƒ๋‹จ์ฒ˜๋Ÿผ ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์„ ์ง“๊ณ  ์™ผ์ชฝ ํ–„๋ฒ„๊ฑฐ ๋ฒ„ํŠผ์œผ๋กœ ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด๋กœ ๋„˜์–ด์˜ค๋ฉด ์˜ค

smujihoon.tistory.com

 

 

[SpringBoot] OAuth2 Google Login

OAuth2 Google Login ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์„ค์ • build.gradle์— ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ๊ด€๋ จ ์˜์กด์„ฑ ์ถ”๊ฐ€ spring-boot-starter-oauth2-client ์†Œ์…œ ๋กœ๊ทธ์ธ ๋“ฑ ํด๋ผ์ด์–ธํŠธ ์ž…์žฅ์—์„œ ์†Œ์…œ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์‹œ ํ•„์š”ํ•œ ์˜์กด์„ฑ spring-b..

seokr.tistory.com

 

 

์Šคํ”„๋ง ๋ถ€ํŠธ(Spring Boot): ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ์—ฐ๋™ (์Šคํ”„๋ง ๋ถ€ํŠธ ์Šคํƒ€ํ„ฐ์˜ oauth2-client) ์ด์šฉ + ๋„ค์ด๋ฒ„ ์•„์ด

  ์ด ๋ฐฉ๋ฒ•์€ JSTL, Thymeleaf, Mustache ๋“ฑ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ํ…œํ”Œ๋ฆฟ ์—”์ง„์„ ์‚ฌ์šฉํ•˜๋Š” ๋กœ๊ทธ์ธ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. SPA์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์†Œ์…œ ๋กœ๊ทธ์ธ ์—ฐ๋™ ๋ฐฉ๋ฒ•์€ ์•„๋ž˜ ๊ธ€์„ ์ฐธ๊ณ ํ•˜์„ธ์š”, ์Šคํ”„๋ง ๋ถ€ํŠธ(Spring Boot): SPA

yoonbumtae.com

 

 

Spring Boot Security๋กœ ์นด์นด์˜ค ์†Œ์…œ ๋กœ๊ทธ์ธ ๋งŒ๋“ค๊ธฐ

์ฐธ๊ณ  ํ• ์ .... ์ด๋ฒˆ ํฌ์ŠคํŒ…์€ ์Šคํ”„๋ง ๋ถ€ํŠธ์™€ AWS๋กœ ํ˜ผ์ž ๊ตฌํ˜„ํ•˜๋Š” ์›น ์„œ๋น„์Šค์˜ ์†Œ์…œ๋กœ๊ทธ์ธ ํŒŒํŠธ๋ฅผ ์ฐธ์กฐํ•˜์—ฌ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์ด ์ฑ…์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ž์„ธํ•œ ์ฝ”๋“œ๋Š” ์ฑ…์„

sundries-in-myidea.tistory.com

 

 

Spring Security 5 - OAuth2 Login | Baeldung

Learn how to authenticate users with Facebook, Google or other credentials using OAuth2 in Spring Security 5.

www.baeldung.com

 

๋ฐ˜์‘ํ˜•