Skip to content

jayden-lee/hello-spring-security

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

43 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Spring Security Study Repo

์ธํ”„๋Ÿฐ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ๊ฐ•์ขŒ๋ฅผ ํ•™์Šตํ•˜๊ณ  ์ •๋ฆฌํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค

Prerequisites

  • Installing MySQL 5.7

Account Info

  • Normal User : user / 123
  • Admin User : admin / !@#

Password Encoder

๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ‰๋ฌธ์ด ์•„๋‹Œ ๋‹จ๋ฐฉํ–ฅ ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ์ธ์ฝ”๋”ฉํ•ด์„œ ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค

  • {id}encodePassword
PasswordEncoder passwordEncoder = 
        PasswordEncoderFactories.createDelegatingPasswordEncoder();

Password Encoder ์ข…๋ฅ˜

  • BCryptPasswordEncoder
  • NoOpPasswordEncoder
  • Pbkdf2PasswordEncoder
  • ScryptPasswordEncoder
  • StandardPasswordEncoder

Spring Web Mock Mvc Test

@AutoConfigureMockMvc ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด MockMvc ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SpringBootTest {

}

Anonymous, User, Admin Test

Anonymous

@Test
@WithAnonymousUser
public void index_anonymous() throws Exception {
    mockMvc.perform(get(INDEX_PAGE))
        .andDo(print())
        .andExpect(status().isOk());
}

User

@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(username = "user", roles="USER")
public @interface WithNormalUser {
}

@Test
@WithNormalUser
public void index_user() throws Exception {
    mockMvc.perform(get(INDEX_PAGE))
        .andDo(print())
        .andExpect(status().isOk());
}

Admin

@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(username = "admin", roles="ADMIN")
public @interface WithAdminUser {
}

@Test
@WithAdminUser
public void admin_admin() throws Exception {
    mockMvc.perform(get(ADMIN_PAGE))
        .andDo(print())
        .andExpect(status().isOk());
}

SecurityContextHolder์™€ Authentication

  • SecurityContext ์ œ๊ณต
  • ํ•˜๋‚˜์˜ Thread์—์„œ Authentication ๊ณต์œ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ThreadLocal ์‚ฌ์šฉ
  • Authentication๋Š” Principal๊ณผ GrantAuthority ์ œ๊ณต
    • Principal์€ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ •๋ณด
    • GrantAuthority๋Š” ๊ถŒํ•œ ์ •๋ณด (์ธ๊ฐ€ ๋ฐ ๊ถŒํ•œ ํ™•์ธํ•  ๋•Œ ์‚ฌ์šฉ)
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

// ์‚ฌ์šฉ์ž ์ •๋ณด
Object principal = authentication.getPrincipal();

// ์‚ฌ์šฉ์ž ๊ถŒํ•œ
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

// ์ธ์ฆ ์—ฌ๋ถ€
boolean authenticated = authentication.isAuthenticated();

UserDetailsService ํด๋ž˜์Šค๋Š” DAO๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์‹ค์ œ ์ธ์ฆ์€ AuthenticationManager ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์ˆ˜ํ–‰ํ•œ๋‹ค.

AuthenticationManager์™€ Authentication

  • ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ธ์ฆ์€ AuthenticationManager๊ฐ€ ์ˆ˜ํ–‰
  • SecurityContext๋Š” ์ธ์ฆ ์ •๋ณด๋ฅผ ๊ฐ–๊ณ  ์žˆ์Œ
  • ๋Œ€๋ถ€๋ถ„ AuthenticationManager ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ProviderManager ๊ตฌํ˜„์ฒด ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค
public interface AuthenticationManager {

	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
}

DaoAuthenticationProvider

  • UsernamePasswordAuthenticationToken์€ DaoAuthenticationProvider๊ฐ€ ์ธ์ฆํ•˜๋Š” ์ž‘์—…์„ ์ฒ˜๋ฆฌ
  • UserDetailsService ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค์˜ loadUserByUsername ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ
  • AccountService ํด๋ž˜์Šค์˜ loadUserByUsername ๋ฉ”์„œ๋“œ๋Š” User ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜
  • User ํด๋ž˜์Šค๋Š” UserDetails ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ๊ตฌ์ฒด ํด๋ž˜์Šค

DaoAuthenticationProvider

public class AccountService implements UserDetailsService {

    @Autowired
    AccountRepository accountRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account account = accountRepository.findByUsername(username);
        if (account == null) {
            throw new UsernameNotFoundException(username);
        }

        return User.builder()
                .username(account.getUsername())
                .password(account.getPassword())
                .roles(account.getRole())
                .build();
    }
}

ThreadLocal

  • java.lang ํŒจํ‚ค์ง€์—์„œ ์ œ๊ณตํ•˜๋Š” ์“ฐ๋ ˆ๋“œ ๋ฒ”์œ„ ๋ณ€์ˆ˜
  • ์“ฐ๋ ˆ๋“œ ์ˆ˜์ค€์˜ ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ
  • ๊ฐ™์€ ์“ฐ๋ ˆ๋“œ ๋‚ด์—์„œ๋งŒ ๊ณต์œ 
  • ๊ฐ™์€ ์“ฐ๋ ˆ๋“œ๋ผ๋ฉด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”์„œ๋“œ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋„˜๊ฒจ์ค„ ํ•„์š” ์—†์Œ
public class AccountContext {

    private static final ThreadLocal<Account> ACCOUNT_THREAD_LOCAL
            = new ThreadLocal<>();

    public static void setAccount(Account account) {
        ACCOUNT_THREAD_LOCAL.set(account);
    }

    public static Account getAccount() {
        return ACCOUNT_THREAD_LOCAL.get();
    }

}

