Spring Security, Spring Cloud, JWT, MSA
JWT + Spring Security + Spring Cloud = MSA 1. ํ™˜๊ฒฝ์„ธํŒ…์— ์ด์–ด์„œ Spring Security๋ฅผ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‚ฌ์šฉํ•ด๋ณด์ž

Spring Security์— ๋Œ€ํ•ด ๊ธฐ๋ณธ์ ์ธ ๊ฒƒ์„ ์•ˆ๋‹ค๋Š” ๊ฐ€์ •ํ•˜์— ์ง„ํ–‰ํ•˜์˜€๋‹ค. Spring Security์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์€ Spring Security๋ฅผ ์ด์šฉํ•œ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ์—์„œ ์ƒ์„ธํ•˜๊ฒŒ ์„ค๋ช…ํ•˜์˜€๋‹ค

Spring Security๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์„ธ์…˜๊ณต์œ ๋ฅผ ์ „์ œ๋กœ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌํ•œ๋‹ค. ํ•˜์ง€๋งŒ MSA ๋ฐฉ์‹์—์„œ ์„ธ์…˜ ๊ณต์œ ๊ฐ€ ๊นŒ๋‹ค๋กญ๊ธฐ๋•Œ๋ฌธ์— jwt ํ† ํฐ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์˜€๋‹ค. ์ด๋ฅผ ์œ„ํ•ด Spring Security๋ฅผ ์ „๋ถ€๋‹ค ์ปค์Šคํ…€ ์ง„ํ–‰

Security ํ•ญ๋ชฉ
์ „์ฒด์ ์ธ ๋กœ๊ทธ์ธ ๊ฒ€์ฆ ํ•„ํ„ฐ : AuthenticationFilter
์„ฑ๊ณต ์‹คํŒจ ํŒ๋‹จ: CustomAuthenticationProvider
์„ฑ๊ณต์‹œ ์ฒ˜๋ฆฌ: AuthenticationFilter.successfulAuthentication
์‹คํŒจ์‹œ ์ฒ˜๋ฆฌ: AuthFailureHandler

1. WebSecurity

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurity extends WebSecurityConfigurerAdapter {

    private final UserService userService;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
    private final Environment env;
    private final JwtTokenProvider jwtTokenProvider;
    private final CustomAuthenticationProvider authProvider;

    private final AuthFailureHandler authFailureHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();

        http.authorizeRequests().antMatchers("/**")
                .hasIpAddress("127.0.0.1") //127.0.0.1์—์„œ ์ ‘์†ํ•˜๋Š” ๋ชจ๋“  ์„œ๋น„์Šค์š”์ฒญ์€ ๊ถŒํ•œ์—†์ด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค
                .antMatchers("/css/**").permitAll()
                .and()
                .addFilter(getAuthenticationFilter()); // Spring Security์—์„œ ์ œ๊ณตํ•˜๋Š” ๋กœ๊ทธ์ธ ์ธ์ฆ ํˆด์„ ์‚ฌ์šฉ์•ˆํ•˜๊ณ  ์ง์ ‘ ์ธ์ฆ ํ•„ํ„ฐ๋ฅผ ๋งŒ๋“ ๋‹ค

        http.headers().frameOptions().disable(); // for H2 frame
    }

    private AuthenticationFilter getAuthenticationFilter() throws Exception {
        AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager(), userService, env, jwtTokenProvider);
        authenticationFilter.setFailureHandler(authFailureHandler); //๋กœ๊ทธ์ธ ์‹คํŒจ ํ•ธ๋“ค๋Ÿฌ ์ฃผ์ž…
        return authenticationFilter;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider);
        auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
    }


}

JwtTokenProvider๋Š” Jwt๊ด€๋ จ์œ ํ‹ธ์ด๋‹ค. ์•„๋ž˜์— ์„ค๋ช…์ถ”๊ฐ€

