728x90

Spring Security 환경설정

시큐리티 적용시 기본적으로 로그인화면이 나온다. 기본 Username/Password =? user/시스템 로그에 출력됨

Spring Security 의존성 추가

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

 • 스프링 시큐리티를 사용하기 위한 스타터

• 타임리프에서 스프링 시큐리티를 사용하기 위한 의존성

• 스프링 시큐리티를 테스트 하기 위한 의존성

 

spring:
  security:
    user:
      name: user
      password: 1111

 


SecurityConfig

Spring Security의 환경설정을 구성하기 위한 클래스다!

 

HttpSecurity로 대부분 구현한다고 생각하면 된다. 참고로 현재는 WebSecurityConfigurerAdapter는 사용을 안한다.

  • 먼저 config 패키지에 SecurityConfig라는 시큐리티 설정 파일을 만들어 주고 필요한 @bean들을 추가해 사용할 수 있다. --> 사진을 찾다보니 현재 WebSecurityConfigurerAdapter는 시큐리티3부터 사용을 안하지만 HttpSecurity에 대한 설명이 나와있어서 사용했다. 현재 WebSecurityConfigurerAdapter 를 상속하지 않는다!
  • 이제는 @Bean 으로 SpringSecurityFilterChain 을 구현한다.
  • config 클래스에@EnableWebSecurity 어노테이션을 달아서 시큐리티 설정을 해준다.
@Configuration
@EnableWebSecurity
public class SecurityConfig{

	// 패스워드 암호화 관련 메소드
  @Bean 
  public PasswordEncoder passwordEncoder(){
      return new BCryptPasswordEncoder();
  }

	// 특정 HTTP 요청에 대한 웹 기반 보안 구성
	// 시큐리티 대부분의 설정을 담당하는 메소드
	@Bean 
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
			http	
				.csrf(AbstractHttpConfigurer::disable)
				.httpBasic(AbstractHttpConfigurer::disable)
				.authorizeHttpRequests((authorize) -> authorize
						.requestMatchers("/signup", "/", "/login").permitAll()
						.anyRequest().authenticated()
				)
				// Form 로그인을 활용하는경우 (JWT에는 필요없음)
				.formLogin(form -> form
	            		.loginPage("/loginform") 
	            		.loginProcessingUrl("/login") 
              			.defaultSuccessUrl("/")
              			.permitAll()
        			)
				.logout((logout) -> logout
				.logoutUrl("/logout")
             			.logoutSuccessUrl("/")
				.invalidateHttpSession(true)
				)
				.sessionManagement(sessionManagement -> sessionManagement
             			.maximumSessions(1) 
             			.maxSessionsPreventsLogin(true)
             			.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        			)
			);
		return http.build();
	}

	// 이외에도 등록해서 사용하면 된다..
}

 

코드설명

  • filterChain() : 특정 Http 요청에 대해 웹 기반 보안 구성. 인증/인가 및 로그아웃을 설정한다.
    • .csrf(Cross site Request forgery) : 공격자가 인증된 브라우저에 저장된 쿠키의 세션 정보를 활용하여 웹 서버에 사용자가 의도하지 않은 요청을 전달하는 것. 즉, 정상적인 사용자가 의도치 않은 위조요청을 보내는 것을 의미한다.
      • REST API를 이용한 개발을 진행 할 예정일 때, Rest Api 환경에서는 Session 기반 인증과 다르기 때문에 서버에 인증 정보를 보관하지 않고, 권한 요청시 필요한 인증정보(OAuth2, Jwt토큰 등)요청을 포함하기 때문에 굳이 불필요한 csrf 보안을 활성화할 필요가 없다.
      • 따라서 csrf는 disable 처리
    • .HttpBasic()
      • HttpBasic() : Http basic Auth 기반으로 로그인 인증창이 뜬다.
    • .authorizeHttpRequests() : 인증, 인가가 필요한 URL 지정
      • anyRequest() : requestMatchers에서 지정된 URL 외의 요청에 대한 설정
      • authenticated() : 해당 URL에 진입하기 위해서는 인증이 필요함
      • requestMatchers("Url").permitAll() : requestMatchers에서 지정된 url은 인증, 인가 없이도 접근 허용
      • Url에 /**/ 와 같이 ** 사용 : ** 위치에 어떤 값이 들어와도 적용 (와일드 카드)
      • hasAuthority() : 해당 URL에 진입하기 위해서 Authorization(인가, 예를 들면 ADMIN만 진입 가능)이 필요함
        • .hasAuthority(UserRole.ADMIN.name()) 와 같이 사용 가능
    • formLogin() : Form Login 방식 적용
      • loginPage() : 로그인 페이지 URL
      • defaultSuccessURL() : 로그인 성공시 이동할 URL
      • failureURL() : 로그인 실패시 이동할 URL
    • logout() : 로그아웃에 대한 정보
      • invalidateHttpSession() : 로그아웃 이후 전체 세션 삭제 여부
    • sessionManagement() : 세션 생성 및 사용여부에 대한 정책 설정
      • SessionCreationPolicy() : 정책을 설정
      • SessionCreationPolicy.Stateless : 4가지 정책 중 하나로, 스프링 시큐리티가 생성하지 않고 존재해도 사용하지 않는다. (JWT와 같이 세션을 사용하지 않는 경우에 사용)