SecurityContextHolder์— Authentication ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ํ•„ํ„ฐ

  1. UsernamePasswordAuthenticationFilter

    • AuthenticationManager๋ฅผ ์ด์šฉํ•ด์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋กœ๊ทธ์ธ ์ •๋ณด(์ด๋ฆ„, ๋น„๋ฐ€๋ฒˆํ˜ธ)๋ฅผ ์ธ์ฆ UsernamePasswordAuthenticationFilter
    • ์ธ์ฆ์— ์„ฑ๊ณตํ•˜๋ฉด successfulAuthentication ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ
    • SecurityContextHolder์˜ SecurityContext์— ์ธ์ฆ ์ •๋ณด๋ฅผ ์ €์žฅ AbstractAuthenticationProcessingFilter
  2. SecurityContextPersistenceFilter

    • HttpSessionSecurityContextRepository ์ €์žฅ์†Œ๋ฅผ ํ†ตํ•ด SecurityContext ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค
    • ๊ธฐ๋ณธ ์ „๋žต์œผ๋กœ Http ์„ธ์…˜์— ์ €์žฅํ•˜๊ณ  ๋ณต์›ํ•œ๋‹ค
    • Repository์—์„œ ๊ฐ€์ ธ์˜จ SecurityContext ์ •๋ณด๋ฅผ ๋‹ค์‹œ SecurityContextHolder์— ๋„ฃ์–ด ์ค€๋‹ค

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ Filter์™€ FilterChainProxy

  • FilterChainProxy๋Š” ์š”์ฒญ(HttpServletRequest)์— ๋”ฐ๋ผ ์ ํ•ฉํ•œ SecurityFilterChain์„ ์‚ฌ์šฉ
  • ๊ธฐ๋ณธ ์ „๋žต์œผ๋กœ DefaultSecurityFilterChain์„ ์‚ฌ์šฉ
  • DefaultSecurityFilterChain๋Š” Filter ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค
  • SecurityFilterChain์„ ์—ฌ๋Ÿฌ๊ฐœ ๋งŒ๋“ค๊ณ  ์‹ถ์œผ๋ฉด SecurityConfig ํด๋ž˜์Šค๋ฅผ ์—ฌ๋Ÿฌ๊ฐœ ๋งŒ๋“ ๋‹ค
    • ์ด ๋•Œ SecurityConfig๊ฐ€ ์ƒ์ถฉํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ Order ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ง€์ •ํ•œ๋‹ค
  • Filter ๊ฐœ์ˆ˜๋Š” SecurityConfig ์„ค์ •์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง„๋‹ค
  • FilterChainProxy๋Š” ํ•„ํ„ฐ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์‹คํ–‰ํ•œ๋‹ค
  1. WebAsyncManagerIntergrationFilter
  2. SecurityContextPersistenceFilter
  3. HeaderWriterFilter
  4. CsrfFilter
  5. LogoutFilter
  6. UsernamePasswordAuthenticationFilter
  7. DefaultLoginPageGeneratingFilter
  8. DefaultLogoutPageGeneratingFilter
  9. BasicAuthenticationFilter
  10. RequestCacheAwareFilter
  11. SecurityContextHolderAwareReqeustFilter
  12. AnonymouseAuthenticationFilter
  13. SessionManagementFilter
  14. ExeptionTranslationFilter
  15. FilterSecurityInterceptor
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .mvcMatchers("/", "/info").permitAll()
            .mvcMatchers("/admin").hasRole("ADMIN")
            .anyRequest().authenticated();
        http.formLogin();
        http.httpBasic();
    }

}

DelegatingFilterProxy

  • ์ผ๋ฐ˜์ ์ธ ์„œ๋ธ”๋ฆฟ ํ•„ํ„ฐ
  • ์„œ๋ธ”๋ฆฟ ํ•„ํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ์Šคํ”„๋ง์— ๋“ค์–ด์žˆ๋Š” ๋นˆ์œผ๋กœ ์œ„์ž„ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋ธ”๋ฆฟ ํ•„ํ„ฐ
  • ํƒ€๊ฒŸ ๋นˆ ์ด๋ฆ„์„ ์„ค์ •
  • ์Šคํ”„๋ง ๋ถ€ํŠธ(์ž๋™ ์„ค์ •) ์—†์ด ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์„ค์ •ํ•  ๋•Œ๋Š” AbstractSecurityWebApplicationInitializer๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋“ฑ๋ก
  • ์Šคํ”„๋ง ๋ถ€ํŠธ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ž๋™์œผ๋กœ ๋“ฑ๋ก (SecurityFilterAutoConfiguration)
  • FilterChainProxy๋Š” springSecurityFilterChain ์ด๋ฆ„์œผ๋กœ ๋นˆ ๋“ฑ๋ก
public abstract class AbstractSecurityWebApplicationInitializer
		implements WebApplicationInitializer {

	private static final String SERVLET_CONTEXT_PREFIX = "org.springframework.web.servlet.FrameworkServlet.CONTEXT.";

	public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";

    ...
}

SecurityFilterAutoConfiguration

AccessDecisionManager

Access Control ๊ฒฐ์ •์„ ๋‚ด๋ฆฌ๋Š” ์ธํ„ฐํŽ˜์ด์Šค, ๊ตฌํ˜„์ฒด 3๊ฐ€์ง€๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•œ๋‹ค

  • AffirmativeBased : ์—ฌ๋Ÿฌ Voter ์ค‘์— ํ•œ ๋ช…์ด๋ผ๋„ ํ—ˆ์šฉํ•˜๋ฉด ์ธ๊ฐ€ (๊ธฐ๋ณธ ์ „๋žต)
  • ConsensusBased : ๋‹ค์ˆ˜๊ฒฐ
  • UnanimousBased : ๋งŒ์žฅ์ผ์น˜