์ฒซ๋ฒˆ์งธ configure

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();

    http.authorizeRequests().antMatchers("/**")
            .hasIpAddress("127.0.0.1") //127.0.0.1์—์„œ ์ ‘์†ํ•˜๋Š” ๋ชจ๋“  ์„œ๋น„์Šค์š”์ฒญ์€ ๊ถŒํ•œ์—†์ด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค
            .antMatchers("/css/**").permitAll()
            .and()
            .addFilter(getAuthenticationFilter()); // Spring Security์—์„œ ์ œ๊ณตํ•˜๋Š” ๋กœ๊ทธ์ธ ์ธ์ฆ ํˆด์„ ์‚ฌ์šฉ์•ˆํ•˜๊ณ  ์ง์ ‘ ์ธ์ฆ ํ•„ํ„ฐ๋ฅผ ๋งŒ๋“ ๋‹ค

    http.headers().frameOptions().disable(); // for H2 frame
}

private AuthenticationFilter getAuthenticationFilter() throws Exception {
        AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager(), userService, env, jwtTokenProvider);
        authenticationFilter.setFailureHandler(authFailureHandler); //๋กœ๊ทธ์ธ ์‹คํŒจ ํ•ธ๋“ค๋Ÿฌ ์ฃผ์ž…
        return authenticationFilter;
    }
  • addFilter(getAuthenticationFilter());๋ฅผ ํ†ตํ•ด AuthenticationFilter๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๋ช…์‹œํ•œ๋‹ค. ์ฆ‰, Spring Security์—์„œ ์ œ๊ณตํ•˜๋Š” ๋กœ๊ทธ์ธ ํ•„ํ„ฐ๋ฅผ ์‚ฌ์šฉ์•ˆํ•˜๊ณ  ์ง์ ‘ ์ธ์ฆ ํ•„ํ„ฐ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•œ๋‹ค. ์ด ํ•„ํ„ฐ๋Š” CustomAuthenticationProvider์—๊ฒŒ ๋กœ๊ทธ์ธ์„ ์‹œ๋„ํ•˜๋ผ๊ณ  ๋ช…๋ น์„ ๋‚ด๋ฆฌ๊ณ  ๋กœ๊ทธ์ธ๊ฒ€์ฆ์—์„œ ์ „๋‹ฌ๋œ ๊ฒฐ๊ณผ(์„ฑ๊ณตor์‹คํŒจ)์— ๋”ฐ๋ผ ๋กœ์ง์„ ์‹คํ–‰ํ•œ๋‹ค.

๋‘๋ฒˆ์งธ configure


@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(authProvider);
    auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
}

  • CustomAuthenticationProvider๋ฅผ ์ฃผ์ž…ํ•œ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ํผ์„ ์ž…๋ ฅํ•  ์‹œ CustomAuthenticationProvider๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธ ์„ฑ๊ณต์ด๋‚˜ ์‹คํŒจ๋ฅผ ํŒ๋‹จํ•œ๋‹ค.

2. JWT

  • jwt ๊ด€๋ จ ์œ ํ‹ธํ•จ์ˆ˜
  • application.yaml์—์„œ ์ง€์ •ํ•œ ๊ฐ’๋“ค์„ @Valueํ†ตํ•ด ์‚ฌ์šฉํ•œ๋‹ค
@Component
@Slf4j
public class JwtTokenProvider {

    @Value("${token.access-expired-time}")
    private long ACCESS_EXPIRED_TIME;

    @Value("${token.refresh-expired-time}")
    private long REFRESH_EXPIRED_TIME;

    @Value("${token.secret}")
    private String SECRET;

    public String getUserId(String token) {
        return getClaimsFromJwtToken(token).getSubject();
    }

    public String getRefreshTokenId(String token) {
        return getClaimsFromJwtToken(token).get("value").toString();
    }

    public List<String> getRoles(String token) {
        return (List<String>) getClaimsFromJwtToken(token).get("roles");
    }

    public void validateJwtToken(String token) {
        try {
            Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
        } catch (SignatureException | MalformedJwtException |
                 UnsupportedJwtException | IllegalArgumentException | ExpiredJwtException jwtException) {
            throw jwtException;
        }
    }

    private Claims getClaimsFromJwtToken(String token) {
        try {
            return Jwts.parser().setSigningKey(SECRET)
                    .parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            return e.getClaims();
        }
    }

