티스토리 뷰

Spring

[SpringBoot] Spring Security란?

망나니개발자 2020. 6. 10. 14:25
반응형

대부분의 시스템에서는 회원의 관리를 하고 있고, 그에 따른 인증(Authentication)과 인가(Authorization)에 대한 처리를 해주어야 한다. Spring에서는 Spring Security라는 별도의 프레임워크에서 관련된 기능을 제공하고 있는데, 이번에는 Spring Security에 대해서 알아보도록 하겠다.

 

 

1. Spring Security란? 


[ Spring Security란? ]

Spring Security는 Spring 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크이다. Spring Security는 '인증'과 '권한'에 대한 부분을 Filter 흐름에 따라 처리하고 있다. Filter는 Dispatcher Servlet으로 가기 전에 적용되므로 가장 먼저 URL 요청을 받지만, Interceptor는 Dispatcher와 Controller사이에 위치한다는 점에서 적용 시기의 차이가 있다. Spring Security는 보안과 관련해서 체계적으로 많은 옵션을 제공해주기 때문에 개발자 입장에서는 일일이 보안관련 로직을 작성하지 않아도 된다는 장점이 있다.

이러한 Spring Security의 아키텍쳐는 아래와 같다. (처리 과정은 다음 글에서 자세히 설명하도록 하겠습니다.)

 

 

 

[ 인증(Authorizatoin)과 인가(Authentication) ]

  • 인증(Authentication): 해당 사용자가 본인이 맞는지를 확인하는 절차

  • 인가(Authorization): 인증된 사용자가 요청한 자원에 접근 가능한지를 결정하는 절차 

 

Spring Security는 기본적으로 인증 절차를 거친 후에 인가 절차를 진행하게 되며, 인가 과젱에서 해당 리소스에 대한 접근 권한이 있는지 확인을 하게 된다. Spring Security에서는 이러한 인증과 인가를 위해 Principal을 아이디로, Credential을 비밀번호로 사용하는 Credential 기반의 인증 방식을 사용한다. 

  • Principal(접근 주체): 보호받는 Resource에 접근하는 대상

  • Credential(비밀번호): Resource에 접근하는 대상의 비밀번호

 

2. Spring Security 모듈


[ Spring Security 주요 모듈 ]

Spring Security의 주요 모듈은 아래와 같이 구성되며 각 항목들에 대해서 간단히 살펴보도록 하자.

 

[ SecurityContextHolder ]

SecurityContextHolder는 보안 주체의 세부 정보를 포함하여 응용프래그램의 현재 보안 컨텍스트에 대한 세부 정보가 저장된다. SecurityContextHolder는 기본적으로 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL 방법과SecurityContextHolder.MODE_THREADLOCAL 방법을 제공한다.

 

[ SecurityContext ]

Authentication을 보관하는 역할을 하며, SecurityContext를 통해 Authentication 객체를 꺼내올 수 있다.

 

[ Authentication ]

Authentication는 현재 접근하는 주체의 정보와 권한을 담는 인터페이스이다. Authentication 객체는 Security Context에 저장되며, SecurityContextHolder를 통해 SecurityContext에 접근하고, SecurityContext를 통해 Authentication에 접근할 수 있다.

public interface Authentication extends Principal, Serializable {
    // 현재 사용자의 권한 목록을 가져옴
    Collection<? extends GrantedAuthority> getAuthorities();
    
    // credentials(주로 비밀번호)을 가져옴
    Object getCredentials();
    
    Object getDetails();
    
    // Principal 객체를 가져옴.
    Object getPrincipal();
    
    // 인증 여부를 가져옴
    boolean isAuthenticated();
    
    // 인증 여부를 설정함
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

 

[ UsernamePasswordAuthenticationToken ]

UsernamePasswordAuthenticationToken은 Authentication을 implements한 AbstractAuthenticationToken의 하위 클래스로, User의 ID가 Principal 역할을 하고, Password가 Credential의 역할을 한다. UsernamePasswordAuthenticationToken의 첫 번째 생성자는 인증 전의 객체를 생성하고, 두번째 생성자는 인증이 완려된 객체를 생성한다.

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
    // 주로 사용자의 ID에 해당함
    private final Object principal;
    // 주로 사용자의 PW에 해당함
    private Object credentials;
    
    // 인증 완료 전의 객체 생성
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
		super(null);
		this.principal = principal;
		this.credentials = credentials;
		setAuthenticated(false);
	}
    
    // 인증 완료 후의 객체 생성
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
			Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		this.credentials = credentials;
		super.setAuthenticated(true); // must use super, as we override
	}
}


public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
}

 

[ AuthenticationProvider ]

AuthenticationProvider에서는 실제 인증에 대한 부분을 처리하는데, 인증 전의 Authentication객체를 받아서 인증이 완료된 객체를 반환하는 역할을 한다. 아래와 같은 AuthenticationProvider 인터페이스를 구현해서 Custom한 AuthenticationProvider을 작성해서 AuthenticationManager에 등록하면 된다.