public interface AccessDecisionManager {

	void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
			InsufficientAuthenticationException;

	boolean supports(ConfigAttribute attribute);


	boolean supports(Class<?> clazz);
}

AccessDecisionVoter

  • Authentication์ด ํŠน์ •ํ•œ Object์— ์ ‘๊ทผํ•  ๋•Œ ํ•„์š”ํ•œ ConfigAttribute๋ฅผ ๋งŒ์กฑํ•˜๋Š”์ง€ ํ™•์ธ
  • WebExpressionVoter : ์›น ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด, ROLE_XXX ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ
  • RoleHierarchyVoter : ๊ณ„์ธตํ˜• Role ์ง€์›

Custom AccessDecisionManager

  • RoleHierarchyImpl ๊ฐ์ฒด์— Role ๊ณ„์ธต์„ ์„ค์ •
public AccessDecisionManager accessDecisionManager() {
    RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
    roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");

    DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
    handler.setRoleHierarchy(roleHierarchy);

    WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
    webExpressionVoter.setExpressionHandler(handler);

    List<AccessDecisionVoter<? extends Object>> voters = Arrays.asList(webExpressionVoter);
    return new AffirmativeBased(voters);
}

FilterSecurityInterceptor

  • FilterChainProxy๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” ์‹œํ๋ฆฌํ‹ฐ ํ•„ํ„ฐ ๋ชฉ๋ก ์ค‘์— ํ•˜๋‚˜์ด๋ฉฐ, ๋Œ€๋ถ€๋ถ„ ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰์— ์œ„์น˜ํ•จ
  • ์ธ์ฆ์ด ๋œ ์ƒํƒœ์—์„œ ํŠน์ • ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”์ง€ Role์„ ํ™•์ธํ•จ
  • AccessDecisionManager๋ฅผ ์‚ฌ์šฉํ•ด์„œ Access Control ๋˜๋Š” ์˜ˆ์™ธ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•„ํ„ฐ

AbstractSecurityInterceptor

  • FilterSecurityInterceptor ํด๋ž˜์Šค์˜ ๋ถ€๋ชจ ํด๋ž˜์Šค

AbstractSecurityInterceptor

ExceptionTranslationFilter

  • ํ•„ํ„ฐ ์ฒด์ธ์—์„œ ๋ฐœ์ƒํ•˜๋Š” AccessDeniedException๊ณผ AuthenticationException์„ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•„ํ„ฐ

ExceptionTranslationFilter

AuthenticationException

  • ์ธ์ฆ์— ์‹คํŒจํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ
  • AbstractSecurityInterceptor ํ•˜์œ„ ํด๋ž˜์Šค์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋งŒ ์ฒ˜๋ฆฌ

AccessDeniedException

  • ์ต๋ช… ์‚ฌ์šฉ์ž๋ผ๋ฉด AuthenticationEntryPoint ์‹คํ–‰ (๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™)
  • ์ต๋ช… ์‚ฌ์šฉ์ž๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด AccessDeniedHandler์—๊ฒŒ ์œ„์ž„

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์ ์šฉ ๋ฌด์‹œํ•˜๊ธฐ (ignoring)

์ธ์ฆ์ด ํ•„์š”์—†๋Š” ํŽ˜์ด์ง€๋ฅผ ์ ‘์†ํ•  ๋•Œ favicon.ico์™€ ๊ฐ™์€ ์ •์  ์ž์›์„ ์š”์ฒญํ•˜๋Š” ๊ฒฝ์šฐ์— FilterChainProxy ๋ฆฌ์ŠคํŠธ์˜ ํ•„ํ„ฐ๋ฅผ ํƒ€๊ฒŒ ๋œ๋‹ค. ์•„๋ž˜ ์ด๋ฏธ์ง€์—์„œ favicon.ico๋ฅผ ์š”์ฒญํ•˜๋ฉด DefaultLoginPageGeneratingFilter ํ•„ํ„ฐ๊ฐ€ ์ธ์ฆ์„ ์œ„ํ•ด์„œ login ์š”์ฒญ์„ ๋‹ค์‹œ ํ•˜๊ฒŒ๋œ๋‹ค.

before_ignoring

์ด๋Ÿฌํ•œ ์ •์  ์ž์›์„ ํ•„ํ„ฐ์—์„œ ์ œ์™ธํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด WebSecurity์— ignoring์„ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค. CommonLocations์€ 5๊ฐœ์˜ ์ž์›์— ๋Œ€ํ•ด ํ•„ํ„ฐ๋ฅผ ๋ฌด์‹œํ•˜๋„๋ก ํ•œ๋‹ค.

@Override
public void configure(WebSecurity web) {
    web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}

StaticResourceLocation

WebSecurityConfigurerAdapter ์ƒ์† ๋ฐ›์€ ํด๋ž˜์Šค์—์„œ ์ •์  ์ž์›์„ ๋ฌด์‹œํ•˜๋„๋ก ์„ค์ •ํ•˜๊ณ , ๋‹ค์‹œ ์ธ์ฆ์ด ํ•„์š”์—†๋Š” ํŽ˜์ด์ง€๋ฅผ ์ ‘์†ํ•˜๊ฒŒ ๋˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์Šคํ”„๋ง ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•˜์ง€ ์•Š๊ณ  ๋ฐ”๋กœ ์ •์  ์ž์›์„ ์ „๋‹ฌํ•œ๋‹ค.

after_ignoring