    public Date getExpiredTime(String token) {
        return getClaimsFromJwtToken(token).getExpiration();
    }

    public String createJwtAccessToken(String userId, String uri, UserRole role) {
        Claims claims = Jwts.claims();
        claims.put("role", role);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userId)
                .setExpiration(
                        new Date(System.currentTimeMillis() + ACCESS_EXPIRED_TIME)
                )
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .setIssuer(uri)
                .compact();

    }

    public String createJwtRefreshToken() {
        Claims claims = Jwts.claims();
        claims.put("value", UUID.randomUUID());

        return Jwts.builder()
                .addClaims(claims)
                .setExpiration(
                        new Date(System.currentTimeMillis() + REFRESH_EXPIRED_TIME)
                )
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
    }
}

3. ํ•„ํ„ฐ ๊ตฌํ˜„

/**
 * ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ํ•„ํ„ฐ
 * @author ๊น€์ฐฌ์˜
 */
@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private UserService userService;
    private Environment env;
    private final JwtTokenProvider jwtTokenProvider;

    @Autowired
    public AuthenticationFilter(AuthenticationManager authenticationManager,
                                UserService userService,
                                Environment env, JwtTokenProvider jwtTokenProvider){
        this.jwtTokenProvider = jwtTokenProvider;
        this.userService = userService;
        this.env = env;
        super.setAuthenticationManager(authenticationManager);
    }

    /**
     * ์ธ์ฆ ์‹œ๋„
     * <p>์ž…๋ ฅ๋ฐ›์€ ๊ฐ’์„ ์ธ์ฆ ํ† ํฐ์— ์ €์žฅ
     * <p>์ดํ›„ {@link CustomAuthenticationProvider#authenticate(Authentication)}๋กœ ์ „๋‹ฌํ•œ๋‹ค
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {

        try{
            RequestLogin creds = new ObjectMapper().readValue(request.getInputStream(), RequestLogin.class);
            if (creds==null){
                throw new NoActivatedException("hello");
            }
            return getAuthenticationManager().authenticate(
                    new UsernamePasswordAuthenticationToken(
                            creds.getEmail(),
                            creds.getPassword(),
                            new ArrayList<>()
                    )
            );
        } catch(IOException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * ๋กœ๊ทธ์ธ ์„ฑ๊ณต์‹œ ์ฒ˜๋ฆฌ
     * <p> JWT ํ† ํฐ์„ ๋ฐœํ–‰ํ•˜๊ณ  ์ฟ ํ‚ค์— ํ† ํฐ ๊ฐ’์„ ์ €์žฅํ•œ๋‹ค
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                            FilterChain chain, Authentication authResult)
            throws IOException, ServletException {
        String userName = ((User)authResult.getPrincipal()).getUsername();
        UserDto userDetails = userService.getUserDetailsByEmail(userName);

        String token = jwtTokenProvider.createJwtAccessToken(
                userDetails.getId(),
                request.getRequestURI(),
                userDetails.getRole()
                );

        ResponseCookie cookie = ResponseCookie.from("token", token)
                .sameSite("Strict")
                .path("/")
                .build();
        response.addHeader("Set-Cookie", cookie.toString() + ";HttpOnly");
        response.addHeader("token", token);
    }

    /**
     * ์‹คํŒจ ํ•ธ๋“ค๋Ÿฌ({@link AuthFailureHandler})๋ฅผ ์ง์ ‘ ์ฃผ์••ํ•œ๋‹ค
     */
    public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
        Assert.notNull(failureHandler, "FailureHandler๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค");
        super.setAuthenticationFailureHandler(failureHandler);
    }

}
  • attemptAuthentication: ๋กœ๊ทธ์ธ๊ฒ€์ฆ์ฒ˜๋ฆฌ๊ธฐ(AuthenticationProvider)์—๊ฒŒ ๋กœ๊ทธ์ธ ๊ฒ€์ฆ์„ ๋ช…๋ นํ•œ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ™”๋ฉด์—์„œ ์ž…๋ ฅํ•œ ์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ request์™€ ํ•จ๊ป˜ ์ „์†ก๋˜๊ณ  ์ „๋‹ฌ๋ฐ›์€ ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ๋งŒ๋“ค์–ด๋‘” RequestLogin๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋กœ๊ทธ์ธ๊ฒ€์ฆ์ฒ˜๋ฆฌ๊ธฐ์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค
    • RequestLogin
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        public class RequestLogin {
      
            @NotNull(message = "Email cannot be null")
            @Size(min = 2, message = "Email not be less than two characters")
            @Email
            private String email;
      
            @NotNull(message = "Password cannot be null")
            @Size(min = 8, message = "Password must be equals or greater than 8 characters")
            private String password;
      
            @NotNull(message = "Nickname cannot be null")
            @Size(min = 8, message = "Password must be equals or greater than 8 characters")
            private String nickName;
        }
      
  • successfulAuthentication: ๋กœ๊ทธ์ธ ๊ฒ€์ฆ์ด ์„ฑ๊ณตํ•œ๋‹ค๋ฉด JWT ํ† ํฐ์„ ๋ฐœํ–‰ํ•˜๊ณ  ์ฟ ํ‚ค์— ํ† ํฐ ๊ฐ’์„ ์ €์žฅํ•œ๋‹ค
  • setFailureHandler: ๋กœ๊ทธ์ธ ์‹คํŒจ์‹œ ์‹คํ–‰ํ•  ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ฃผ์ž…ํ•ด์ค€๋‹ค.
    • ์ด ํ•จ์ˆ˜๋Š” ์œ„์—์„œ ๋ณธ WebSecurity์—์„œ setFailureHandler๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š”๋ฐ ์ด ๋•Œ ๋งŒ๋“ค์–ด๋‘” ์ปค์Šคํ…€ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ฃผ์ž…ํ•ด์ฃผ์—ˆ๋‹ค(AuthFailureHandler)
        public class WebSecurity extends WebSecurityConfigurerAdapter {
      
          ...
      
          private final AuthFailureHandler authFailureHandler;
              
          private AuthenticationFilter getAuthenticationFilter() throws Exception {
              AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager(), userService, env, jwtTokenProvider);
              authenticationFilter.setFailureHandler(authFailureHandler); //๋กœ๊ทธ์ธ ์‹คํŒจ ํ•ธ๋“ค๋Ÿฌ ์ฃผ์ž…
              return authenticationFilter;
          }
      
          ...
      
      }
      
      