BCryptPasswordEncoder

BCrype 인코딩을 통하여 비밀번호에 대한 암호화를 수행한다.

password를 암호화해줌

  • Spring Security에서 비밀번호를 안전하게 저장할 수 있도록 비밀번호의 단방향 암호화를 지원한다.
    -> PasswordEncoder 인터페이스와 구현체들

  • encode() : 비밀번호를 암호화(단방향)
  • matches() : 암호화된 비밀번호와 암호화되지 않은 비밀번호가 일치하는지 비교
  • upgradeEncoding() : 인코딩된 암호화를 다시 한번 인코딩 할 때 사용 (true일 경우 다시 인코딩, default=false)

 

PasswordEncoder가 제공하는 구현 클래스

  • StandardPasswordEncoder : SHA-256을 이용해 암호를 해시한다. (강도가 약한 해싱 알고리즘이기 때문에 지금은 많이 사용되지 않는다.)
  • BCryptPasswordEncoder : bcrypt 강력 해싱 함수로 암호를 인코딩
  • NoOpPasswordEncoder : 암호를 인코딩하지 않고 일반 텍스트로 유지(테스트 용도로만 사용.)
  • SCryptPasswordEncoder : scrypt 해싱 함수로 암호를 인코딩한다.
@Bean // 패스워드 암호화 관련 메소드
public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}
  • 현재 사용되는 알고리즘에서 취약성이 발견되어 다른 인코딩 알고리즘으로 변경하고자 할 때 대응하기 좋은 방법은 DelegatingPasswordEncoder을 사용하는 것
@Bean // DelegatingPasswordEncoder: 여러 인코딩 알고리즘을 사용할 수 있게 해주는 기능
	public static PasswordEncoder passwordEncoder() {
		return PasswordEncoderFactories.createDelegatingPasswordEncoder();
	}

 

 


기타 참고용

 

Configure 작성 문법 바뀐 부분

스프링 3.0 이상의 버전부터는 스프링 시큐리티 버전도 바뀌어서 기존의 Configuration과는 다르게 작성해야 한다. WebSecurity, HttpSecurity 모두 큰 변화를 맞이 했는데, 그중 하나가 lambdas 형식의 작성법이다.

  • 람다식을 파라미터로 전달하여 아래와 같이 사용한다.
.formLogin(formLogin -> formLogin
						.loginPage("/login")
						.defaultSuccessUrl("/home"))

 

 

 

HttpSecurity

스프링시큐리티의 각종 설정은 HttpSecurity로 대부분 하게 된다!

Spring Boot 3.1(Spring 6.1) Security Config: 'csrf()' is deprecated and marked for removal

  • 스프링 부트 버전이 올라가면서 작성방식에 차이가 생김

Spring boot 3.0.6, Spring security 6, jwt적용 및 인증, 예외 처리

  • 버전이 올라가면서 동작방식이 달라짐

 

 

HttpSecurity - 리소스(URL) 접근 권한 설정

특정 리소스의 접근 허용 또는 특정 권한을 가진 사용자만 접근을 가능하게 할 수 있다.

