Spring Boot + Spring Security

moon.kickยท2025๋…„ 4์›” 16์ผ

์ข‹์•„! ๐Ÿ˜Ž
์ง€๊ธˆ๋ถ€ํ„ฐ Spring Boot + Spring Security๋กœ ์•„๋ž˜ 4๊ฐ€์ง€ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์„ ๋‹จ๊ณ„๋ณ„๋กœ ์ •๋ฆฌํ•ด์ค„๊ฒŒ:


โœ… ์ „์ฒด ๋ชฉํ‘œ ๊ธฐ๋Šฅ

๊ธฐ๋Šฅ ๋ฒˆํ˜ธ๊ตฌํ˜„ ํ•ญ๋ชฉ
1๏ธโƒฃDB ๊ธฐ๋ฐ˜ ํšŒ์›๊ฐ€์ž… & ๋กœ๊ทธ์ธ
2๏ธโƒฃRedis ์„ธ์…˜ ์ €์žฅ ๋ฐฉ์‹ ์ ์šฉ
3๏ธโƒฃ๋กœ๊ทธ์ธ ์‹คํŒจ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ
4๏ธโƒฃ๋กœ๊ทธ์ธ ์ƒํƒœ ํ™•์ธ (์„ธ์…˜์—์„œ ์‚ฌ์šฉ์ž ์ด๋ฆ„ ๊ฐ€์ ธ์˜ค๊ธฐ)

๐Ÿ”จ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ์˜ˆ์‹œ (Spring Boot ๊ธฐ๋ฐ˜)

src/
 โ””โ”€โ”€ main/
     โ”œโ”€โ”€ java/
     โ”‚   โ””โ”€โ”€ com.example.securitydemo/
     โ”‚       โ”œโ”€โ”€ config/             โ† Security ์„ค์ •
     โ”‚       โ”œโ”€โ”€ controller/         โ† ๋กœ๊ทธ์ธ/ํ™ˆ ์ปจํŠธ๋กค๋Ÿฌ
     โ”‚       โ”œโ”€โ”€ entity/             โ† User ์—”ํ‹ฐํ‹ฐ
     โ”‚       โ”œโ”€โ”€ repository/         โ† JPA Repository
     โ”‚       โ”œโ”€โ”€ service/            โ† UserDetailsService ๊ตฌํ˜„
     โ”‚       โ””โ”€โ”€ SecurityDemoApplication.java
     โ””โ”€โ”€ resources/
         โ”œโ”€โ”€ templates/
         โ”‚   โ”œโ”€โ”€ login.html
         โ”‚   โ””โ”€โ”€ home.html
         โ””โ”€โ”€ application.yml

1๏ธโƒฃ DB ๊ธฐ๋ฐ˜ ํšŒ์›๊ฐ€์ž… & ๋กœ๊ทธ์ธ

๐Ÿ“ฆ Entity

@Entity
public class User {

    @Id
    private String username;

    private String password;

    private String role;
}

๐Ÿ—ƒ๏ธ Repository

public interface UserRepository extends JpaRepository<User, String> {
}

๐Ÿ” UserDetailsService ๊ตฌํ˜„

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) {
        User user = userRepository.findById(username).orElseThrow();
        return org.springframework.security.core.userdetails.User.builder()
                .username(user.getUsername())
                .password(user.getPassword()) // ์•”ํ˜ธํ™” ํ•„์š”
                .roles(user.getRole())
                .build();
    }
}

๐Ÿ”ง PasswordEncoder ๋“ฑ๋ก

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

๐Ÿ” SecurityConfig

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .authorizeRequests()
            .antMatchers("/register", "/login").permitAll()
            .anyRequest().authenticated()
        .and()
            .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/home", true)
            .failureUrl("/login?error=true")
        .and()
            .logout()
            .logoutSuccessUrl("/login?logout");
}

๐Ÿ“ ํšŒ์›๊ฐ€์ž… ์ปจํŠธ๋กค๋Ÿฌ

@PostMapping("/register")
public String register(User user) {
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    userRepository.save(user);
    return "redirect:/login";
}

2๏ธโƒฃ Redis ์„ธ์…˜ ์ €์žฅ ๋ฐฉ์‹

๐Ÿ“ฆ Gradle ์˜์กด์„ฑ

implementation 'org.springframework.session:spring-session-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

โš™๏ธ application.yml

spring:
  redis:
    host: localhost
    port: 6379

  session:
    store-type: redis
    timeout: 30m

โž• Redis ์„œ๋ฒ„๋Š” ๋”ฐ๋กœ ์ผœ์ค˜์•ผ ํ•จ (redis-server ๋ช…๋ น์–ด ๋˜๋Š” Docker)


3๏ธโƒฃ ๋กœ๊ทธ์ธ ์‹คํŒจ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ

๐Ÿ”„ login.html์—์„œ ๋ฉ”์‹œ์ง€ ๋ฐ›๊ธฐ

<div th:if="${param.error}">
  <p style="color:red;">๋กœ๊ทธ์ธ ์‹คํŒจ! ์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.</p>
</div>

/login?error=true โ†’ param.error๋กœ ํ™”๋ฉด์— ํ‘œ์‹œ ๊ฐ€๋Šฅ


4๏ธโƒฃ ์„ธ์…˜์—์„œ ์‚ฌ์šฉ์ž ์ด๋ฆ„ ๊ฐ€์ ธ์˜ค๊ธฐ

๐Ÿ‘€ SecurityContext์—์„œ ์„ธ์…˜ ์‚ฌ์šฉ์ž ๊ฐ€์ ธ์˜ค๊ธฐ

@GetMapping("/home")
public String home(Model model, Authentication authentication) {
    String username = authentication.getName(); // ์„ธ์…˜ ์œ ์ €๋ช…
    model.addAttribute("username", username);
    return "home";
}

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ์š”์•ฝ

๊ธฐ๋Šฅ๊ตฌํ˜„ ๋ฐฉ์‹
โœ… DB ๋กœ๊ทธ์ธUserDetailsService + JPA
โœ… ์„ธ์…˜ ์ €์žฅ์†ŒRedis ์ ์šฉ + spring.session.store-type: redis
โœ… ์‹คํŒจ ๋ฉ”์‹œ์ง€/login?error=true + Thymeleaf ์กฐ๊ฑด
โœ… ๋กœ๊ทธ์ธ ์ƒํƒœ ํ™•์ธAuthentication.getName() ๋˜๋Š” Principal.getName()

ํ•„์š”ํ•˜๋‹ค๋ฉด:

  • ์ฝ”๋“œ ์ „์ฒด ZIP ํ˜•ํƒœ๋กœ ์ •๋ฆฌ
  • React ํ”„๋ก ํŠธ์—”๋“œ ์—ฐ๋™ ํ™•์žฅ (SPA ์ธ์ฆ ํ๋ฆ„)
  • Redis ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ + JWT๋กœ ํ™•์žฅ

๊นŒ์ง€ ๊ฐ€๋Šฅํ•ด!
ํ˜น์‹œ ์ง€๊ธˆ ์ง„ํ–‰ ์ค‘์ธ ํ”„๋กœ์ ํŠธ์— ๋งž์ถฐ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•ด์ค„๊นŒ? ๐Ÿค—

profile
@mgkick

0๊ฐœ์˜ ๋Œ“๊ธ€