WebAsyncManagerIntegrationFilter

์Šคํ”„๋ง MVC์˜ Async ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ๋•Œ๋„ SecurityContext๋ฅผ ๊ณต์œ ํ•˜๋„๋ก ๋„์™€์ฃผ๋Š” ํ•„ํ„ฐ

  • PreProcess: SecurityContext๋ฅผ ์„ค์ •ํ•œ๋‹ค.
  • Callable: ๋น„๋ก ๋‹ค๋ฅธ ์“ฐ๋ ˆ๋“œ์ง€๋งŒ ๊ทธ ์•ˆ์—์„œ๋Š” ๋™์ผํ•œ SecurityContext๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.
  • PostProcess: SecurityContext๋ฅผ ์ •๋ฆฌ(clean up)ํ•œ๋‹ค.

MVC ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋Š” ์“ฐ๋ ˆ๋“œ ์ž‘์—…์„ ์™„๋ฃŒํ•˜๊ณ  ๋‚˜์„œ๋„ SecurityContextHolder์—์„œ๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋™์ผํ•˜๊ฒŒ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ ์—ญํ• ์„ WebAsyncManagerIntegrationFilter๊ฐ€ ์ˆ˜ํ–‰ํ•œ๋‹ค.

@Controller
public class SampleController {

    @GetMapping("/async-handler")
    @ResponseBody
    public Callable<String> asyncHandler() {
        // http-nio-8080-exec ์“ฐ๋ ˆ๋“œ
        SecurityLogger.log("MVC");

        return () -> {
            // task-1 ์“ฐ๋ ˆ๋“œ
            SecurityLogger.log("Callable");
            return "Async Handler";
        };
    }
    
}

SecurityContextCallableProcessingInterceptor

WebAsyncManagerIntegrationFilter๋Š” SecurityContextCallableProcessingInterceptor๋ฅผ ์‚ฌ์šฉํ•ด์„œ SecurityContextHolder์— SecurityContext ์ •๋ณด๋ฅผ ์ €์žฅํ•œ๋‹ค.

SecurityContextCallableProcessingInterceptor

@Async ์„œ๋น„์Šค์—์„œ SecurityContextHolder ๊ณต์œ 

  • SecurityContextHolder ๊ธฐ๋ณธ ์ „๋žต์€ ThreadLocal
  • @Async ์„œ๋น„์Šค์—์„œ SecurityContextHolder๊ฐ€ ๊ณต์œ  ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•จ
  • SecurityContextHolder ์ „๋žต์„ ๋‹ค์Œ ์ฝ”๋“œ์™€ ๊ฐ™์ด ๋ฐ”๊พธ๋ฉด ์“ฐ๋ ˆ๋“œ ๊ณ„์ธต ์‚ฌ์ด์—์„œ๋„ SecurityContextHolder ์ •๋ณด๊ฐ€ ๊ณต์œ ๋œ๋‹ค
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

SecurityContextPersistenceFilter

SecurityContextRepository๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ธฐ์กด์˜ SecurityContext ์ •๋ณด๋ฅผ ์ฝ์–ด์˜ค๊ฑฐ๋‚˜ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค

  • ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ „๋žต์€ HTTP Session ์‚ฌ์šฉ (HttpSessionSecurityContextRepository)
  • Spring-Session๊ณผ ์—ฐ๋™ํ•˜์—ฌ ์„ธ์…˜ ํด๋Ÿฌ์Šคํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค

HeaderWriterFilter

์‘๋‹ต ํ—ค๋”์— ์‹œํ๋ฆฌํ‹ฐ ๊ด€๋ จ ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๋Š” ํ•„ํ„ฐ

  • XContentTypeOptionsHeaderWriter : ๋งˆ์ž„ ํƒ€์ž… ์Šค๋‹ˆํ•‘ ๋ฐฉ์–ด.
  • XXssProtectionHeaderWriter : ๋ธŒ๋ผ์šฐ์ €์— ๋‚ด์žฅ๋œ XSS ํ•„ํ„ฐ ์ ์šฉ.
  • CacheControlHeadersWriter : ์บ์‹œ ํžˆ์Šคํ† ๋ฆฌ ์ทจ์•ฝ์  ๋ฐฉ์–ด.
  • HstsHeaderWriter : HTTPS๋กœ๋งŒ ์†Œํ†ตํ•˜๋„๋ก ๊ฐ•์ œ.
  • XFrameOptionsHeaderWriter : clickjacking ๋ฐฉ์–ด.

response-headers

CsrfFilter

CSRF ์–ดํƒ ๋ฐฉ์ง€ ํ•„ํ„ฐ

  • ์ธ์ฆ๋œ ์œ ์ €์˜ ๊ณ„์ •์„ ์‚ฌ์šฉํ•ด์„œ ์•…์˜์ ์ธ ๋ณ€๊ฒฝ ์š”์ฒญ์„ ๋งŒ๋“ค์–ด ๋ณด๋‚ด๋Š” ๊ธฐ๋ฒ•
  • ์˜๋„ํ•œ ์‚ฌ์šฉ์ž๋งŒ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•˜๋Š” ํ•„ํ„ฐ
  • CSRF ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฒดํฌ

CsrfFilter Token

form ํ˜•์‹์— hidden ํƒ€์ž…์œผ๋กœ csrf ํ† ํฐ ๊ฐ’์ด ํฌํ•จ๋˜์–ด ์žˆ๋‹ค

csrf-token

Postman์„ ์ด์šฉํ•ด์„œ /signup POST ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด, 401 Unauthorized ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์ด์œ ๋Š” csrf ํ† ํฐ ๊ฐ’์ด ์—†์–ด์„œ ํผ ์ธ์ฆ์ด ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•œ๋‹ค.