http
		
       .authorizeHttpRequests(authorizeRequest -> authorizeRequest
		
        // 해당 경로는 모든 권한을 허용한다.
    	.requestMatchers(HttpMethod.GET, "/login**", "/web-resources/**", "/actuator/**").permitAll()

	// 해당 경로는 어드민 권한이 있어야한다.
    	.requestMatchers(HttpMethod.GET, "/admin/**").hasAnyRole("ADMIN")

	// 해당 경로는 유저 권한이 있어야 한다.
    	.requestMatchers(HttpMethod.GET, "/order/**").hasAnyRole("USER")

	// 나머지는 모두 권한이 필요하다.
    	.anyRequest().authenticated()
  • requestMatchers
    • 특정 리소스에 대해서 권한을 설정한다.
  • permitAll
    • 리소스의 접근을 인증절차 없이 허용한다.
  • authenticated
    • 리소스의 접근을 인증절차를 통해 허용한다.
  • hasAnyRole
    • 해당 권한을 가진 사용자만 접근을 허용한다.
  • anyRequest
    • 모든 리소스를 의미하며, anyMatcher로 설정하지 않은 리소스를 말한다. 

 

HttpSecurity - 로그인처리 설정

로그인 FORM 페이지를 이용하여 로그인하는 방식을 사용하려고 할때 여러가지 설정을 할 수 있다.

// Form 로그인을 활용하는경우 (JWT에는 필요없음)
// .formLogin(Customizer.withDefaults()); // Security가 제공하는 로그인 방식 사용
.formLogin(formLogin -> formLogin
        .loginPage("/login")
        .loginProcessingUrl("/loginProc")
        .usernameParameter("userId")
        .passwordParameter("userPw")
        .permitAll())

 


JwtAuthenticationFilter 사용

HttpSecurity - 커스텀 필드 등록 ⭐

커스텀 필터를 생성해서 등록할 수 있다!

.addFilterBefore(jwtAuthenticationFilter, 
	UsernamePasswordAuthenticationFilter.class)
    // UsernamePasswordAuthenticationFilter가 기존 시큐리티 세션 방식의 로그인 필터이기 때문에
    // UsernamePasswordAuthenticationFilter 앞에 커스텀한 필터 체인을 넣어준다.
  • addFilterBefore
    • 지정된 필터 앞에 커스텀 필터를 추가한다.
  • addFilterAfter
    • 지정된 필터 뒤에 커스텀 필터를 추가한다.
  • addFilterAt
    • 지정된 필터의 순서에 커스텀 필터가 추가된다.

 

 

JwtAuthenticationFilter

  • JwtAuthenticationFilter.java
    • jwt 방식으로 로그인을 진행할 것이기 때문에 커스텀한 필터이다.
@Order(0)
@RequiredArgsConstructor
@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final TokenProvider tokenProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String token = parseBearerToken(request);

        // 토큰값이 유요하다면 검증을 시작한다.
        if (token != null && tokenProvider.validToken(token)) {
            // 토큰 검증
            Authentication authentication = tokenProvider.getAuthentication(token);

            // SecurityContextHolder => 인증정보를 담는다.
            SecurityContextHolder.getContext().setAuthentication(authentication);
            log.info("Security Context에 {} 인증 정보를 저장했다", authentication.getPrincipal());
        } else {
            log.info("유효한 JWT 토큰이 없습니다, uri: {}", request.getRequestURI());
        }

        filterChain.doFilter(request, response);
    }

    /**
     * Authorization Bearer 제거(공백포함 7글자)
     * @param request 요청 request
     * @return token (없는경우 null)
     */
    private String parseBearerToken(HttpServletRequest request) {
        return Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION))
                .filter(token -> token.length() >= 7 && token.substring(0, 7).equalsIgnoreCase("Bearer "))
                .map(token -> token.substring(7))
                .orElse(null);
    }
}

 


JwtAuthenticationFilter에 대해서는 이전 블로그에 자세하게 적어놓았다.
Spring Security + JWT (RefreshToken, AccessToken)를 사용한 로그인, 로그아웃 구현 - 4편 :: 미정 (tistory.com)

 

Spring Security + JWT (RefreshToken, AccessToken)를 사용한 로그인, 로그아웃 구현 - 4편

개요* 1~3편 정리1편에서 기본적인 세팅은 끝이 났다.2편에서는 SecurityConfig에 대해서 보안 설정을 했다.3편에서는 RefreshToken과 BlackListToken에 대해서 엔티티와 레포지토리, 서비스를 작성했다. 4편

eesko.tistory.com

 

jwt에 대해서 알고싶다면

[멋쟁이사자처럼 백엔드 TIL] Spring Security : JWT(JSON Web Token) :: 미정 (tistory.com)

728x90

+ Recent posts