3. ๋กœ๊ทธ์ธ ๊ฒ€์ฆ๊ธฐ

  • ๋กœ๊ทธ์ธ์„ ๊ฒ€์ฆํ•œ๋‹ค
/**
 * DB์— ์žˆ๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ํ† ๋Œ€๋กœ ๊ฒ€์ฆ ์‹œ์ž‘
 * @author ๊น€์ฐฌ์˜
 */
@Component
@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {
    private final PasswordEncoder passwordEncoder;

    private final UserDetailsService customUserDetailsService;


    /**
     *
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if(authentication == null){
            throw new InternalAuthenticationServiceException("์ธ์ฆ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค");
        }
        String username = authentication.getName();
        if(authentication.getCredentials() == null){
            throw new AuthenticationCredentialsNotFoundException("Credentials์ด ์—†์Šต๋‹ˆ๋‹ค");
        }
        String password = authentication.getCredentials().toString();
        try {
            /* ์‚ฌ์šฉ์ž ์ •๋ณด ๋ฐ›๊ธฐ */
            UserDetails loadedUser = customUserDetailsService.loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException("์œ ์ €์ •๋ณด๊ฐ€ ์ž…๋ ฅ๋˜์ง€์•Š์•˜์Šต๋‹ˆ๋‹ค");
            }
            if (!loadedUser.isAccountNonLocked()) {
                throw new LockedException("์ž ๊ธด ๊ณ„์ •์ž…๋‹ˆ๋‹ค");
            }
            if (!loadedUser.isEnabled()) {
                throw new DisabledException("ํƒˆํ‡ดํ•œ ๊ณ„์ •์ž…๋‹ˆ๋‹ค");
            }
            if (!loadedUser.isAccountNonExpired()) {
                throw new AccountExpiredException("๊ธฐํ•œ ๋งŒ๋ฃŒ ๊ณ„์ •์ž…๋‹ˆ๋‹ค");
            }
            /* ์‹ค์งˆ์ ์ธ ์ธ์ฆ ์‹œ์ž‘ */
            if (!passwordEncoder.matches(password, loadedUser.getPassword())) {
                throw new BadCredentialsException("ํŒจ์Šค์›Œ๋“œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค");
            }
            if (!loadedUser.isCredentialsNonExpired()) {
                throw new CredentialsExpiredException("์ธ์ฆ๊ธฐํ•œ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค");
            }
            /* ์ธ์ฆ ์™„๋ฃŒ */
            UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(loadedUser, null, loadedUser.getAuthorities());
            result.setDetails(authentication.getDetails());
            return result;
        }
        catch (NoActivatedException e){
            throw new NoActivatedException("ํ•ด๋‹น ์•„์ด๋””๋Š” ์—†์Šต๋‹ˆ๋‹ค");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

  • try ๊ตฌ๋ฌธ ์•ˆ์ชฝ UserDetails loadedUser = customUserDetailsService.loadUserByUsername(username);๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ํ™•์ธํ•œ๋‹ค.
  • customUserDetailsService๋Š” ์ธํ„ฐํŽ˜์ด์Šค UserDetailsService์ด๊ณ  ๊ตฌํ˜„์ฒด๋Š” ์ง์ ‘ ๋งŒ๋“  UserService๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค
    • UserService
      public class UserService implements UserDetailsService {
      
        ...
      
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      
            UserEntity userEntity = userRepository.findByEmail(username).orElseThrow(()->new NoActivatedException("ํ•ด๋‹น์œ ์ €๋Š” ์—†์Šต๋‹ˆ๋‹ค"));
      
            // ์‚ญ์ œ๋œ๊ณ„์ •์ธ ๊ฒฝ์šฐ
            if(userEntity.getActive()=="0")
                return new User(userEntity.getEmail(), userEntity.getEncryptedPwd(),
                        false, true, true, true,
                        new ArrayList<>());
      
            // ์ธ์ฆ์ด ๋˜์ง€์•Š์€ ํšŒ์›
            if(userEntity.getRole().equals(UserRole.ROLE_NOT_PERMITTED))
                return new User(userEntity.getEmail(), userEntity.getEncryptedPwd(),
                        true, true, true, false,
                        new ArrayList<>());
      
      
            return new User(userEntity.getEmail(), userEntity.getEncryptedPwd(),
                    true, true, true, true,
                    new ArrayList<>());
        }
      
        ...
      
      }
      
      • DB์—์„œ ์œ ์ €๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ฒดํฌํ•˜๊ธฐ์ „์— DB์—์„œ ๊ฐ€์ ธ์˜จ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์œ ์ €์˜ ์œ ํšจ์„ฑ ์ฒดํฌ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ๊ทธ์— ๋”ฐ๋ฅธ User๋ฅผ returnํ•œ๋‹ค. ์ด๋•Œ User๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์˜ ๊ฐ์ฒด์ด๋‹ค.
      • ์œ„ ํ•จ์ˆ˜์—์„œ ๋ฐ˜ํ™˜๋œ ๊ฐ’์ด ํ˜ธ์ถœํ•œ AuthenticationProvider๋กœ ์ „๋‹ฌ๋œ๋‹ค
  • loadUserByUsername์— ์˜ํ•ด ๋ฐ˜ํ™˜๋œ User๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค. passwordEncoder.matches(password, loadedUser.getPassword()์—์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ์„ ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๋กœ๊ทธ์ธ ์„ฑ๊ณต์ด ์•„๋‹ ๊ฒฝ์šฐ, Exception์„ ๋˜์ง€๊ณ  ์ด Exception์€ AuthenticationFailureHandler์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค

4. ๋กœ๊ทธ์ธ Exception Handler

@Component
public class AuthFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {

        // ํ•œ๊ธ€ ์‚ฌ์šฉ
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        String errorMsg = "";

        if(exception instanceof AccountExpiredException){
            response.setStatus(HttpServletResponse.SC_CONFLICT);
            errorMsg = "๋งŒ๋ฃŒ๋œ ํšŒ์›์ž…๋‹ˆ๋‹ค";
        }
        if(exception instanceof DisabledException){
            response.setStatus(HttpServletResponse.SC_CONFLICT);
            errorMsg = "ํƒˆํ‡ดํ•œ ํšŒ์›์ž…๋‹ˆ๋‹ค";
        }
        if(exception instanceof NoActivatedException){
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            errorMsg = "์•„์ด๋””๋‚˜ ํŒจ์Šค์›Œ๋“œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€์•Š์Šต๋‹ˆ๋‹ค.";
        }
        if(exception instanceof BadCredentialsException){
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            errorMsg = "์•„์ด๋””๋‚˜ ํŒจ์Šค์›Œ๋“œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€์•Š์Šต๋‹ˆ๋‹ค.";
        }
        if(exception instanceof LockedException){
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            errorMsg = "์ด๋ฉ”์ผ ์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.";
        }
        response.getWriter().print(errorMsg);
        response.getWriter().flush();

    }
}
  • AuthenticationProvider์—์„œ ๋˜์ง„ Exception์— ๋”ฐ๋ผ ์—๋Ÿฌ๋ฉ”์„ธ์ง€๋ฅผ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ˜ํ™˜ํ•˜์—ฌ ๋ณด์—ฌ์ค€๋‹ค.

