목차
- [이론] 스프링 시큐리티 1
- [이론] 스프링 시큐리티2
- [실습] 스프링 시큐리티 Form Login
- [추가] CustomAuthenticationProvider vs DaoAuthenticationProvider
- [이론] 스프링 시큐리티3
- [이론] 스프링 시큐리티4
- [추가] AuthorizeReqeusts vs AuthorizeHttpRequests
- [실습] 스프링 시큐리티 Json data Login 처리
- [실습] 스프링 시큐리티 JWT 설정
- [실습] 스프링 시큐리티 JWT 처리
- OAuth2
이제 그럼 로그인 후 JWT 생성 처리와 Token을 가지고 접근 시 처리를 알아보자
Config
SecurityConfig
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors();
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests()
.mvcMatchers("/loginhome").authenticated()
.anyRequest().permitAll();
http
.addFilterAfter(jsonLoginProcessFilter, LogoutFilter.class)
//jwt 관련 필터 추가
.addFilterAfter(jwtAuthorizationFilter, JwtAuthenticationFilter.class);
return http.build();
}
JWTFilterConfig
@Configuration
@RequiredArgsConstructor
public class SecurityFilterBeanConfig {
private final ObjectMapper objectMapper;
private final AuthenticationManager authenticationManager;
private final JwtService jwtService;
private final UserRepository userRepository;
@Bean
public JsonLoginProcessFilter jsonLoginProcessFilter(JwtProviderHandler jwtProviderHandler) {
JsonLoginProcessFilter jsonLoginProcessFilter = new JsonLoginProcessFilter(objectMapper, authenticationManager);
//성공시 JWT 발행
jsonLoginProcessFilter.setAuthenticationSuccessHandler(jwtProviderHandler);
return jsonLoginProcessFilter;
}
//client로 부터 받은 jwt 유효성 검사 필터
@Bean
public JwtAuthorizationFilter jwtAuthorizationFilter() {
return new JwtAuthorizationFilter(jwtService, userRepository);
}
// 로그인 성공 시 JWT 발행
@Bean
public JwtProviderHandler jwtProviderHandler() {
return new JwtProviderHandler(jwtService, userRepository);
}
}
JsonLoginProcessFilter에 대해서는 [실습] 스프링시큐리티 Json data Login 처리에서 확인해보자
이제부터 알아볼 것은 JwtAuthorizationFilter와 JwtProviderHandler이다 이름을 보다시피 Jwt를 사용해서 권한을 부여하는 것과 Jwt를 발행하는 두가지 역할을 한다
JwtProviderHandler
위 설정에서 authenticationSuccesHandler를 설정해준 것이 AbstractAuthenticationProcessingFilter.doFilter에서 마지막 successfulAuthentication()
부분에서 실행이 된다
@RequiredArgsConstructor
public class JwtProviderHandler extends SimpleUrlAuthenticationSuccessHandler {
private final JwtService jwtService;
private final UserRepository userRepository;
@Override
@Transactional
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
CustomUser customUser = (CustomUser) authentication.getPrincipal();
String email = customUser.getEmail();
//토큰 생성
String accessToken = jwtService.createAccessToken(email);
String refreshToken = jwtService.createRefreshToken();
//client로 보내기
jwtService.sendBothToken(response, accessToken, refreshToken);
System.out.println("accessToken = " + accessToken);
System.out.println("refreshToken = " + refreshToken);
//refresh토큰 저장
userRepository.findUserByEmail(email).get()
.updateRefreshToken(refreshToken);
}
}
JsonLoginProcessFilter가 상속 한 AbstractAuthenticationProcessingFilter.doFilter
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//생략
try {
//생략
successfulAuthentication(request, response, chain, authenticationResult);
}
catch (InternalAuthenticationServiceException failed) {
//생략
}
}
JwtAuthorizationFilter
JwtAuthorizationFilter를 구현을 할때는 accessToken과 refreshToken의 재발행에 있어서 아래와 같이 결정을 했다
- accessToken 유효 -> authentication 저장
- accessToken 만료
- refreshToken 유효 -> authentication 저장, accessToken 갱신
- refreshToken 만료 -> throw Exception
accessToken이 유효하다면 어떠한 토큰도 재발행을 할 필요없이 SecurityContextHolder에 저장을 하면된다
만일 만료가되었더라도 refreshToken이 유효하다면 accessToken을 갱신후 저장을 하도록 했다
하지만 refreshToken이 만료가 되었다면 다시 로그인을 시도하도록 예외를 던지도록 결정을 했다
위 결정은 각자의 상황에 맞게 변경하여 사용하면 될 듯 하다
@RequiredArgsConstructor
public class JwtAuthorizationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserRepository userRepository;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
jwtService.extractAccessToken(request)
.filter(jwtService::isTokenValid)
.ifPresentOrElse(
this::saveAuthentication,
() -> checkRefreshToken(request, response)
);
filterChain.doFilter(request, response);
}
private void saveAuthentication(String accessToken) {
String email = jwtService.extractUserEmail(accessToken);
CustomUser customUser = new CustomUser(userRepository.findUserByEmail(email).get());
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(customUser, null, customUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
private void checkRefreshToken(HttpServletRequest request, HttpServletResponse response) {
Optional<String> refreshToken = jwtService.extractRefreshToken(request)
.filter(jwtService::isTokenValid);
if (refreshToken.isPresent()) {
User user = userRepository.findUserByRefreshToken(refreshToken.get())
.orElseThrow(IllegalArgumentException::new);
String accessToken = jwtService.createAccessToken(user.getEmail());
jwtService.setAccessTokenInHeader(response, accessToken);
saveAuthentication(accessToken);
return;
}
throw new IllegalArgumentException("Both tokens are not valid please Login again");
}
}
이로써 formLogin, json형식의 Login, Login 후 JWT 발행, JWT를 통한 권한 부여까지 알아보았다
이제 마지막으로 OAuth로그인에 대해서 추후 알아보도록 하자
'공부기록 > Spring Security' 카테고리의 다른 글
[실습] 스프링 시큐리티 OAuth2 Login 1 (0) | 2023.02.21 |
---|---|
[이론] 스프링 시큐리티 OAuth (0) | 2023.02.20 |
[실습] 스프링 시큐리티 JWT 설정 (0) | 2023.01.26 |
[실습] 스프링시큐리티 Json data Login 처리 (0) | 2022.12.28 |
[추가] AuthorizeRequests vs AuthorizeHttpRequests (0) | 2022.12.28 |