2025.02.21 작성
OS : Window
개발환경: IntelliJ IDEA
개발언어: Java
프레임워크: Spring Boot
Spring Initializr는 Spring Boot 프로젝트를 쉽게 생성할 수 있도록 도와주는 도구로,
Spring 공식 사이트(https://start.spring.io)에서 제공하는 서비스.
logging.level.org.springframework.security.web=trace
logging.level.org.zerock=debug
package org.zerock.club.config;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
@Log4j2
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
이 코드는 Spring Security에서 비밀번호를 안전하게 저장하기 위한 BCrypt 해싱(암호화) 인코더를 빈(Bean)으로 등록하는 것.
BCryptPasswordEncoder()
package org.zerock.club.security;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
@SpringBootTest
public class PasswordTests {
@Autowired
private PasswordEncoder passwordEncoder;
@Test
public void testEncode() {
String password = "1111";
String enPw = passwordEncoder.encode(password);
System.out.println("enPw = " + enPw);
boolean matchResult = passwordEncoder.matches(password, enPw);
System.out.println("matchResult = " + matchResult);
}
}
→ 같은 비밀번호라도 다르게 저장됨 (Salt 때문)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((auth) -> {
auth.requestMatchers("/sample/all").permitAll(); // 로그인 없이 접속 가능
});
http.formLogin(formLogin -> {
});
return http.build();
}
package org.zerock.club.entity;
public enum ClubMemberRole {
USER, MANAGER, ADMIN;
}
@Test
public void insertDummies() {
IntStream.rangeClosed(1, 100).forEach( i -> {
ClubMember clubMember = ClubMember.builder()
.email("user"+i+"zerock.org")
.name("사용자" + i)
.fromSocial(false)
.password(passwordEncoder.encode("1111"))
.build();
clubMember.addMemberRole(ClubMemberRole.USER);
if(i > 80) {
clubMember.addMemberRole(ClubMemberRole.MANAGER);
}
if(i > 90) {
clubMember.addMemberRole(ClubMemberRole.ADMIN);
}
repository.save(clubMember);
});
}
@Test
public void testRead() {
Optional<ClubMember> result = repository.findByEmail("user1@zerock.org", false);
ClubMember clubMember = result.get();
System.out.println(clubMember);
}
package org.zerock.club.security.handler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.zerock.club.security.dto.ClubAuthMemberDTO;
import java.io.IOException;
@Log4j2
@RequiredArgsConstructor
public class ClubLoginSuccessHandler implements AuthenticationSuccessHandler {
private final PasswordEncoder passwordEncoder;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("----------------------------");
log.info("onAuthenticationSuccess");
ClubAuthMemberDTO authMember = (ClubAuthMemberDTO) authentication.getPrincipal();
boolean fromSocial = authMember.isFromSocial();
boolean passwordResult = passwordEncoder.matches("1111", authMember.getPassword());
log.info("fromSocial: " + fromSocial);
log.info("passwordResult: " + passwordResult);
log.info("password:"+authMember.getPassword());
if (fromSocial && passwordResult) {
redirectStrategy.sendRedirect(request, response, "/member/modify?from=social");
}
}
}
1. 로그인 성공 후 호출
2. 사용자 정보 가져오기
ClubAuthMemberDTO authMember = (ClubAuthMemberDTO) authentication.getPrincipal();
3. 소셜 로그인 여부 확인
boolean fromSocial = authMember.isFromSocial();
4. 초기 비밀번호 확인
boolean passwordResult = passwordEncoder.matches("1111", authMember.getPassword());
5. 비밀번호 변경 페이지로 리다이렉트.
if (fromSocial && passwordResult) {
redirectStrategy.sendRedirect(request, response, "/member/modify?from=social");
}
6. 로그 출력
Spring Security에서 사용자의 인증 및 권한 정보를 담는 인터페이스.
package org.zerock.club.security.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.Collection;
import java.util.Map;
@Getter
@Setter
@ToString
@Log4j2
public class ClubAuthMemberDTO extends User implements OAuth2User {
public ClubAuthMemberDTO(String username, String password, boolean fromSocial, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.email = username;
this.fromSocial = fromSocial;
this.password = password;
}
public ClubAuthMemberDTO(String username, String password, boolean fromSocial, Collection<? extends GrantedAuthority> authorities, Map<String, Object> attr) {
super(username, password, authorities);
this.email = username;
this.fromSocial = fromSocial;
this.password = password;
this.attr = attr;
}
private String email;
private String name;
private String password;
private boolean fromSocial;
private Map<String, Object> attr;
@Override
public Map<String, Object> getAttributes() {
return this.attr;
}
}
Optional<ClubMember> result = clubMemberRepository.findByEmail(username, false);