- 웹 서비스가 로드될 때 Spring Container에 의해 관리가 되는 클래스이며 사용자에 대한 ‘인증’과 ‘인가’에 대한 구성을 Bean 메서드로 주입을 한다.
package com.adjh.multiflexapi.config;
import com.adjh.multiflexapi.config.filter.CustomAuthenticationFilter;
import com.adjh.multiflexapi.config.handler.CustomAuthFailureHandler;
import com.adjh.multiflexapi.config.handler.CustomAuthSuccessHandler;
import com.adjh.multiflexapi.config.handler.CustomAuthenticationProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* Spring Security 환경 설정을 구성하기 위한 클래스입니다.
* 웹 서비스가 로드 될때 Spring Container 의해 관리가 되는 클래스이며 사용자에 대한 ‘인증’과 ‘인가’에 대한 구성을 Bean 메서드로 주입을 한다.
*/@Slf4j@Configuration@EnableWebSecuritypublicclassWebSecurityConfig {
/**
* 1. 정적 자원(Resource)에 대해서 인증된 사용자가 정적 자원의 접근에 대해 ‘인가’에 대한 설정을 담당하는 메서드이다.
*
* @return WebSecurityCustomizer
*/@Beanpublic WebSecurityCustomizer webSecurityCustomizer() {
// 정적 자원에 대해서 Security를 적용하지 않음으로 설정return (web) -> web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
/**
* 2. HTTP에 대해서 ‘인증’과 ‘인가’를 담당하는 메서드이며 필터를 통해 인증 방식과 인증 절차에 대해서 등록하며 설정을 담당하는 메서드이다.
*
* @param http HttpSecurity
* @return SecurityFilterChain
* @throws Exception Exception
*/@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http)throws Exception {
// [STEP1] 서버에 인증정보를 저장하지 않기에 csrf를 사용하지 않는다.
http.csrf().disable();
// [STEP2] form 기반의 로그인에 대해 비 활성화하며 커스텀으로 구성한 필터를 사용한다.
http.formLogin().disable();
// [STEP3] 토큰을 활용하는 경우 모든 요청에 대해 '인가'에 대해서 사용.
http.authorizeHttpRequests((authz) -> authz.anyRequest().permitAll());
// [STEP4] Spring Security Custom Filter Load - Form '인증'에 대해서 사용
http.addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// [STEP5] Session 기반의 인증기반을 사용하지 않고 추후 JWT를 이용하여서 인증 예정
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// [STEP6] Spring Security JWT Filter Load// http.addFilterBefore(jwtAuthorizationFilter(), BasicAuthenticationFilter.class);// [STEP7] 최종 구성한 값을 사용함.return http.build();
}
/**
* 3. authenticate 의 인증 메서드를 제공하는 매니져로'Provider'의 인터페이스를 의미합니다.
* - 과정: CustomAuthenticationFilter → AuthenticationManager(interface) → CustomAuthenticationProvider(implements)
*
* @return AuthenticationManager
*/@Beanpublic AuthenticationManager authenticationManager() {
returnnewProviderManager(customAuthenticationProvider());
}
/**
* 4. '인증' 제공자로 사용자의 이름과 비밀번호가 요구됩니다.
* - 과정: CustomAuthenticationFilter → AuthenticationManager(interface) → CustomAuthenticationProvider(implements)
*
* @return CustomAuthenticationProvider
*/@Beanpublic CustomAuthenticationProvider customAuthenticationProvider() {
returnnewCustomAuthenticationProvider(bCryptPasswordEncoder());
}
/**
* 5. 비밀번호를 암호화하기 위한 BCrypt 인코딩을 통하여 비밀번호에 대한 암호화를 수행합니다.
*
* @return BCryptPasswordEncoder
*/public BCryptPasswordEncoder bCryptPasswordEncoder() {
returnnewBCryptPasswordEncoder();
}
/**
* 6. 커스텀을 수행한 '인증' 필터로 접근 URL, 데이터 전달방식(form) 등 인증 과정 및 인증 후 처리에 대한 설정을 구성하는 메서드입니다.
*
* @return CustomAuthenticationFilter
*/@Beanpublic CustomAuthenticationFilter customAuthenticationFilter() {
CustomAuthenticationFiltercustomAuthenticationFilter=newCustomAuthenticationFilter(authenticationManager());
customAuthenticationFilter.setFilterProcessesUrl("/api/v1/user/login"); // 접근 URL
customAuthenticationFilter.setAuthenticationSuccessHandler(customLoginSuccessHandler()); // '인증' 성공 시 해당 핸들러로 처리를 전가한다.
customAuthenticationFilter.setAuthenticationFailureHandler(customLoginFailureHandler()); // '인증' 실패 시 해당 핸들러로 처리를 전가한다.
customAuthenticationFilter.afterPropertiesSet();
return customAuthenticationFilter;
}
/**
* 7. Spring Security 기반의 사용자의 정보가 맞을 경우 수행이 되며 결과값을 리턴해주는 Handler
*
* @return CustomLoginSuccessHandler
*/@Beanpublic CustomAuthSuccessHandler customLoginSuccessHandler() {
returnnewCustomAuthSuccessHandler();
}
/**
* 8. Spring Security 기반의 사용자의 정보가 맞지 않을 경우 수행이 되며 결과값을 리턴해주는 Handler
*
* @return CustomAuthFailureHandler
*/@Beanpublic CustomAuthFailureHandler customLoginFailureHandler() {
returnnewCustomAuthFailureHandler();
}
/**
* 9. JWT 토큰을 통하여서 사용자를 인증합니다.
*
* @return JwtAuthorizationFilter
*/// @Bean// public JwtAuthorizationFilter jwtAuthorizationFilter() {// return new JwtAuthorizationFilter();// }
}
- 인증 성공 시 CustomAuthSuccessHandler를 호출하고, 실패 시 CustomAuthFailureHandler를 호출합니다. - 커스텀을 수행한 '인증' 필터로 접근 URL, 데이터 전달 방식(form) 등 인증 과정 및 인증 후 처리에 대한 설정을 구성하는 메서드입니다. - 최종 인증이 완료되면 사용자 아이디와 비밀번호 기반으로 토큰을 발급합니다.
package com.adjh.multiflexapi.config.filter;
import com.adjh.multiflexapi.model.UserDto;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 아이디와 비밀번호 기반의 데이터를 Form 데이터로 전송을 받아 '인증'을 담당하는 필터입니다.
*/@Slf4jpublicclassCustomAuthenticationFilterextendsUsernamePasswordAuthenticationFilter {
publicCustomAuthenticationFilter(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
/**
* 지정된 URL로 form 전송을 하였을 경우 파라미터 정보를 가져온다.
*
* @param request from which to extract parameters and perform the authentication
* @param response the response, which may be needed if the implementation has to do a
* redirect as part of a multi-stage authentication process (such as OpenID).
* @return Authentication {}
* @throws AuthenticationException {}
*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {
UsernamePasswordAuthenticationToken authRequest;
try {
authRequest = getAuthRequest(request);
setDetails(request, authRequest);
} catch (Exception e) {
thrownewRuntimeException(e);
}
returnthis.getAuthenticationManager().authenticate(authRequest);
}
/**
* Request로 받은 ID와 패스워드 기반으로 토큰을 발급한다.
*
* @param request HttpServletRequest
* @return UsernamePasswordAuthenticationToken
* @throws Exception e
*/private UsernamePasswordAuthenticationToken getAuthRequest(HttpServletRequest request)throws Exception {
try {
ObjectMapperobjectMapper=newObjectMapper();
objectMapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true);
UserDtouser= objectMapper.readValue(request.getInputStream(), UserDto.class);
log.debug("1.CustomAuthenticationFilter :: userId:" + user.getUserId() + " userPw:" + user.getUserPw());
// ID와 패스워드를 기반으로 토큰 발급returnnewUsernamePasswordAuthenticationToken(user.getUserId(), user.getUserPw());
} catch (UsernameNotFoundException ae) {
thrownewUsernameNotFoundException(ae.getMessage());
} catch (Exception e) {
thrownewException(e.getMessage(), e.getCause());
}
}
}
💡 데이터 값이 존재하지 않는 경우 (로그인 정보가 존재하지 않는 경우) [ Spring Security 수행과정 ]
1. 로그 상으로 CustomAuthenticationFilter의 수행을 확인하였습니다. 2. 로그 상으로 CustomAuthenticationProvider를 수행하였으나 데이터가 존재하지 않아서 CustomAuthFailureHandler로 이동하여 에러 메시지를 발생시켰습니다.
3.2. 사용자 정보를 인증한 경우
💡 데이터 값이 존재하는 경우 -> 사용자 정보를 인증한 경우
[ Spring Security 수행과정 ]
1. 로그 상으로 CustomAuthenticationFilter의 수행을 확인하였습니다. 2. 로그 상으로 CustomAuthenticationProvider를 수행하여 데이터 조회를 수행을 확인하였습니다. 3. 로그 상으로 CustomLoginSuccessHandler를 잘 수행하여 응답 값을 반환받음을 확인하였습니다.
[참고] Spring Security의 인증 방법으로 JWT를 이용한 인증 방법에 대한 이해에 대해서 공유드립니다.