# [Security] Security ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ
Study Repository

[Security] Security ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ

by rlaehddnd0422

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ Spring Security๋ฅผ ์ด์šฉํ•ด ํšŒ์› ๊ฐ€์ž…๊ณผ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ด…์‹œ๋‹ค.

 

build.gradle ์„ค์ • 

์˜์กด๊ด€๊ณ„์— ์ถ”๊ฐ€

implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์˜์กด์„ฑ ์ถ”๊ฐ€ ์‹œ

1. ์„œ๋ฒ„ ์‹คํ–‰ ์‹œ Spring Security์˜ ์ดˆ๊ธฐํ™” ์ž‘์—… ๋ฐ ๋ณด์•ˆ ์„ค์ •๋ฉ๋‹ˆ๋‹ค.

2. ๋ณ„๋„์˜ ์„ค์ •์ด๋‚˜ ๊ตฌํ˜„์„ ํ•˜์ง€ ์•Š์•„๋„ ๊ธฐ๋ณธ์ ์ธ ์›น ๋ณด์•ˆ ๊ธฐ๋Šฅ์ด ์„œ๋ฒ„์— ์—ฐ๋™๋ฉ๋‹ˆ๋‹ค.

3. ๋ชจ๋“  ์š”์ฒญ์€ ์ธ์ฆ์ด ๋˜์–ด์•ผ ์ ‘๊ทผ ๊ฐ€๋Šฅ

4. ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์ œ๊ณต

๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€

 

+ application.properties์— ๊ธฐ๋ณธ name/password ์„ค์ •์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

ํ•„ํ„ฐ ๋“ฑ๋ก

Request์— ๋Œ€ํ•ด์„œ ํ•„ํ„ฐ๊ฐ€ ๊ฐ€๋กœ์ฑ„์–ด ์ธ์ฆ ์ „ ๊ฐ์ฒด๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ํŠน์ • URI ์ ‘์† ์‹œ loginForm์œผ๋กœ ์ด๋™ํ•˜๋„๋ก ์„ค์ •ํ•ด๋ด…์‹œ๋‹ค. 

Spring Security๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด SecurityFilterChain์„ ์‚ฌ์šฉํ•˜๋„๋ก ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