csrf-401-code

CsrfFilter Test

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class SignUpControllerTest {

    @Autowired
    MockMvc mockMvc;

    // SignUp Get ์š”์ฒญ
    @Test
    public void signUpForm() throws Exception {
        mockMvc.perform(get("/signup"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("_csrf")));
    }

    // SignUp Post ์š”์ฒญ, csrf ํ† ํฐ์„ ํฌํ•จ 
    @Test
    public void processSignUp() throws Exception {
        mockMvc.perform(post("/signup")
                .param("username", "jayden")
                .param("password", "123")
                .with(csrf()))
                .andDo(print())
                .andExpect(status().is3xxRedirection());
    }
}

CsrfFilter ๋น„ํ™œ์„ฑํ™”

http.csrf().disable();

LogoutFilter

์—ฌ๋Ÿฌ LogoutHanlder๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์•„์›ƒ์‹œ ํ•„์š”ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  LogoutSuccessHandler๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋กœ๊ทธ์•„์›ƒ ํ›„์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค.

Default LogoutHanlder

  • CsrfLogoutHandler
  • SecurityContextLogoutHandler

Default LogoutSuccessHandler

  • SimpleUrlLogoutSuccessHandler

UsernamePasswordAuthenticationFilter

ํผ ๋กœ๊ทธ์ธ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ธ์ฆ ํ•„ํ„ฐ

  • ์‚ฌ์šฉ์ž๊ฐ€ ํผ์— ์ž…๋ ฅํ•œ ์ •๋ณด๋ฅผ ํ† ๋Œ€๋กœ Authentication ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  AuthenticationManager๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ์„ ์‹œ๋„ํ•œ๋‹ค
  • AuthenticationManager(ProviderManager)๋Š” ์—ฌ๋Ÿฌ AuthenticationProvider๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ์„ ์‹œ๋„ํ•˜๋Š”๋ฐ, ๊ทธ ์ค‘ DaoAuthenticationProvider๋Š” UserDetailsService๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ UserDetails ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ •๋ณด์™€ ๋™์ผํ•œ์ง€ ๋น„๊ตํ•œ๋‹ค

DefaultLoginPageGeneratingFilter

๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•„ํ„ฐ

์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„ ๋ณ€๊ฒฝ

http.formLogin()
        .usernameParameter("app_username")
        .passwordParameter("app_password");

DefaultLoginPageGeneratingFilter

์ปค์Šคํ…€ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€

์ปค์Šคํ…€ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ๋“ฑ๋กํ•˜๋ฉด FilterChainProxy์—์„œ DefaultLoginPageGeneratingFilter์™€ DefaultLogoutPageGeneratingFilter ๋‘ ํ•„ํ„ฐ๊ฐ€ ์ œ์™ธ๋จ

http.formLogin()
        .loginPage("/login");

DefaultLogoutPageGeneratingFilter

๊ธฐ๋ณธ ๋กœ๊ทธ์•„์›ƒ ํŽ˜์ด์ง€๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•„ํ„ฐ

๋กœ๊ทธ์ธ/๋กœ๊ทธ์•„์›ƒ ํผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

๋กœ๊ทธ์ธ/๋กœ๊ทธ์•„์›ƒ ํผ ํŽ˜์ด์ง€๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•˜๊ธฐ ์œ„ํ•ด์„œ LogInOutController๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. ์ด ์ปจํŠธ๋กค๋Ÿฌ๋Š” Get ์š”์ฒญ์œผ๋กœ ๋กœ๊ทธ์ธ/๋กœ๊ทธ์•„์›ƒ ํŽ˜์ด์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

@Controller
public class LogInOutController {

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

    @GetMapping("/logout")
    public String logoutForm() {
        return "/logout";
    }

}

SpirngSecurity ์„ค์ •์—์„œ ๋กœ๊ทธ์ธ ํผ ํŽ˜์ด์ง€ URL๊ณผ ๋กœ๊ทธ์•„์›ƒ URL์„ ์„ค์ •ํ•œ๋‹ค.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .mvcMatchers("/", "/info", "/signup").permitAll()
            .mvcMatchers("/admin").hasRole("ADMIN")
            .mvcMatchers("/user").hasRole("USER")
            .anyRequest().authenticated()
            .accessDecisionManager(accessDecisionManager());


    http.httpBasic();

    http.formLogin()
            .loginPage("/login")
            .permitAll();

    http.logout()
            .logoutUrl("/logout")
            .logoutSuccessUrl("/");

    SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}

BasicAuthenticationFilter

  • Http Basic ์ธ์ฆ์„ ์ง€์›ํ•˜๋Š” ํ•„ํ„ฐ
  • ์š”์ฒญ ํ—ค๋”์— ์•„์ด๋””์™€ ํŒจ์Šค์›Œ๋“œ๋ฅผ ๋ณด๋‚ด๋ฉด ๋ธŒ๋ผ์šฐ์ € ๋˜๋Š” ์„œ๋ฒ„๊ฐ€ ๊ทธ ๊ฐ’์„ ์ฝ์–ด์„œ ์ธ์ฆํ•˜๋Š” ๋ฐฉ์‹
  • ์ •๋ณด๋Š” Base64 ์ธ์ฝ”๋”ฉ ๋˜์–ด ๋ณด๋‚ด์ง€๊ณ  ์ฝ์„ ๋•Œ ๋‹ค์‹œ ๋””์ฝ”๋”ฉํ•ด์„œ ๊ฐ’์„ ์ฝ๋Š”๋‹ค
  • ์Šค๋‹ˆํ•‘ํ•˜๋ฉด ์š”์ฒญ ์ •๋ณด๋ฅผ ์‰ฝ๊ฒŒ ์ทจ๋“ํ•˜๋Š” ์œ„ํ—˜์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— HTTPS๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์„ ๊ถŒ์žฅ