public interface AuthenticationProvider {

	// 인증 전의 Authenticaion 객체를 받아서 인증된 Authentication 객체를 반환
    Authentication authenticate(Authentication var1) throws AuthenticationException;

    boolean supports(Class<?> var1);
    
}

 

 

[ Authentication Manager ]

인증에 대한 부분은 SpringSecurity의 AuthenticatonManager를 통해서 처리하게 되는데, 실질적으로는 AuthenticationManager에 등록된 AuthenticationProvider에 의해 처리된다. 인증이 성공하면 2번째 생성자를 이용해 인증이 성공한(isAuthenticated=true) 객체를 생성하여 Security Context에 저장한다. 그리고 인증 상태를 유지하기 위해 세션에 보관하며, 인증이 실패한 경우에는 AuthenticationException를 발생시킨다.

public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication) 
		throws AuthenticationException;
}

 

 

AuthenticationManager를 implements한 ProviderManager는 실제 인증 과정에 대한 로직을 가지고 있는 AuthenticaionProvider를 List로 가지고 있으며, ProividerManager는 for문을 통해 모든 provider를 조회하면서 authenticate 처리를 한다.

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
    public List<AuthenticationProvider> getProviders() {
		return providers;
	}
    public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		Authentication result = null;
		boolean debug = logger.isDebugEnabled();
        //for문으로 모든 provider를 순회하여 처리하고 result가 나올 때까지 반복한다.
		for (AuthenticationProvider provider : getProviders()) {
            ....
			try {
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			}
            ....
		}
		throw lastException;
	}
}

 

위에서 설명한 ProviderManager에 우리가 직접 구현한 CustomAuthenticationProvider를 등록하는 방법은 WebSecurityConfigurerAdapter를 상속해 만든 SecurityConfig에서 할 수 있다. WebSecurityConfigurerAdapter의 상위 클래스에서는 AuthenticationManager를 가지고 있기 때문에 우리가 직접 만든 CustomAuthenticationProvider를 등록할 수 있다.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
    @Bean
    public AuthenticationManager getAuthenticationManager() throws Exception {
        return super.authenticationManagerBean();
    }
      
    @Bean
    public CustomAuthenticationProvider customAuthenticationProvider() throws Exception {
        return new CustomAuthenticationProvider();
    }
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(customAuthenticationProvider());
    }
}

 

[ UserDetails ]

인증에 성공하여 생성된 UserDetails 객체는 Authentication객체를 구현한 UsernamePasswordAuthenticationToken을 생성하기 위해 사용된다. UserDetails 인터페이스를 살펴보면 아래와 같이 정보를 반환하는 메소드를 가지고 있다. UserDetails 인터페이스의 경우 직접 개발한 UserVO 모델에 UserDetails를 implements하여 이를 처리하거나 UserDetailsVO에 UserDetails를 implements하여 처리할 수 있다.

public interface UserDetails extends Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
    
}

 

[ UserDetailsService ]

UserDetailsService 인터페이스는 UserDetails 객체를 반환하는 단 하나의 메소드를 가지고 있는데, 일반적으로 이를 구현한 클래스의 내부에 UserRepository를 주입받아 DB와 연결하여 처리한다. UserDetails 인터페이스는 아래와 같다.

public interface UserDetailsService {

    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;

}

 

[ Password Encoding ]

AuthenticationManagerBuilder.userDetailsService().passwordEncoder() 를 통해 패스워드 암호화에 사용될 PasswordEncoder 구현체를 지정할 수 있다.

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	// TODO Auto-generated method stub
	auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

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

 

[ GrantedAuthority ]

GrantAuthority는 현재 사용자(principal)가 가지고 있는 권한을 의미한다. ROLE_ADMIN나 ROLE_USER와 같이 ROLE_*의 형태로 사용하며, 보통 "roles" 이라고 한다. GrantedAuthority 객체는 UserDetailsService에 의해 불러올 수 있고, 특정 자원에 대한 권한이 있는지를 검사하여 접근 허용 여부를 결정한다.

 

 

 

 

 

 

관련 포스팅

  1. SpringSecurity란? (1/2)

  2. SpringSecurity 처리 과정 및 구현 예제(2/2)

 

 

 

참고 자료

 