@Configuration
public class SecurityConfig {

    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/user/**").authenticated() // ๋กœ๊ทธ์ธํ•ด์•ผ ๋“ค์–ด์˜ฌ์ˆ˜ ์žˆ์Œ
                .antMatchers("/manager/**").access("hasRole('ROLE_MANAGER')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/loginForm")
                .loginProcessingUrl("/login") // login ์ฃผ์†Œ๊ฐ€ ํ˜ธ์ถœ์ด๋˜๋ฉด ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ๋‚š์•„์ฑ„์„œ ๋Œ€์‹  ๋กœ๊ทธ์ธ ์ง„ํ–‰ํ•ด์คŒ.
                .defaultSuccessUrl("/");
        return http.build();
    }
  • SecurityFilterChain ๋“ฑ๋ก
    • http.csrf().disable() : CSRF ๋ฐฉ์ง€.  CSRF(Cross-Site Request Forgery) ๋กœ๋ถ€ํ„ฐ ๋ณดํ˜ธ
    • http.authorizeRequests()
      • antMatchers(String).authenticated() : String URI ์— ๋Œ€ํ•ด ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”  ๋กœ๊ทธ์ธ ํ•„์ˆ˜
      • antMatchers(String).access("ROLE") : ํ•ด๋‹น ROLE์„ ๊ฐ€์ ธ์•ผ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Œ
      • loginPage(String) : ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€์›ํ•˜๋Š” ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋ณ„๋„๋กœ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ URI ๋“ฑ๋ก
      • loginProcessUrl("/login") : /login ์˜ URI ์š”์ฒญ์ด ์˜ค๋ฉด ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ๋‚š์•„์ฑ„์„œ ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
      • defaultSuccessUrl() : ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ ์ด๋™ํ•  URI, ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ด์ „์— ์š”์ฒญํ–ˆ๋˜ URI๋กœ ์ž๋™์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•ฉ๋‹ˆ๋‹ค.

ํšŒ์›๊ฐ€์ž… Controller

@Slf4j
@Controller
@RequiredArgsConstructor
public class IndexController {
    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
@GetMapping("/joinForm")
public String joinForm() {
    return "joinForm";
}
@PostMapping("/join")
public String join(User user) {
    user.setRole("USER");
    String rawPassword = user.getPassword();
    String encPassword = bCryptPasswordEncoder.encode(rawPassword);
    user.setPassword(encPassword);
    userRepository.save(user); 
    log.info("user = {} ", user);
    return "redirect:/loginForm";
}

joinForm์—์„œ Post ์ „์†ก๋œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ user์— ๋‹ด๊ณ , BCryptPasswordEncoder๋ฅผ ํ†ตํ•ด ํ•ด์‹ฑํ•จ์ˆ˜๋กœ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

 

+ ์‹œํ๋ฆฌํ‹ฐ๋กœ ๋กœ๊ทธ์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ฐ˜๋“œ์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์œ„์™€ ๊ฐ™์ด ์•”ํ˜ธํ™” ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

๊ฐ์ฒด์˜ ๊ฒฝ์šฐ ์ž๋™์œผ๋กœ @ModelAttribute๊ฐ€ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

๋กœ๊ทธ์ธ Controller

@GetMapping("/loginForm")
public String login() {
    return "loginForm";
}

 

loginForm

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>๋กœ๊ทธ์ธ ํŽ˜์ด์ง€</title>
</head>
<body>
<h1>๋กœ๊ทธ์ธ ํŽ˜์ด์ง€</h1>
<hr/>
<!-- ์‹œํ๋ฆฌํ‹ฐ๋Š” x-www-form-url-encoded ํƒ€์ž…๋งŒ ์ธ์‹ -->
<form action="/login" method="post">
    <input type="text" name="username" placeholder="Username" />
    <input type="password" name="password" placeholder="Password"/>
    <button>๋กœ๊ทธ์ธ</button>
</form>
<a href="/joinForm">ํšŒ์›๊ฐ€์ž…์„ ์•„์ง ํ•˜์ง€ ์•Š์œผ์…จ๋‚˜์š”?</a>
</body>
</html>

 

์ž…๋ ฅํ•œ ID์™€ PW๋ฅผ /login ์œผ๋กœ Post ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

์ด ๋•Œ ์œ„์—์„œ ๋“ฑ๋กํ•œ ํ•„ํ„ฐ์— ์˜ํ•ด  (http.loginProcessingUrl("/login")) login ์ฃผ์†Œ๊ฐ€ ํ˜ธ์ถœ์ด ๋˜๋ฉด ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ๋‚š์•„์ฑ„์„œ ๋Œ€์‹  ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. 

+ loginProcessingUrI๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ๋‚ด๋ถ€์—์„œ AuthenticationManager์™€ AuthenticationProvider๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ .loginProcessingUrl์„ ์‚ฌ์šฉํ•˜๋ฉด ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋Š” ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ํ•ด๋‹น URL๋กœ ๋กœ๊ทธ์ธ์„ ์‹œ๋„ํ•  ๋•Œ ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด AuthenticationManager์™€ AuthenticationProvider๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ณ„๋„๋กœ AuthenticationManager์™€ AuthenticationProvider๋ฅผ ๊ตฌ์„ฑํ•˜์ง€ ์•Š์•„๋„ ๊ธฐ๋ณธ์ ์ธ ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋กœ๊ทธ์ธ ์™„๋ฃŒ ํ›„, Security Session์— ์„ธ์…˜ ์ •๋ณด๋ฅผ ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์—ฌ๊ธฐ์— ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด๋Š” Authentication ๊ฐ์ฒด ๋ฟ์ž…๋‹ˆ๋‹ค. Authentication ๊ฐ์ฒด ์•ˆ์—๋Š” User ์ •๋ณด๋ฅผ ์ €์žฅํ•ด์•ผ ํ•˜๋Š”๋ฐ ์ด ๋•Œ User๋Š” ๋ฐ˜๋“œ์‹œ UserDetails ํƒ€์ž…์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์‰ฝ๊ฒŒ ๋งํ•ด,  ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ /login ์š”์ฒญ์„ ๋‚š์•„์ฑ„์„œ ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๋กœ๊ทธ์ธ ์ง„ํ–‰ ์™„๋ฃŒ๊ฐ€ ๋˜๋ฉด, SecurityContextHolder์— ์‹œํ๋ฆฌํ‹ฐ ์„ธ์…˜์„ ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

์ด ๊ณผ์ •์—์„œ ์—ฌ๊ธฐ(SecurityContextHolder)์— ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด๋Š” Authentication ๋ฟ์ด๊ณ ,

Authentication ๊ฐ์ฒด ์•ˆ์—๋Š” ์œ ์ €์ •๋ณด๊ฐ€ ๋“ค์–ด๊ฐ€ ์žˆ์–ด์•ผ ํ•˜๋Š”๋ฐ, ์œ ์ €์ •๋ณด๋Š” ๋ฐ˜๋“œ์‹œ UserDetails ํƒ€์ž…์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

SecurityContextHolder(Session(Authentication(UserDetails)))

 

๋”ฐ๋ผ์„œ UserDetails๋ฅผ ๊ตฌํ˜„ํ•œ ๊ตฌํ˜„์ฒด PrincipalDetails๋ฅผ Authentication ๊ฐ์ฒด์— ๋‹ด๋„๋ก ํ•ฉ์‹œ๋‹ค.

 

์šฐ์„  UserDetails ๊ตฌํ˜„ - PrincipalDetails

public class PrincipalDetails implements UserDetails {
    private User user;

    public PrincipalDetails(User user) {
        this.user = user;
    }

    // ํ•ด๋‹น User์˜ ๊ถŒํ•œ์„ ๋ฆฌํ„ดํ•˜๋Š” ๊ณณ
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collection = new ArrayList<>();
        collection.add(
                new GrantedAuthority(){
            @Override
            public String getAuthority() {
                return user.getRole();
            }
        });
        return collection;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    // ๊ณ„์ • ๋งŒ๋ฃŒ์—ฌ๋ถ€
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // ๊ณ„์ • ์ž ๊น€x ์—ฌ๋ถ€
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // ํœด๋ฉด ๊ณ„์ •
    @Override
    public boolean isEnabled() {

        // 1๋…„๋™์•ˆ ๋กœ๊ทธ์ธ ์•ˆํ•˜๋ฉด ํœด๋ฉด๊ณ„์ •์œผ๋กœ ํ•˜๊ธฐ๋กœํ•จ
        // ํ˜„์žฌ์‹œ๊ฐ„ - ๋งˆ์ง€๋ง‰๋กœ๊ทธ์ธ๋‚ ์งœ => 1๋…„์ดˆ๊ณผํ™”๋ฉด false
        // else true
        return true;
    }
}

 

UserDetailsService ๊ตฌํ˜„ - PrincipalDetailService

// ์‹œํ๋ฆฌํ‹ฐ ์„ค์ •์—์„œ loginProcessingUrl("/login");
// /login ์š”์ฒญ์ด์˜ค๋ฉด ์ž๋™์œผ๋กœ UserDetailsService ํƒ€์ž…์œผ๋กœ Ioc ๋˜์–ด์žˆ๋Š” loadUserByUsername ์ด ์‹คํ–‰

@Slf4j
@Service
@RequiredArgsConstructor
public class PrincipalDetailsService implements UserDetailsService {
    private final UserRepository userRepository;
	
    //  ์‹œํ๋ฆฌํ‹ฐ session(๋‚ด๋ถ€ Authentication(๋‚ด๋ถ€ UserDetails))
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("username = {}",username);
        User user = userRepository.findByUsername(username);
        if (user != null) {
            return new PrincipalDetails(user);
        }
        return null;
    }
}
  • ์‹œํ๋ฆฌํ‹ฐ๋Š” ์ž๋™์œผ๋กœ UserDetailsServcie ํƒ€์ž…์œผ๋กœ Ioc ๋˜์–ด์žˆ๋Š” PrincipalDetailsService ๊ฐ€ ์‹คํ–‰๋˜๋ฉด์„œ, loadUserByUsername ํ•จ์ˆ˜๊ฐ€ ์ž๋™์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  • ์ด ํ•จ์ˆ˜์—์„œ DB์— ์ฟผ๋ฆฌ๋ฉ”์†Œ๋“œ๋ฅผ ๋‚ ๋ ค ์ผ์น˜ํ•˜๋Š” ํšŒ์›์„ ์ฐพ๊ณ  ์ฐพ์€๊ฒฝ์šฐ PrincipalDetails (UserDetails) ์— ์œ ์ €์ •๋ณด๋ฅผ ๋‹ด์•„ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค.
  • ์ด์ œ ์‹œํ๋ฆฌํ‹ฐ Session ์•ˆ์— UserDetails ํƒ€์ž…์ธ PrincipalDetails ๊ฐ์ฒด๋ฅผ ๋‚ด์žฅํ•˜๊ณ  ์žˆ๋Š” Authentication ๊ฐ์ฒด๊ฐ€ ๋“ค์–ด๊ฐ€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ( loadByUsername ํ•จ์ˆ˜์—์„œ ์ž๋™ํ™” ) 

์˜๋ฌธ์  ?

UserDetailsService ์ธํ„ฐํŽ˜์ด์Šค์˜ loadUserByUsername(String username)์„ ๊ตฌํ˜„ํ•ด์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ DB์—์„œ ์กฐํšŒํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ฒดํฌํ•˜๋Š” ์ฝ”๋“œ๋Š” ์—†์–ด๋ณด์ด๋Š”๋ฐ, ์ž˜๋ชป๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๋กœ๊ทธ์ธ์— ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

์–ด๋””์—์„ ๊ฐ€ ๋น„๋ฐ€ ๋ฒˆํ˜ธ ์ฒดํฌ๋ฅผ ํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ ๊ฐ™๊ธดํ•œ๋ฐ ๊ณผ์—ฐ ์–ด๋””์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฑธ๊นŒ์š”?

 

๋ฐ”๋กœ DaoAuthenticationProvider์—์„œ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.
String presentedPassword = authentication.getCredentials().toString();

    if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
        logger.debug("Authentication failed: password does not match stored value");

        throw new BadCredentialsException(messages.getMessage(
                "AbstractUserDetailsAuthenticationProvider.badCredentials",
                "Bad credentials"));
    }
    ...

 

<์ฐธ๊ณ  ์ž๋ฃŒ>

 

Spring security - csrf๋ž€?

๋ชจ๋“  ์ฝ”๋“œ๋Š” github์— ์žˆ์Šต๋‹ˆ๋‹ค.์ตœ๊ทผ์— Spring security๋ฅผ ํ•œ์ฐฝ ๊ณต๋ถ€ํ•˜๊ณ  ์žˆ๋Š”๋ฐ (์กฐ๋งŒ๊ฐ„ ๋ธ”๋กœ๊ทธ์— security ๊ด€๋ จ ๊ธ€์ด ์—ฌ๋Ÿฌ๊ฐœ ์˜ฌ๋ผ๊ฐˆ๊ฒƒ ๊ฐ™๋‹ค) ํ•œ ๊ฐ€์ง€ ๊ถ๊ธˆํ•˜๊ฒŒ ํ•œ ์ฝ”๋“œ๊ฐ€ ์žˆ์—ˆ๋‹ค.http.csrf().disable()์—์„œ csr

velog.io

 

GitHub - HomoEfficio/dev-tips: ๊ฐœ๋ฐœํ•˜๋‹ค ๋งˆ์ฃผ์ณค๋˜ ์ž‘์€ ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• ์ •๋ฆฌ

๊ฐœ๋ฐœํ•˜๋‹ค ๋งˆ์ฃผ์ณค๋˜ ์ž‘์€ ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• ์ •๋ฆฌ. Contribute to HomoEfficio/dev-tips development by creating an account on GitHub.

github.com

 

 

Spring Security) UserDetailsService์—์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์–ด๋””์—์„œ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฑธ๊นŒ?

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ form login์„ ๊ตฌํ˜„ํ•˜๋‹ค๊ฐ€ ๋ฌธ๋“ ๋“  ์˜๋ฌธ์ธ๋ฐ UserDetailsService์—์„œ loadUserByUsername(String username)์—์„œ๋Š” ์•„์ด๋””๋กœ๋งŒ DB์—์„œ ์กฐํšŒํ•˜๊ณ  ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ฃผ๊ธฐ๋งŒ ํ•˜์ง€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ฒ€์ฆํ•˜

velog.io

 

๋ธ”๋กœ๊ทธ์˜ ์ •๋ณด

Study Repository

rlaehddnd0422

ํ™œ๋™ํ•˜๊ธฐ