http.httpBasic();

RequestCacheAwareFilter

ํ˜„์žฌ ์š”์ฒญ๊ณผ ๊ด€๋ จ ์žˆ๋Š” ์บ์‹œ๋œ ์š”์ฒญ์ด ์žˆ๋Š”์ง€ ์ฐพ์•„์„œ ์ ์šฉํ•˜๋Š” ํ•„ํ„ฐ

  • ์บ์‹œ๋œ ์š”์ฒญ์ด ์—†๋‹ค๋ฉด, ํ˜„์žฌ ์š”์ฒญ ์ฒ˜๋ฆฌ
  • ์บ์‹œ๋œ ์š”์ฒญ์ด ์žˆ๋‹ค๋ฉด, ์บ์‹œ๋œ ์š”์ฒญ ์ฒ˜๋ฆฌ

๋Œ€์‹œ๋ณด๋“œ(๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€) ํŽ˜์ด์ง€๋ฅผ ์ ‘์†ํ•˜๋ ค๊ณ  ํ•˜๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ๋‹ค. ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์—์„œ ๋กœ๊ทธ์ธ ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋‚˜๋ฉด, RequestCacheAwareFilter์—์„œ ์บ์‹œํ•œ ์š”์ฒญ(๋Œ€์‹œ๋ณด๋“œ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋ ค๋Š” ์š”์ฒญ)์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

public class RequestCacheAwareFilter extends GenericFilterBean {

	private RequestCache requestCache;

	public RequestCacheAwareFilter() {
		this(new HttpSessionRequestCache());
	}

	public RequestCacheAwareFilter(RequestCache requestCache) {
		Assert.notNull(requestCache, "requestCache cannot be null");
		this.requestCache = requestCache;
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
				(HttpServletRequest) request, (HttpServletResponse) response);

        // ์บ์‹œ๋œ ์š”์ฒญ์ด ์žˆ๋Š”์ง€ ์ฒดํฌํ•˜๊ณ  ํ˜„์žฌ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ• ์ง€ ์บ์‹œ๋œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ• ์ง€ ๊ฒฐ์ •
		chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
				response);
	}

}

SecurityContextHolderAwareRequestFilter

์‹œํ๋ฆฌํ‹ฐ ๊ด€๋ จ ์„œ๋ธ”๋ฆฟ API๋ฅผ ๊ตฌํ˜„ํ•ด์ฃผ๋Š” ํ•„ํ„ฐ

  • HttpServletRequest#authenticate(HttpServletResponse)
  • HttpServletRequest#login(String, String)
  • HttpServletRequest#logout()
  • AsyncContext#start(Runnable)

AnonymousAuthenticationFilter

SecurityContext์— Authentication์ด null ๊ฐ’์ด๋ฉด, ์ต๋ช… Authentication์„ ์ƒ์„ฑํ•ด์„œ ๋„ฃ์–ด์ค€๋‹ค. Authentication์ด null ๊ฐ’์ด ์•„๋‹ˆ๋ฉด, ์•„๋ฌด์ผ๋„ ํ•˜์ง€ ์•Š๋Š” ํ•„ํ„ฐ์ด๋‹ค. (null object pattern)

AnonymousAuthenticationFilter_dofilter

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋Š” ๋ณ„๋„์˜ ์„ค์ •์ด ์—†์–ด๋„ AnonymousUser๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒ์„ฑํ•œ๋‹ค. Principal์€ anonymousUser์ด๊ณ  ๊ถŒํ•œ์€ ROLE_ANONYMOUS๋กœ ์„ค์ •ํ•œ๋‹ค.

AnonymousAuthenticationFilter

SessionManagementFilter

  • ์„ธ์…˜ ๋ณ€์กฐ ๋ฐฉ์ง€ ์ „๋žต ์„ค์ •
    • ์„ธ์…˜ ๋ณ€์กฐ ๋ฐฉ์ง€ ์ „๋žต์œผ๋กœ changeSessionId๋กœ ์„ค์ •
    http.sessionManagement()
            .sessionFixation()
            .changeSessionId();
  • ์œ ํšจํ•˜์ง€ ์•Š์€ ์„ธ์…˜์„ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์‹œํ‚ฌ URL ์„ค์ •
  • ๋™์‹œ์„ฑ ์ œ์–ด
    • ์„ธ์…˜ ๊ฐœ์ˆ˜ ์ œ์–ด
    • ์ถ”๊ฐ€ ๋กœ๊ทธ์ธ์„ ๋ง‰์„์ง€ ์—ฌ๋ถ€ (๊ธฐ๋ณธ๊ฐ’์€ false)
    http.sessionManagement()
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true);
  • ์„ธ์…˜ ์ƒ์„ฑ ์ „๋žต
    1. ALWAYS
    2. NEVER
    3. IF_REQUIRED
    4. STATELESS

ExceptionTranslationFilter

  • try-catch ๊ตฌ๋ฌธ์œผ๋กœ ๊ฐ์‹ธ๊ณ  FilterSecurityInterceptor๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค
  • FilterSecurityInterceptor๋Š” AccessDecisionManager๋ฅผ ์ด์šฉํ•ด์„œ ์ธ๊ฐ€ ์ฒ˜๋ฆฌ๋ฅผ ํ•จ
  • AuthenticationEntryPoint, AccessDeniedException ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•จ