์—ฌ๊ธฐ์„œ AuthFailureHandler๋Š” ์–ด๋””์„œ ์ฃผ์ž…ํ–ˆ๋”๋ผ

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurity extends WebSecurityConfigurerAdapter {

    private final UserService userService;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
    private final Environment env;
    private final JwtTokenProvider jwtTokenProvider;
    private final CustomAuthenticationProvider authProvider;

    private final AuthFailureHandler authFailureHandler;

    ...

    private AuthenticationFilter getAuthenticationFilter() throws Exception {
        AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager(), userService, env, jwtTokenProvider);
        authenticationFilter.setFailureHandler(authFailureHandler); //๋กœ๊ทธ์ธ ์‹คํŒจ ํ•ธ๋“ค๋Ÿฌ ์ฃผ์ž…
        return authenticationFilter;
    }

    ...

}

WebSecurity์—์„œ setFailureHandler๋ฅผ ํ†ตํ•ด ์ฃผ์ž…ํ•˜๊ณ  ์ด ํ•จ์ˆ˜๋Š” AuthenticationFilter์—์„œ ๊ตฌํ˜„๋˜์—ˆ๋‹ค.

@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private UserService userService;
    private Environment env;
    private final JwtTokenProvider jwtTokenProvider;

    @Autowired
    public AuthenticationFilter(AuthenticationManager authenticationManager,
                                UserService userService,
                                Environment env, JwtTokenProvider jwtTokenProvider){
        this.jwtTokenProvider = jwtTokenProvider;
        this.userService = userService;
        this.env = env;
        super.setAuthenticationManager(authenticationManager);
    }

    ...

    /**
     * ์‹คํŒจ ํ•ธ๋“ค๋Ÿฌ({@link AuthFailureHandler})๋ฅผ ์ง์ ‘ ์ฃผ์••ํ•œ๋‹ค
     */
    public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
        Assert.notNull(failureHandler, "FailureHandler๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค");
        super.setAuthenticationFailureHandler(failureHandler);
    }

}
  • super๋ฅผ ํ†ตํ•ด ๋ถ€๋ชจํด๋ž˜์Šค๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด
image image
๋ถ€๋ชจํด๋ž˜์Šค AbstractAuthenticationProcessingFilter



๋ถ€๋ชจํด๋ž˜์Šค์—์„œ ํ•ด๋‹น ํ•จ์ˆ˜๊ฐ€ ๊ตฌํ˜„๋˜์–ด์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Spring

Java

๋Œ“๊ธ€ ์“ฐ๊ธฐ