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";
	}

Categories:

Updated:

Leave a comment