Skip to content

Commit

Permalink
Merge pull request #19 from plog-us/feat/#15-oauth-api
Browse files Browse the repository at this point in the history
Feat/#15 oauth api
  • Loading branch information
melitina915 authored Feb 16, 2024
2 parents c2391d5 + 2caae60 commit 9a1b37e
Show file tree
Hide file tree
Showing 34 changed files with 327 additions and 211 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
public class BCryptPasswordEncoderConfig extends DefaultOAuth2UserService {

// password 암호화하는 Bean 등록
// 해당 메서드의 리턴되는 오브젝트를 IoC(빈)로 등록해준다.
@Bean
public BCryptPasswordEncoder encodePassword() {
return new BCryptPasswordEncoder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ public Storage storage() throws IOException {
ClassPathResource resource = new ClassPathResource("oceanic-craft-412007-8dee390f63d1.json");
GoogleCredentials credentials = GoogleCredentials.fromStream(resource.getInputStream());
String projectId = "oceanic-craft-412007";

return StorageOptions.newBuilder()
.setProjectId(projectId)
.setCredentials(credentials)
.build()
.getService();

}

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.gdscsmwu.earthus.plogus.config;

import com.gdscsmwu.earthus.plogus.config.oauth.PrincipalOauth2UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsUtils;
Expand All @@ -17,71 +17,63 @@
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig {

private final PrincipalOauth2UserService principalOauth2UserService;

@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {

httpSecurity
// Request
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
// .antMatchers(
// "/api/login", "/api/register"
// ).permitAll()
// .anyRequest().hasAnyRole("USER")
.and()
// 사용하지 않는 필터
.formLogin()
.disable()

.oauth2Login(oauth2 ->
// oauth2Login
// - OAuth 2 로그인 기능에 대한 여러 설정의 진입점
oauth2
.userInfoEndpoint( userInfoEndpointConfig ->
// userInfoEndpoint
// - OAuth 2 로그인 성공 이후 사용자 정보를 가져올 때의 설정들을 담당
// 구글 로그인이 완료된 뒤의 후처리가 필요함.
// 구글 로그인이 되면 코드를 받는 것이 아니라 엑세스 토큰 + 사용자 프로필 정보를 한 번에 받게 된다.
// 때문에 AuthClient 관련 라이브러리가 굉장히 편리하다
userInfoEndpointConfig
.userService(principalOauth2UserService)
// userService
// - 소셜 로그인 성공 시 후속 조치를 진행할 UserService 인터페이스의 구현체를 등록
// - 리소스 서버(즉, 소셜 서비스들)에서 사용자 정보를 가져옹 상태에서 추가로 진행하고자 하는 기능 명시
// .userService()에 넣어줄 타입이 OAuth2UserService가 돼야 한다.
)
// 이를 PrincipalOauth2UserService.java에 만들어준다.
// 이렇게 구글 로그인을 통해 보낸 엑세스 토큰 + 사용자 프로필 정보들이
// PrincipalOauth2UserService.java의 loadUser 함수의 매개변수 userRequest에 리턴된다.

.defaultSuccessUrl("http://localhost:8080/")
//.failureUrl("/error")

)

.csrf()
.disable()

.headers()
.disable()

.httpBasic()
.disable()

.rememberMe()
.disable()

.logout()
.disable()

.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// .and()
// 인증/인가 실패 Response
// .exceptionHandling()
// .authenticationEntryPoint(authenticationEntryPoint(objectMapper()))
// .accessDeniedHandler(accessDeniedHandler(objectMapper()))
// .and()
// Redis Session
// .addFilterBefore(userAuthenticationFilter(),
// UsernamePasswordAuthenticationFilter.class)
// CORS
// .cors().configurationSource(corsConfigurationSource());

/*
.csrf(AbstractHttpConfigurer::disable)
//.csrf().disable()
.authorizeHttpRequests(
(authorizeRequests) ->
authorizeRequests
.anyRequest().permitAll()
)
.formLogin((formLogin) ->
formLogin
.loginPage("/join")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/")
);
*/
/*
.logout((logout) ->
logout
.logoutUrl("/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
//.logoutSuccessUrl("/")
// 로그아웃 성공 시 리다이렉트할 URL
.permitAll()
);
*/

return httpSecurity.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,54 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

@Data
public class PrincipalDetails implements UserDetails {
//public class PrincipalDetails implements UserDetails, OAuth2User {
public class PrincipalDetails implements UserDetails, OAuth2User {

private Users users;
// 콤포지션
private Map<String, Object> attributes;



// login 시 사용하는 생성자
public PrincipalDetails(Users users) {
this.users = users;
}

// OAuth 로그인 시 사용하는 생성자
// OAuth2User로 로그인 할 때 Authentication에 PrincipalDetails를 저장할 것이다.
// OAuth 로그인을 하면 attributes 정보와 user 정보를 가지게 될 것이다.
// attributes 정보를 토대로 user 정보가 생성될 것이다.
public PrincipalDetails(Users users, Map<String, Object> attributes) {
this.users = users;
this.attributes = attributes;
}



// 해당 user의 권한 return
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {

Collection<GrantedAuthority> collection = new ArrayList<>();

collection.add(() -> {
return users.getRole().name();
});
collection.add(
new GrantedAuthority() {
@Override
public String getAuthority() {
return users.getRole().name();
}
}
);

return collection;
}

// 계정의 고유한 값을 PK로 넘겨준다
@Override
public String getUsername() {
//return users.getEmail();
return users.getUsername();
}

Expand Down Expand Up @@ -69,16 +88,18 @@ public boolean isEnabled() {
return true;
}

// @Override
// public Map<String, Object> getAttributes() {
// return attributes;
// }
//
// @Override
// public String getName() {
// //return attributes.get("sub");
// // 구글 회원 정보 PK가 sub이다.
// return null;
// }


@Override
public Map<String, Object> getAttributes() {
return attributes;
}

@Override
public String getName() {
//return attributes.get("sub");
// 구글 회원 정보 PK가 sub이다.
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx
if(users != null) {
return new PrincipalDetails(users);
}

// users 존재 X
return null;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.gdscsmwu.earthus.plogus.config.oauth;

import com.gdscsmwu.earthus.plogus.config.oauth.dto.OAuthAttributes;
import com.gdscsmwu.earthus.plogus.config.oauth.dto.SessionUsers;
import com.gdscsmwu.earthus.plogus.users.domain.Users;
import com.gdscsmwu.earthus.plogus.users.repository.UsersRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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;

@RequiredArgsConstructor
@Service
public class PrincipalOauth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
// SecurityConfig.java의 .userService()에 넣을 데이터 타입이 OAuth2UserService이므로
// 해당 클래스 타입도 DefaultOAuth2UserService로 만들어준다.

private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final UsersRepository usersRepository;
private final HttpSession httpSession;



// 구글로부터 받은 userRequest 데이터에 대한 후처리되는 함수
// loadUser 함수에 의해 후처리가 된다.
@Override
// OAuth 로그인했을 때 Override 한 이유
// 1. PrincipalDetails 타입으로 묶기 위해
// 2. OAuth 로그인 시 회원가입을 강제로 진행시키기 위해
// 함수 종료 시 @AuthenticationPrincipal 어노테이션이 만들어진다.
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
// 구글 로그인을 통해 보낸 엑세스 토큰 + 사용자 프로필 정보들이 매개변수 userRequest에 리턴된다.

OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);

String registraionId = userRequest.getClientRegistration().getRegistrationId();
String usernameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();

OAuthAttributes attributes = OAuthAttributes.of(registraionId, usernameAttributeName, oAuth2User.getAttributes());

// 회원가입을 강제로 진행해볼 예정
String provider = userRequest.getClientRegistration().getClientId();
// google
String providerId = oAuth2User.getAttribute("sub");
// OAuth 로그인이기 때문에 필요없지만 만들어준다.
// 크게 의미가 없는 코드이다.
String password = bCryptPasswordEncoder.encode(provider + "_" + providerId);

Users users = saveOrUpdate(attributes, provider, providerId, password);
httpSession.setAttribute("user", new SessionUsers(users));

return new DefaultOAuth2User(
Collections.singleton(
new SimpleGrantedAuthority(users.getRole().name())
),
attributes.getAttributes(),
attributes.getNameAttributeKey()
);

}

private Users saveOrUpdate(OAuthAttributes attributes, String provider, String providerId, String password) {

Users users = usersRepository.findByEmail(attributes.getEmail())
.map(entity -> entity.update(attributes.getUsername(), password, provider, providerId, attributes.getUserProfile()))
.orElse(attributes.toEntity(password, provider, providerId));

return usersRepository.save(users);

}

}
Loading

0 comments on commit 9a1b37e

Please sign in to comment.