스프링 시큐리티에는 UsernamePasswordAuthenticationFilter라는 필터가 존재한다. 이는 기본적으로 /login에 username,password를 전송하면 동작을 한다. 허나 formLogin을 disable하면 작동하지 않는다.
이를 작동하게 하려면 다시 securityconfig에 등록을 해줘야한다.
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
private final UserDetailsService userDetailsService;
@Bean
public BCryptPasswordEncoder encodePwd(){
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//AuthenticationManager 생성
AuthenticationManagerBuilder sharedObject = http.getSharedObject(AuthenticationManagerBuilder.class);
sharedObject.userDetailsService(userDetailsService);
AuthenticationManager authenticationManager = sharedObject.build();
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션을 stateless로 관리
.httpBasic(AbstractHttpConfigurer::disable) // 기본적인 로그인 기능 사용 x
.formLogin(AbstractHttpConfigurer::disable)
// 특정 URL에 대한 권한 설정.
.authorizeHttpRequests((authorizeRequests) -> {
authorizeRequests.requestMatchers("/api/v1/user/**").hasAnyRole("ADMIN", "MANAGER","USER");
authorizeRequests.requestMatchers("/api/v1/manager/**")
.hasAnyRole("ADMIN", "MANAGER");
authorizeRequests.requestMatchers("/api/v1/admin/**")
.hasRole("ADMIN");
authorizeRequests.anyRequest().permitAll();
})
.cors((cors)->{
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("*"));
config.setAllowedHeaders(List.of("*"));
config.setAllowedMethods(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**",config);
cors.configurationSource(source);
})
.addFilterBefore(new MYFilter3(), SecurityContextHolderFilter.class)
.authenticationManager(authenticationManager)
.addFilter(new JwtAuthenticationFilter(authenticationManager)); //AuthenticationFilter 등록
return http.getOrBuild();
}
}
UsernamePasswordAuthenticationFilter에는 attemptAuthentication라는 메소드가 존재하는데, 로그인을 시도할 때 실행된다.
이제 UsernamePasswordAuthenticationFilter 필터를 보자
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
// /login 요청을 하면 로그인 시도를 위해서 실행되는 함수
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
System.out.println("attempt login");
// 1. username, password 받기
try {
ObjectMapper om = new ObjectMapper();
User user = om.readValue(request.getInputStream(),User.class);
System.out.println(user);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());
System.out.println(authenticationToken);
// PrincipalDetailsService의 loadUserByUsername()이 실행됨.
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// authentication이 들어왔다는건 DB에 username과 password가 일치하는 것을 찾았다는 것.
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
System.out.println(principalDetails.getUser().getUsername());
// 이제 Authentication 객체가 세션영역에 저장됨. 즉, 로그인이 되었다는 뜻.
return authentication; // 저장을 위해 리턴
} catch (IOException e) {
throw new RuntimeException(e);
}
// 2. 정상인지 로그인 시도
// authenticationManager로 로그인 시도를 하면 PrincipalDetailsService가 호출됨.
// loadUserByUsername()이 호출됨.
// 3. PrincipalDetails를 세션에 담기
// 담지 않으면 권한관리가 되지 않음.
// 4. JWT 토큰을 만들어서 응답.
}
이렇게 로그인이 되면 successfulAuthentication()가 호출이 된다.
이제 successfulAuthentication()을 활용해서 로그인 후 사용자에게 줄 토큰을 만들어보자.
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
System.out.println("successfulAuthentication : 인증 완료!" );
PrincipalDetails principalDetails = (PrincipalDetails) authResult.getPrincipal();
//Hash암호 방식
String jwtToken = JWT.create()
.withSubject("token")
.withExpiresAt(new Date(System.currentTimeMillis()+(60000*10))) // 토큰의 만료시간
.withClaim("id",principalDetails.getUser().getId())
.withClaim("username",principalDetails.getUser().getUsername())
.sign(Algorithm.HMAC512("secret"));
response.addHeader("Authorization","Bearer "+jwtToken);
}
이제 로그인을 하면

header에 성공적으로 Authorization에 JWT값이 들어갔다.