Spring Security session방식 OAUTH2로그인- 일반FORM회원가입 설정(3)
2025. 2. 26. 17:05ㆍSpringboot/security
전체코드 : https://github.com/gks930620/spring_securty_all
동작방식을 이해하고, 카카오개발자 센터에 애플리케이션 등록(+ 앱키,redirect-uri 등)을 완료했다면
이제 구현을 해보자.
개념
사용자 입장에서 우리사이트에서 따로 회원가입을 안하고 카카오로 로그인을 하지만
카카오로 로그인을 처음 했을 때 우리사이트에 회원가입한 것처럼 DB에 넣어야한다.
단지 email, nickname 등을 카카오에서 가지고 올 뿐.
즉, 카카오로그인사용자는 우리사이트의 회원가입과정만 진행하지 않는거지
내부적으로는 카카오로그인 했을 때 우리서버DB에 가입되고
일반FORM회원이랑 똑같이 사이트를 이용하게된다.
세팅파일
application.yml
dotenv:
enabled: true # .env파일설정. KAKAO_CLIENT_ID 직접 쓰려면 없어도됨.
spring:
datasource:
url: jdbc:h2:mem:security
driver-class-name: org.h2.Driver
username: sa
password:
h2:
console: # H2 DB를 웹에서 관리할 수 있는 기능
enabled: true # H2 Console 사용 여부
path: /h2-console # H2 Console 접속 주소
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
show_sql: true
format_sql: true
default_batch_fetch_size: 100
open-in-view: false
devtools:
livereload:
enabled: true
freemarker:
cache: false
restart:
enabled: true
thymeleaf:
cache: false
security:
oauth2:
client:
registration:
kakao:
client-id: ${KAKAO_CLIENT_ID} # 카카오 REST API 키
#client-secret: ${KAKAO_CLIENT_SECRET} # 선택 (일반적으로 필요 없음)
redirect-uri: http://localhost:8080/login/oauth2/code/kakao # 카카오 로그인 후 리디렉트될 주소
authorization-grant-type: authorization_code # OAuth2 인증 방식 (코드 방식)
scope:
- profile_nickname
- account_email # 요청할 사용자 정보 (이메일, 닉네임)
provider:
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize # 카카오 로그인 URL (인증서버)
token-uri: https://kauth.kakao.com/oauth/token # 액세스 토큰 요청 URL (인증서버)
user-info-uri: https://kapi.kakao.com/v2/user/me # 사용자 정보 요청 URL (리소스서버)
user-name-attribute: id # OAuth2User에서 사용할 식별 값. 왠만하면 oauth에서 제공하는대로사용하자. 카카오는 id, 구글은 sub
logging:
level:
org.hibernate.SQL: debug
org.hibernate.type: trace
SecurityConfig
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
@Bean
public PasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
private final CustomUserDetailsService customUserDetailsService;
private final CustomOAuth2UserService customOAuth2UserService;
@Bean
public SecurityFilterChain filterChain1(HttpSecurity http) throws Exception{
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/h2-console/**").permitAll() // H2 콘솔 접근 허용
)
.csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**")) // H2 콘솔 CSRF 비활성화
.headers(headers -> headers.frameOptions(frame -> frame.disable())); // H2 콘솔을 iframe에서 허용
// 이 설정은 내부 H2DB 사용을 위한 설정일뿐 의미없음.
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/images/**").permitAll() // 정적 자원 허용
);
http.authorizeHttpRequests((auth) -> auth
.requestMatchers("/join/**", "/login").permitAll()
.requestMatchers("/admin").hasAuthority("ADMIN")
.requestMatchers("/my/**").hasAnyAuthority("ADMIN", "USER")
.anyRequest().authenticated()
);
http.formLogin(auth -> auth
.loginPage("/login")
.loginProcessingUrl("/loginProc") //기본값은 /login (POST방식)
.defaultSuccessUrl("/")
.failureUrl("/login?error=true") // 로그인 실패 시 다시 로그인 페이지로. 기본값이 /login?error 지만 명시적으로.
.permitAll()
);
http.userDetailsService(customUserDetailsService); //명시적으로 등록. 일반 form 로그인사용자처리
http.oauth2Login(oauth2 -> oauth2
.loginPage("/login") // 한 페이지에서 폼 카카오 다 할거면 form이랑 같은게 좋음
.defaultSuccessUrl("/") // 로그인 성공 후 이동할 URL
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService) // 명시적. OAuth2 사용자 로그인 처리
)
);
http.logout((auth) -> auth.logoutUrl("/logout")
.logoutSuccessUrl("/"));
http.csrf((auth) -> auth.disable()); //csrf 적용X
return http.build();
}
}
일반 FORM 회원 가입
DB설정
UserEntity
@Entity
@Getter
@Setter
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; //JPA 기본 ID Long
@Column(unique = true)
private String username; //spring이 entity의 필드랑 파라미터를 비교하는건 아님. security에서 username이 기본이지만, 사용자입장에선 ID
private String password; //spring이 entity의 필드랑 파라미터를 비교하는건 아님
private String email;
private String nickname;
private String provider; // Oauth2Provider이름. 일반FORM로그인,카카오로그인 구별용. 꼭 필요한 필드는 아님.
private List<String> roles=new ArrayList<>();
}
userRepository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
public UserEntity findByUsername(String username);
}
JoinDTO
@Setter
@Getter
public class JoinDTO {
private String username;
private String password;
private String email;
private String nickname;
}
JoinController
@Controller
@RequestMapping("/join")
@RequiredArgsConstructor
public class JoinController {
private final JoinService joinService;
@GetMapping("/join")
public String join(){
return "join";
}
@ResponseBody
@PostMapping("/join")
public String joinPost(JoinDTO joinDTO){
joinService.joinProcess(joinDTO);
return "회원가입 완료!";
}
}
JoinService
@Service
public class JoinService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public void joinProcess(JoinDTO joinDTO) {
//db에 이미 동일한 username을 가진 회원이 존재하는지?
UserEntity find = userRepository.findByUsername(joinDTO.getUsername());
if(find!=null) {
System.out.println("이미 있는 ID입니다.");
return ;
}
UserEntity user = new UserEntity();
user.setUsername(joinDTO.getUsername());
user.setPassword(passwordEncoder.encode(joinDTO.getPassword())); //DB에 저장될 때는 반드시 encoding되서 저장되어야한다.
user.setNickname(joinDTO.getNickname());
user.setEmail(joinDTO.getEmail());
user.getRoles().add("USER"); //hasAuthority("USER") 에 맞게 USER로 세팅
user.setProvider(null);
userRepository.save(user);
}
}