반응형
댓글
댓글쓰기 폼
  • 화이팅 개념이 너무 어렵네요..ㅠㅠ좋은글 감사합니다. 2020.08.06 16:57
  • 망나니개발자 Spring Security가 하나의 새로운 프레임워크라서 저도 처음에 공부할게 많았습니다ㅜㅜ 여러번 반복해서 보다보면 괜찮아지실거에요! 2020.08.06 20:15 신고
  • ㅎㅎ 스프링시큐리티를 잘 모르고 썼던 것 같은데, 이 글 읽고 어느정도 느낌이 잡히는 것 같아요! 쉽고 자세히 설명해주셔서 감사합니다!! 2020.11.10 21:47
  • 망나니개발자 이런 부분을 알고 구현하는게 꽤나 중요한 것 같습니다:) 좋게 봐주셔서 감사합니다 ㅎㅎ 2020.11.11 09:33 신고
  • pikiforyou 정말 너무 잘보고있습니다.. ㅠㅠ 몇일내내 헤매다가 도저히 시간이 부족해서 결국 댓글로 물어보게 되었습니다 ㅠㅠ ....
    하도 많이 보고, 설명을 잘 해주셔서 이해는 다 되었는데, 저는 스프링부트가 아니라 일반 스프링에서 적당히 바꿔가면서 실행해서 그런지 ㅜ 될듯 말듯 실행이 되지않네요 ...
    팀프로젝트라 스프링+오라클 설정으로 하고 있거든요 . (rest api식으로는 만들순 있어도, 내장된 jsp뷰페이지를 이용하다보니 프론트/백엔드가 완전히 분리되지도 않았습니다 ㅠㅠ 개인 프로젝트라면 서버를 배포시켜보고 한번 프론트/백 나눠서 구현해봤을것같은데 일단은 못합니다 ㅠㅠ)
    그렇다보니 JPA부분으로 하신건 dao/serviceimpl 식으로 구현하였고, boot에서만 쓰이는 것들은 찾아서 필터설정을 하고 인터셉터 설정을 해보았는데 ..
    토큰을 받아오고, 데이터베이스에 암호화해서 저장하는것까지 다 했나 생각했더니,
    (=토큰은 그냥 text형태로 응답값으로 나오기는 하고, 디비에는 그냥 role형태까지 맞춰서 암호화된 비번과 함께 저장만 됩니다...ㅎㅎㅎ)
    정작 login할때 보니까 아예 customAutenticationFilter로 들어가질 않는것같더라구요. provider를 통해서 userpasswordToken을 발급하는것부터 해야하는데 거기에 그 어떤 콘솔(sys~)을 남겨도 보이지가 않아요. 즉 가질 않는것같더라구요 ㅠ
    그래서 jsp페이지라 form 로그인 기반인가해서 처음 configurer 부터 봤더니, 제가 개발자님이 적어주신것처럼 .permitAll() + interceptor등을 쓰지않고 그냥 /**.hasRole('ROLE_USER') 이렇게 설정한 부분도 그냥 ... 중간에서 가로채지않고 그냥 다 실행되더라구요 ...
    ㅠㅠ 짚이는게 너무 많은데, 일반 스프링으로 구현하기에는 많이 무리가 있을까요 ...?
    .xml 설정이 남아있는 시점에서 @configuration이 먹히지 않는건지.. (동시엔 적용못하는지)
    아니면 rest-api가 아니면 form.login방식으로 구현해야하는건지..
    jsp페이지에서 ajax(post)로 요청하는 방식으로 하면 실행이 안되는건지 ...
    원래라면 로그인을 하면 자동으로 filter->provider->token만들기까지 진행이 되는게 콘솔에 찍으면 찍히는건지 ㅠㅠㅠ
    ㅠㅠ정말 너무 답답해서 결국 댓글을 쓰게되었는데, 너무 기초적인걸 물어보는듯하여 죄송합니다 ㅠㅠ 혼자 공부하다보니 너무 답답하네요 ㅠㅠ
    2020.12.27 22:02 신고
  • 망나니개발자 음 우선 하나씩 말씀 드릴께요!
    1. 프론트엔드와 백엔드가 완전히 분리되어 있지 않아도 상관없습니다. 어차피 요청을 보내는 url을 가로채기 때문에 해당 부분은 전혀 문제가 되지 않습니다. 다만 데이터를 전송하는 content-type에 따라 CustomAuthenticationFilter에서 HttpServletRequest로 데이터를 얻는 방식을 변경해주시면 됩니다.
    2. Boot가 아닌 일반 스프링으로 구현해도 전혀 문제가 되지 않습니다. 어차피 Spring의 하위 프레임워크일 뿐이라서 설정만 맞게 해주시면 문제는 없습니다.(실제 저도 Boot가 아닌 Spring으로도 사용해보았습니다.)
    3. Configuration은 Bean을 생성하기 위한 Spring의 어노테이션이므로 Spring에서도 잘 동작 할 것입니다.
    4. 콘솔에 찍히는거는 log 관련 설정을 해주셨으면 남을 것입니다. 손쉽게 확인하려면 System.out.println으로 Filter 내의 함수가 출력되었는지 확인하셔도 될 것 같습니다.
    5. 결국 현재 상황을 보면 Spring Security에 대한 설정에 누락된 부분이 있는 것 같습니다. 상황이 워낙 많아서 특정지을 순 없겠지만 우선 아래와 같은 사항들을 확인해보시면 좋을 것 같습니다.
    1. Servlet Context에 security Filter 등록
    ex)
    context.addFilter("securityFilter", new DelegatingFilterProxy("springSecurityFilterChain")) .addMappingForUrlPatterns(null, false, "/*");

    2. Security를 사용하기 위한 어노테이션의 처리 여부
    @EnableWebSecurity

    3. @PreAuthorize 등과 같은 어노테이션이 안먹힐 경우에는 아래의 어토네이션을 Security 설정 클래스에 추가
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    2020.12.28 12:15 신고
  • pikiforyou 친절하고 자세한 답변 정말 감사드립니다 ㅠㅠㅠ사실 너무 답답해서 적긴했지만, 이렇게 빠르게 대답까지 해주셔서 믿기지않고 정말 힘이됩니다...ㅜㅜ
    일단 문제가 되지않는다해서 다시 반나절 정도 투자해보려구요..!
    >>사실 가장 궁금하고 혼동되는게, 기존에 있던 xml설정파일을 유지하면서, @bean설정?을 둬도 괜찮을지였습니다.
    저만 쓰는 프로젝트 파일이 아니다보니, 모든 xml설정을 다 java bean 기반으로 바꿀수가 없었거든요...
    그래서 webMvCConfigure부분에서 dispatcher servlet의 필터설정을 하는거라고 하길래(boot에서 import된 pathrequest?였나 그걸 스프링으로 구현해보기위해서 찾아봤습니다) 그것도 좀 미심쩍긴합니다만..(여러가지를 짬뽕으로 섞어서 필터 등록을 했습니다.. header filter를...ㅋㅋㅋ)
    일단, 알려주신대로 차근차근 해보려고합니다!
    혹 몇가지 물어봐도 괜찮을까요..? 사실 답을 구하기전에 먼저 물어보는걸 쓰다보니, 죄송함을 먼저 구합니다... v_v


    [1. 데이터를 전송하는 content-type에 따라 CustomAuthenticationFilter에서 HttpServletRequest로 데이터를 얻는 방식을 변경]
    -> 현재 커스텀필터부분은 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) ~ 이런식으로 시작되고 있습니다. 제가 jsp view단에서는
    $.ajax({
    type : "POST",
    contentType : "application/json",
    url : "/user/login",
    data : JSON.stringify(value),
    dataType : "json",
    success : "", error : ""
    });

    이런식의 평범한(?) ajax 요청으로 보내주고 있습니다. 일단은 json으로 보내주기위하여(id/password값) contentType을 "application/json"으로 보내고 있습니다만, 개발자님께서 작성하신 로직을 처리하기위해선 다른 방식으로 보내야할까요?
    만약 제가 react등을 쓴다면 axios를 이용해서 api요청을 보내긴 할것같습니다만, 현재는 저 방식으로 보내고 있습니다. jquery api 문서를 보면 default인 "application/x-www-form-urlencoded;"요방식과, 제가 사용한 "application/json" 방식 두가지인것같은데 혹 다른 방식으로 요청해야하는걸까요 ?
    참고하시기 편하게 개발자님께서 적어주신 커스텀필터의 로직부분은 이렇게 생겼습니다.
    UsernamePasswordAuthenticationToken authRequest;
    try{
    User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
    authRequest = new UsernamePasswordAuthenticationToken(user.getId(), user.getPassword());
    }
    ~~ 생략 ~~
    setDetails(request, authRequest);

    ----------------------------------------

    [ 4. log 관련 설정을 해주셨으면 남을 것입니다. 손쉽게 확인하려면 System.out.println으로 Filter 내의 함수가 출력되었는지 확인 ]
    아직 로그를 제대로 공부를 하지못했다보니, 부끄럽게도 System.out.println으로 작성하고 있었습니다 :)..
    모든 함수의 진입점 부분에서 [위치][실행부분]형식으로 작성해두었는데, 맨처음 bean등록할때 상위만 한번 훑고 지나가고(커스텀필터의 경우 생성자 까지는 가나, provider쪽은 또 찍히지않았습니다), 이후 어떤 uri로 들어가도 더이상 콘솔이 찍히는 부분이 없었습니다. 그래서, 아예 시작할때 인터셉트도 되지않고, custom 필터로 들어가지않고 컨트롤러에 의해 단순 url이동만 일어난다는걸 알았습니다.
    예를 들어, WebSecurityConfig 부분에서 customfilter을 빈등록하는 부분입니다.
    @Bean
    public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
    System.out.println("[1번째콘솔][시큐리티 config] customAuthenticationFilter() 진입 ");
    CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManager());
    System.out.println("[2번째콘솔][시큐리티 config] 여기까지 내려오긴하나 ?");
    customAuthenticationFilter.setFilterProcessesUrl("/user/login");
    customAuthenticationFilter.setAuthenticationSuccessHandler(customLoginSuccessHandler());
    customAuthenticationFilter.afterPropertiesSet();
    return customAuthenticationFilter;
    }
    [1번째콘솔]을 지나, managebuilder부분의 super부분만 한번 스치고, 그 이후 provider나 내부의 다른곳에서는 찍히지않았습니다.
    이후 [2번째콘솔]이 찍히고. 다른 bean인 succersshandler에서 첫번째부분만 찍히고,
    다른 configurer(접근제한,세션설정을 하는곳등)으로 간후 콘솔이 종료됩니다.
    >>> 사실 실행하게되면, setFilterProcessesUrl("/user/login");
    이 부분이 /user/login 로 uri요청이 온다면 무조건 필터로 잡아내서 스프링시큐리티가 실행되는게 아닐까 생각했습니다만 아무런 변화가 없었습니다.... 제가 이해한게 틀렸나싶어 그냥 role 제한을 주었어도 아무 제한없이 모든 페이지가 다 들어가진걸 보니, 아예 실행이 되지않는다 생각하게 된것이었습니다 ㅠㅠ..!
    여하튼 log가 필요하다 생각은 했는데, 이것부터 구현하고 해야지 라는 생각에.. 늦췄더니 ㅠㅠ log관련해서는 제가 공부해서 얼른 설정을 해봐야겠습니다 :) 감사합니다


    ----------------------
    [ 기타 설정에 관한 조언 부분 ]
    1. Servlet Context에 security Filter 등록
    ex)
    context.addFilter("securityFilter", new DelegatingFilterProxy("springSecurityFilterChain")) .addMappingForUrlPatterns(null, false, "/*");


    사실 이부분이 가장 중요한것같습니다. 원래 xml 설정으로 했을때는 >>web.xml << 에 작성하였습니다.
    필터부분을 작성하고,
    <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>

    이후, 같은 web.xml의 맨 위의 context-param에 param value로 >>security-context.xml<<을 넣은후, 파일에 세부설정을 위치시켰습니다.
    사실 그냥 어디에 뭐가 있다 말만해도 아실것같지만, 답변까지 해주시는데 ㅠ 적혀있는게 혹시라도 스크롤바 안움직여도 되고 편하시지않을까해서 적습니다..!

    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
    /WEB-INF/spring/root-context.xml
    /WEB-INF/spring/security/security-context.xml
    </param-value>
    </context-param>


    *** security-context.xml 에는 현재 기본적으로 알려진 설정들이 들어가져있는 상태였습니다.

    <security:http auto-config="true" use-expressions="true">
    <security:headers>
    <security:frame-options disabled="true"/>
    </security:headers>
    를 비롯하여,
    <security:intercept-url pattern="/api/**" ..... > 등의 인터셉트url패턴을 작성하는 부분과,
    폼 기반의 로그인 설정(각종 로그인 페이지/성공url/실패url등),
    로그아웃 설정, csrf 설정,
    중복로그인 방지를 위한 세션설정(최대허용가능 중복세션수1개 등등..)
    그 외 <security:session-management> 로 시작되는것과,
    <security:authentication-manager>
    <security:authentication-provider>
    <security:user-service>
    요렇게 세개를 설정하는것도 있었네요.

    사실 맨 위에서 말했듯이 .xml설정과 @bean설정이 혼용이 되기도하고
    이미 개발자님을 따라 적은 파일에 설정한부분들이 많아서 (커스텀필터, 프로바이더, 매니저, url설정, csrf설정, 세션x,등등..) 이 파일이 없어도 될것같아 아예 등록하질 않고 삭제했었습니다. 즉, 말씀해주신 시큐리티 필터체인을 등록하지않았고, 그에따라 security-context.xml도 삭제하여 없는 상태였습니다.
    @EnableWebSecurity(debug = true)
    이거는 @Configuration과 같이 붙어있던 상태여서 이 두개만있으면 설정이 되는줄알고있었습니다..^^...!

    말씀해주신 Servlet Context? 라는 클래스에 @ 형태로 쓰고싶습니다만 다른 xml도 있다보니까 ..
    현재 있는 xml은 맨 하단의 web.xml과, // spring>appservlet>servlet-context.xml, // spring > root-context.xml 이렇게 3개가 있습니다.
    그래서 이게 가장 크지않나싶어서 아까 위에적은 xml설정방식대로 web.xml에 추가를 해보긴 했습니다만,


    INFO : org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization started
    INFO : org.springframework.web.context.support.XmlWebApplicationContext - Refreshing Root WebApplicationContext: startup date [Tue Dec 29 02:18:32 KST 2020]; root of context hierarchy
    INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource [/WEB-INF/spring/root-context.xml]
    INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource [/WEB-INF/spring/security/security-context.xml]
    INFO : org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 622 ms
    12월 29, 2020 2:18:32 오전 org.apache.catalina.core.StandardContext filterStart
    심각: 필터 [springSecurityFilterChain]을(를) 시작하는 중 오류 발생
    org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' available
    ------------------ +++ 이 밑에 자세한 로그를 찍는 부분 일부
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:682)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1218)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:284)

    이란 에러가 뜹니다... 흑흑..^_^.. spring filter을 못읽는 현상이... 왜인지 이것만 해결하면 되지않으려나하는 부푼기대를 안고했습니다만 아직은 오류가 절 반겨주네요..!! 또르륵...
    Closing Root WebApplicationContext: startup date [Tue Dec 29 02:18:32 KST 2020]; root of context hierarchy
    아무튼 이렇게 뜨며, 결국 404에러로 끝이 났습니다... ㅠ 길게 답변주셔서 한번에 해결해보고싶었는데 안되서 죄송스럽고 또 감사합니다 ㅠㅠ...
    스프링필터체인을 못읽는 방법에 대해서 검색을 해보긴할건데.. 그냥 평범하게 등록한게 아니라, 여러개를 섞어놓다보니 (빈설정도 섞이고.. xml이 없는것도 아닌...아직 설정초보인 저에게는 함부로 이해가 안되는 영역인듯합니다..:( ) 먼저 개발자님께 고견을 묻고싶습니다.. 혹시 이런식에 대해서는 아시는게 있을지,
    아니라면 뭔가 위의 과정속에서 역시 설정충돌이 생길만한 일이 있을지,
    스프링시큐리티필터체인을 설정할때, 개발자님이 얘기해주신 context.addFilter("securityFilter", new DelegatingFilterProxy("springSecurityFilterChain")) .addMappingForUrlPatterns(null, false, "/*"); 대로 설정을 하려면, 정확히 어느 파일에 설정을 하는게 좋을까요..? 이것도 자바빈으로 xml형식을 구현한다음에 적어야하는건가요 ?? webclass.java...이런식으로요..??
    만약 그렇게 되면 원래 web.xml에 있던것도 다 재설정해줘야할지...

    정말 다시한번더 감사드리고, 예상치못하게 더 ; 긴글로 질문드리는점 죄송합니다...!
    곧 12월의 마지막인데 :) 좋은 날들만 가득하시고 일년 잘 마무리하시길 바랍니다!
    혹 이 댓글이 밤중에 알람이 가지는 않을까하여 한번에 길게 작성+비밀글로 합니다만..
    나중에 다시 공개글로 바꿔 모두가 볼수있게 하겠습니다! :D

    그리고 원래는 질문이 아니라 ㅠ 그냥 잘 읽고간다고 적으면서 감사하다고 적고싶었습니다..ㅎㅎ..!
    시큐리티 글을 보면서, 처음보는 개념들이 많다보니 이것저것 찾아보게 되었거든요. 그중 Enum 과 Optional class부분이 가장 흥미로웠고, 정리가 잘 되어있어서 자바를 좀더 깊게 생각해볼수있었습니다 :)
    하도 코린이라.. 잘 취업을 할수있을지는 모르겠지만... 취업을 하고난다음에는 모든 글 다 살펴보면서 깊게 공부해야겠다는 생각을 했거든요 :) 지금은 너무 정형화되고 날림으로 배우는것같아서...계속 아쉬웠거든요 ㅠㅠ 나중엔 맥북이 있으니 나중엔 ide도 바꾸고 boot랑 jpa도 공부해야겠다 생각했고..
    DI를 Enum으로 관리한다던가, AOP부분도 더 잘써보고싶고 깊게 파고싶다는 생각을 하고있었는데, 그 생각에 더 불을 키게 만드는 내용들이었습니다 ㅎㅎ!!!
    정말 넘 잘 작성해주셔서 감사드리고, 앞으로도 종종 찾아와 잘 공부하고 감탄하고 가겠습니다 :D !!ㅎㅎ

    PS. 그러고보니 Optional로 설정되어있는 메소드를 호출할때, isPresent()를 하게되면 null값 empty을 체크한다고 생각했는데, 뭔가 제대로 잡히지않더라구요.. 아이디가 있는데도 계속 false가 나온다던가.. id가 조회되는데도 null로는 안나오지만 empty로 나오는...^ㅅ^ㅠㅠ?.....ㅠ 제가 mybatis에서 result값을 java.lang.Optional로 설정했는데 그게 혹 잘못된걸까요..혹 Optional은 어떤식으로 매퍼에서 쓰는지도 궁금합니다..! optional을 본김에 제대로 활용하고싶은데, 뭔가 어중간하게 null체크를 하고있어서 제대로 못쓰는 느낌입니다..ㅋㅋㅋ
    아무튼 감사드립니다 :) 제가 더 감사함을 표할수가 없을만큼 감사합니다 !! 코로나 조심하시고 좋은하루되세요 !


    추가합니다 ++
    오늘 다시 블로그글을 보고있는데 바로 밑 게시글에 @bean 설정에 대해서 적어주신게 있더라구요,
    덕분에 한번더 제대로 이해하게 되었습니다.
    여기서 @filterRegisterBean은 boot에만 있는 어노테이션인지 뜨질않아서,
    headerFilter 설정을 위해서 webMvcConfig부분이 개발자님과 조금 틀리게 작성이 되어있습니다. (webSecurityConfig와는 틀립니다)

    @Configuration
    public class WebMvcConfig extends AbstractAnnotationConfigDispatcherServletInitializer implements WebMvcConfigurer{
    부분으로, AbstractAnnotationConfigDispatcherServletInitializer 이걸 상속받아서 사용했습니다.
    여기서 인터셉트오버라이드를 하고,
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    System.out.println("[인터셉터 추가하는 부분 /webmvcconfig]");
    registry.addInterceptor(jwtTokenInterceptor())
    .addPathPatterns("/user/main/*")
    .addPathPatterns("/admin/*");
    }

    다른 블로그글을 뒤지다가 이런식으로 getServletFilters로 불러오면 된다길래 이쪽에서 불러왔습니다.
    @Override
    protected Filter[] getServletFilters() {
    //추가할 필터리스트 작성
    return new Filter[] {new HeaderFilter()}; //, new anotherFilter, ...추가
    }

    이후 이런것들이 자동 오버라이딩 되어있어서.. 이런식으로 3개정도를작성하였습니다.
    ((headerfilter는 Filter를 상속받아서 dofilter등 오버라이딩 되는걸 작성했구요 .))
    //ContextLoadgerListener가 생성한 app컨텍스트 설정. 루트설정은 아래에 정의되어있음.
    @Override
    protected Class<?>[] getRootConfigClasses() {
    return new Class[0]; //[]({RootConfig.class}
    }


    이 헤더필터말고는, 개발자님과 다르게 작성된건 아까 위에서 적은 spring filter chain및 설정파일이 전부 @설정으로 되어있는게 아니라 xml로 되어있다 이것밖에 없는것같습니다 :)
    2020.12.29 02:47 신고
  • 망나니개발자 이번에도 하나씩 답변드리도록 하겠습니다!

    1. JavaConfig와 Xml Config
    최근에는 당연히 @Bean이나 @Component 등을 이용한 JavaConfig를 선호합니다. 하지만 현존하는 프로젝트가 이미 xml로 작성이 되어있다면 일관성있게 xml로 작성하는 것이 좋아 보입니다. 물론 JavaConfig와 Xml Config를 혼용하여 사용해도 시스템에는 문제가 전혀 없습니다.

    2. 로그인 요청 코드
    현재 ajax로 작성이 되어 있는 것에는 문제가 없어 보이며, 적어주신 아래의 커스텀 필더 부분 역시 문제가 없어 보입니다. 그렇기 때문에 현재는 Filter의 등록 쪽에 문제가 있는 것 같습니다.

    3. 현재 문제 상황 분석
    @Bean으로 빈 등록을 했을 때 CustomFilter의 생성자는 처음 빈을 등록하기 위해 호출된 것으로 보입니다. 반면에 xml로 하셨을때는 에러 로그가 NoSuchBeanDefinition이므로 현재 상황은 Filter가 Spring Bean으로 등록이 되지 않은 경우입니다. 그렇기 때문에 현재 상황에서는 web.xml에 등록하는 것이 좋아보입니다. 등록하는 방법은 내용이 많기 때문에 자세히 나와있는 다음과 같은 공식문서를 참고하시는게 좋을 것 같습니다.
    https://docs.spring.io/spring-security/site/docs/4.2.19.RELEASE/guides/html5/helloworld-xml.html

    4. Optional과 MyBatis
    음... Optional을 활용하면 Null처리를 확실히 용이하게 할 수 있습니다. 하지만 Wrapper를 사용하는 것이기 때문에 당연히 그에 대한 비용이 존재하기 마련입니다. Optional을 사용하지 않고 처리할 수 있다면 굳이 사용하지 않는 편이 좋습니다! ex) 초기값으로 반환하기 or 빈 List로 반환하기 등
    추가로 오히려 자기계발을 하신다면 Optional보다는 Stream이나 Lambda 등과 같은 Java8 의 함수형 프로그래밍을 위한 API를 사용하시는 편이 훨씬 좋아 보입니다. 해당 부분도 포스팅을 하려고 정리해뒀는데 시간이 없어서 올리지 못했네요ㅜㅜ 내년 1월~2월쯤에 한번 올리도록 하겠습니다:)

    5. 추가로 이후에 주도적으로 다른 프로젝트를 하거나 개인 프로젝트 등 기술 스택에 선택지가 있다면 Boot를 사용하시길 권장드립니다!
    2020.12.29 22:07 신고
  • 게으른개발자 저도 Spring Security 사용해서 프로젝트를 수행하기도 하고 다르게 구성해서 진행할 때도 있지만 매번 공통 세팅할 때마다 헤깔리고 까먹게 되네요. 개념적인 부분은 잘 이해하고 정리를 잘 해놔야 나중에 찾아볼텐데 게을러서 매번 다른 분 정리해놓을걸 찾아보게 되네요. 개인적으로 SSO랑 Spring Security랑 연동을 좀 더 효율적으로 하고 싶은데 환경적인 요인 등으로 그렇게 못하는 경우가 아쉽네요. 혹시 SSO랑 연동하고 정리한 자료가 있으신지...ㅎㅎ;; 좋은 정보 잘 보고 갑니다. 2021.01.19 16:51
  • 망나니개발자 제가 SSO 연동은 진행해보긴 하였는데 관련 정리한 자료를 포스팅하지는 않았습니다ㅜㅜ 1월이나 2월쯤에 시간이 나면 SSO 관련 부분도 포스팅해보도록 하겠습니다:) 2021.01.19 23:23 신고
  • baek Spring Security관련하여 공부소스가 많이부족한것같던데 어떻게 공부하셨나요??? 2021.01.19 21:47
  • 망나니개발자 가장 중요한 것은 역시 공식 문서인 것 같습니다. 그 외에 코드를 작성할 경우에는 best practice를 찾거나 baeldung에도 좋은 예시가 많이 있어서 참고하시면 좋을 것 같습니다ㅎㅎ 2021.01.19 23:24 신고
  • Kimchi-dev 설명이 너무 잘되어있네요. 감사합니다.
    책내셔도 되겠어요.
    2021.02.03 09:59 신고
  • 망나니개발자 좋게 봐주셔서 감사합니다:) 2021.02.03 10:16 신고
  • baek 글 잘보면서 공부하고 있습니다. 정말 감사합니다!!
    Authentication Provider같은 경우 Manager로 부터 위임 받아 인증을 직접 처리한다고 들었습니다. 그런데 왜 Provider'들'인지 그리고 왜 여러개가 있어야 하는지 궁금하네요!!? 쓰레드나 세션의 부분을 담고 있나요??
    2021.03.02 18:23
  • 망나니개발자 Authentication Provider는 인증 로직을 구현하는 클래스로 쓰레드나 세션의 부분과는 무관합니다! 그렇기 때문에 Authentication Provider가 여러 개 필요한 경우는 여러 인증 로직이 필요한 경우입니다. 첫 번째 Provider에서 인증에 실패하면 다음 Provider의 인증 로직을 진행합니다. 2021.03.02 18:59 신고
  • 망나니개발자 정확한 사례가 될 지는 모르겠지만 이해를 돕도록 적어보도록 하겠습니다. 상황이 부적합해 보여도 이해해주세요:)
    예를 들어 A 사에서 SHA256 해시 알고리즘으로 비밀번호 암호화를 진행했습니다. 그런데 레인보우 테이블 공격에 의해 해당 방법이 취약함을 깨달았고, 특정 시점 이후에 가입된 사용자들에는 Bcrypto 방식으로 암호화를 진행했다고 가정하겠습니다.
    이러한 경우에 DB 테이블에는 2가지 암호화 방식으로 저장된 비밀번호가 섞여있고, 서로 다른 로직으로 처리를 해주어야 합니다. 그래서 SHA256 기반의 인증 로직을 갖는 Authentication Provider와 Bcrypto 기반의 인증 로직을 갖는 Authentication Provider를 AuthenticationManager에 등록하여 위의 상황을 해결할 수 있을 것입니다.
    2021.03.02 19:02 신고
  • baek 친절한 답변 정말 감사합니다!! 덕분에 잘배워갑니다. 인프런에 강의 내셔두 되겠어요!! 2021.03.02 19:21
  • 망나니개발자 아이고 과찬이십니다ㅎㅎ 좋게 봐주셔서 감사합니다:)
    도움이 되었다니 뿌듯하네요ㅎㅎ
    2021.03.02 19:29 신고
  • 익명 비밀댓글입니다 2021.11.11 10:10
  • 망나니개발자 안녕하세요, 이 내용의 다음 포스팅인 SpringSecurity 처리 과정 및 구현 예제(2/2) 을 참고하시면 될 것 같습니다. 감사합니다:)
    2021.11.11 10:26 신고
  • 익명 비밀댓글입니다 2022.01.10 17:05
  • 망나니개발자 넵넵 괜찮습니다ㅎㅎ 출처만 꼭 남겨주세요! 감사합니다:) 2022.01.10 18:58 신고
반응형
공지사항
Total
1,971,129
Today
187
Yesterday
1,908
TAG more
«   2022/01   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
글 보관함