FilterSecurityInterceptor

  • Http ๋ฆฌ์†Œ์Šค ์‹œํ๋ฆฌํ‹ฐ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ํ•„ํ„ฐ
  • AccessDecisionManager๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ๊ฐ€๋ฅผ ์ฒ˜๋ฆฌ
http.authorizeRequests()
        .mvcMatchers("/", "/info", "/signup").permitAll()
        .mvcMatchers("/admin").hasRole("ADMIN")
        .mvcMatchers("/user").hasRole("USER")
        .anyRequest().authenticated()
        .accessDecisionManager(accessDecisionManager());

security-filter-list

RememberMeAuthenticationFilter

  • ์„ธ์…˜์ด ์‚ฌ๋ผ์ง€๊ฑฐ๋‚˜ ๋งŒ๋ฃŒ๊ฐ€ ๋˜๋”๋ผ๋„ ์ฟ ํ‚ค ๋˜๋Š” DB๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ €์žฅ๋œ ํ† ํฐ ๊ธฐ๋ฐ˜์œผ๋กœ ์ธ์ฆ์„ ์ง€์›ํ•˜๋Š” ํ•„ํ„ฐ

RememberMe ์„ค์ •

ํŽ˜์ด์ง€์— ์ ‘์†ํ•˜๋ฉด ์„œ๋ฒ„์—์„œ ์„ธ์…˜์ด ์ƒ์„ฑ๋˜๊ณ  ์›น ๋ธŒ๋ผ์šฐ์ € ์ฟ ํ‚ค์— ์„ธ์…˜ ์•„์ด๋”” ์ •๋ณด๊ฐ€ ๋‹ด๊ธด๋‹ค. ๋กœ๊ทธ์ธ ํ•˜๊ณ  ๋‚˜๋ฉด ์„œ๋ฒ„๋Š” ํ•ด๋‹น ์„ธ์…˜์„ ์ธ์ฆ๋œ ์„ธ์…˜์œผ๋กœ ์ทจ๊ธ‰ํ•œ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ์›น ๋ธŒ๋ผ์šฐ์ € ์ฟ ํ‚ค์—์„œ ์„ธ์…˜ ์•„์ด๋””๋ฅผ ์‚ญ์ œํ•˜๊ฒŒ ๋˜๋ฉด, ์ธ์ฆ๋œ ์„ธ์…˜์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„๋Š” ๋‹ค์‹œ ๋กœ๊ทธ์ธ ์ฐฝ์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋œ๋‹ค.

์„ธ์…˜ ์•„์ด๋””๋ฅผ ์‚ญ์ œํ•˜๋ฉด SecurityContextHolder์—์„œ ์ธ์ฆ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„๋Š” ์ธ์ฆ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๋กœ ํŒ๋‹จํ•˜๊ณ  ์ธ์ฆ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€์˜ ์ ‘์†์„ ๋ง‰๋Š”๋‹ค.

session-id

๋‹ค์Œ๊ณผ ๊ฐ™์ด rememberMe ์„ค์ •์„ ํ•˜๊ณ  ๋กœ๊ทธ์ธํ•  ๋•Œ, remember-me ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋„˜๊ธฐ๋ฉด remember-me ์ฟ ํ‚ค ์ •๋ณด๊ฐ€ ์ƒ๊ธฐ๊ฒŒ ๋œ๋‹ค. remember-me ์ฟ ํ‚ค์—๋Š” ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ์œ ํšจ ๊ธฐ๊ฐ„ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ๋‹ค.

http.rememberMe()
        .userDetailsService(accountService)
        .key("remember-me");

remember-me

์•ž์—์„œ ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋‹ค์‹œ ์„ธ์…˜ ์•„์ด๋””๋ฅผ ์‚ญ์ œํ•˜๊ณ  ๋‚˜์„œ ๋‹ค์‹œ ์ธ์ฆ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•˜๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ํ•˜์ง€ ์•Š๋Š”๋‹ค. ํ•„ํ„ฐ ์ฒด์ธ ๋ชฉ๋ก์—์„œ RememberMeAuthenticationFilter๊ฐ€ RememberMeAuthenticationToken ์ •๋ณด๋ฅผ ์ด์šฉํ•ด์„œ ์ธ์ฆํ•˜๊ณ , ์ธ์ฆ๋œ ์ •๋ณด๋ฅผ ๋‹ค์‹œ SecurityContextHolder์— ๋„ฃ์–ด์ค€๋‹ค.

RememberMeAuthenticationFilter

ํฌ๋กฌ ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ˜„์žฌ ์ ‘์†ํ•œ ํŽ˜์ด์ง€์˜ ์ฟ ํ‚ค ์ •๋ณด๋ฅผ ์‰ฝ๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ EditThisCookie๋ฅผ ์„ค์น˜ํ•ด์„œ ์‚ฌ์šฉํ–ˆ๋‹ค.

์ปค์Šคํ…€ ํ•„ํ„ฐ ์ถ”๊ฐ€ํ•˜๊ธฐ

Filter๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์€ ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์ด ์žˆ์ง€๋งŒ ์ด๋ฒˆ์— ์ถ”๊ฐ€ํ•˜๋Š” LoggingFilter๋Š” GenericFilterBean ํด๋ž˜์Šค๋ฅผ ์ƒ์† ๋ฐ›์•„์„œ ๊ตฌํ˜„ํ•˜๋„๋ก ํ•œ๋‹ค. GenericFilterBean ํด๋ž˜์Šค์—๋Š” ๊ธฐ๋ณธ์ ์ธ ์„ค์ •์ด ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ƒ์† ๋ฐ›์€ ํด๋ž˜์Šค๊ฐ€ doFilter ๋ฉ”์„œ๋“œ๋งŒ ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•˜๋ฉด ๋œ๋‹ค.

public class LoggingFilter extends GenericFilterBean {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        chain.doFilter(request, response);

        stopWatch.stop();
        logger.info(stopWatch.prettyPrint());
    }
}

์ƒˆ๋กœ ์ƒ์„ฑํ•œ LoggingFilter ํ•„ํ„ฐ๋ฅผ ํ•„ํ„ฐ ๋ชฉ๋ก์—์„œ ์›ํ•˜๋Š” ์œ„์น˜๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. WebAsyncManagerIntegrationFilter ํ•„ํ„ฐ๋Š” ํ•„ํ„ฐ ๋ชฉ๋ก์—์„œ ๊ฐ€์žฅ ์ฒซ ๋ฒˆ์งธ์— ์œ„์น˜ํ•˜๋Š” ํ•„ํ„ฐ์ด๋‹ค.

http.addFilterBefore(new LoggingFilter(), WebAsyncManagerIntegrationFilter.class);

loggingfilter

๋ฉ”์„œ๋“œ ์‹œํ๋ฆฌํ‹ฐ

  • ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ๊ธฐ๋Šฅ์„ ์›น ๋˜๋Š” ๋ฐ์Šคํฌํƒ‘ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ๊ธฐ๋Šฅ
  • ๋ฉ”์„œ๋“œ ์‹œํ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„ค์ • ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•œ๋‹ค
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class MethodSecurity {
    
}
  • ๊ถŒํ•œ์— ๋”ฐ๋ผ ํŠน์ • ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ ์œ ๋ฌด๋ฅผ ์„ค์ •ํ•˜๊ณ  ์‹ถ์œผ๋ฉด @Secured ์• ๋…ธํ…Œ์ด์…˜๊ณผ ํ•จ๊ป˜ ROLE_USER ์ด๋ฆ„์„ ์ถ”๊ฐ€ํ•œ๋‹ค
  • @Secured, @RolesAllowed, PreAuthorize ์• ๋…ธํ…Œ์ด์…˜๋“ค์€ dashboard ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค
  • @PostAuthorize ์• ๋…ธํ…Œ์ด์…˜์€ dashboard ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•œ ์ดํ›„์— ๊ถŒํ•œ์„ ์ฒดํฌํ•œ๋‹ค
@Secured("ROLE_USER")
@RolesAllowed("ROLE_USER")
@PreAuthorize("hasRole(USER)")
public void dashboard() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    UserDetails userDetails = (UserDetails) authentication.getPrincipal();
    System.out.println("================");
    System.out.println(authentication);
    System.out.println(authentication.getName());
}

@AuthenticationPrincipal

๊ธฐ์กด์— Principal ์ •๋ณด๋ฅผ ์–ป์œผ๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ–ˆ๋‹ค. ์ฒซ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์„ ํ†ตํ•ด ์–ป์€ Principal ๊ฐ์ฒด์—์„œ๋Š” ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์ •๋ณด๋งŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค. ๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์€ ์šฐ๋ฆฌ๊ฐ€ ์„ ์–ธํ•œ ๋„๋ฉ”์ธ ํƒ€์ž…์˜ ํด๋ž˜์Šค๋กœ ๋ณ€ํ™˜ํ•˜๋ฉด ์ด๋ฆ„, ์—ญํ• , ๋น„๋ฐ€๋ฒˆํ˜ธ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

  1. Argument์— Principal๋ฅผ ์ถ”๊ฐ€

    @GetMapping(value = "/dashboard")
    public String dashboard(Model model, Principal principal) {
        model.addAttribute("message", "Hello " + principal.getName());
        AccountContext.setAccount(accountRepository.findByUsername(principal.getName()));
        sampleService.dashboard();
        return "dashboard";
    }
  2. SecurityContextHolder์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•

    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

์ด๋ฒˆ์— ์‚ดํŽด๋ณผ @AuthenticationPrincipal ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ์šฐ๋ฆฌ๊ฐ€ ์„ ์–ธํ•œ ๋„๋ฉ”์ธ ํƒ€์ž…์˜ Principal ์ •๋ณด๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. ์• ๋…ธํ…Œ์ด์…˜์„ ํ™•์ธํ•˜๊ณ  ArgumentResolver๊ฐ€ ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋„ฃ์–ด์ค€๋‹ค.

@GetMapping(value = "/")
public String index(Model model, @AuthenticationPrincipal UserAccount userAccount) {
    if (userAccount == null) {
        model.addAttribute("message", "Hello Spring Security");
    } else {
        model.addAttribute("message", "Hello " + userAccount.getUsername());
    }
    return "index";
}

UserAccount ํด๋ž˜์Šค๋Š” User ํด๋ž˜์Šค๋ฅผ ์ƒ์† ๋ฐ›์•„ ๊ตฌํ˜„ํ•œ๋‹ค.

@Getter
public class UserAccount extends User {

    private Account account;

    public UserAccount(Account account) {
        super(account.getUsername(), account.getPassword(), List.of(new SimpleGrantedAuthority("ROLE_" + account.getRole())));
        this.account = account;
    }

}

UserAccount ๊ฐ์ฒด์—์„œ Account ์ •๋ณด๋งŒ ๊ฐ€์ ธ์˜ค๊ณ  ์‹ถ์„ ๋•Œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•œ๋‹ค

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
public @interface CurrentUser {
}
@GetMapping(value = "/")
public String index(Model model, @CurrentUser Account account) {
    if (account == null) {
        model.addAttribute("message", "Hello Spring Security");
    } else {
        model.addAttribute("message", "Hello " + account.getUsername());
    }
    return "index";
}

Releases

No releases published

Packages

No packages published