Spring

TIL Spring #3-2

๋ฆฐ์˜ˆ์กฐ 2023. 12. 15. 21:43

ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ ๊ตฌํ˜„

Filter

'Spring security' framwork

 


 

 

1-1)ํšŒ์›๊ฐ€์ž… ๊ตฌํ˜„

- build.gradle 

JPA, MySQL ์ถ”๊ฐ€

// JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// MySQL
runtimeOnly 'com.mysql:mysql-connector-j'

 

+ application.properties / ์‚ฌ์šฉํ•  DB ์—ฐ๊ฒฐ

 

- ํŒจ์Šค์›Œ๋“œ ์•”ํ˜ธํ™” ํ•„์ˆ˜

- Spring security์—์„œ ์ œ๊ณตํ•˜๋Š” ์•”ํ˜ธํ™” ๋ฉ”์„œ๋“œ ์‚ฌ์šฉํ•ด์ค€๋‹ค.(์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์•”ํ˜ธ๋ž‘ ์•”ํ˜ธํ™”๋œ ์•”ํ˜ธ๋ฅผ ๋น„๊ตํ•ด ์ผ์น˜์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ๋„ ์žˆ์–ด ๋งค์šฐ ํŽธ๋ฆฌํ•˜๋‹ค.)

 

import java.util.Optional;

@Service
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    // ADMIN_TOKEN
    private final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";

    public void signup(SignupRequestDto requestDto) {
        String username = requestDto.getUsername();
        String password = passwordEncoder.encode(requestDto.getPassword());

        // ํšŒ์› ์ค‘๋ณต ํ™•์ธ
        Optional<User> checkUsername = userRepository.findByUsername(username);
        if (checkUsername.isPresent()) {
            throw new IllegalArgumentException("์ค‘๋ณต๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.");
        }

        // email ์ค‘๋ณตํ™•์ธ
        String email = requestDto.getEmail();
        Optional<User> checkEmail = userRepository.findByEmail(email);
        if (checkEmail.isPresent()) {
            throw new IllegalArgumentException("์ค‘๋ณต๋œ Email ์ž…๋‹ˆ๋‹ค.");
        }

        // ์‚ฌ์šฉ์ž ROLE ํ™•์ธ
        UserRoleEnum role = UserRoleEnum.USER;
        if (requestDto.isAdmin()) {
            if (!ADMIN_TOKEN.equals(requestDto.getAdminToken())) {
                throw new IllegalArgumentException("๊ด€๋ฆฌ์ž ์•”ํ˜ธ๊ฐ€ ํ‹€๋ ค ๋“ฑ๋ก์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.");
            }
            role = UserRoleEnum.ADMIN;
        }

        // ์‚ฌ์šฉ์ž ๋“ฑ๋ก
        User user = new User(username, password, email, role);
        userRepository.save(user);
    }
}

 

1-2)๋กœ๊ทธ์ธ ๊ตฌํ˜„

 

@Service
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtUtil jwtUtil;

    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, JwtUtil jwtUtil) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.jwtUtil = jwtUtil;
    }

    // ADMIN_TOKEN
    private final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";

    public void signup(SignupRequestDto requestDto) {
        String username = requestDto.getUsername();
        String password = passwordEncoder.encode(requestDto.getPassword());

        // ํšŒ์› ์ค‘๋ณต ํ™•์ธ
        Optional<User> checkUsername = userRepository.findByUsername(username);
        if (checkUsername.isPresent()) {
            throw new IllegalArgumentException("์ค‘๋ณต๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.");
        }

        // email ์ค‘๋ณตํ™•์ธ
        String email = requestDto.getEmail();
        Optional<User> checkEmail = userRepository.findByEmail(email);
        if (checkEmail.isPresent()) {
            throw new IllegalArgumentException("์ค‘๋ณต๋œ Email ์ž…๋‹ˆ๋‹ค.");
        }

        // ์‚ฌ์šฉ์ž ROLE ํ™•์ธ
        UserRoleEnum role = UserRoleEnum.USER;
        if (requestDto.isAdmin()) {
            if (!ADMIN_TOKEN.equals(requestDto.getAdminToken())) {
                throw new IllegalArgumentException("๊ด€๋ฆฌ์ž ์•”ํ˜ธ๊ฐ€ ํ‹€๋ ค ๋“ฑ๋ก์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.");
            }
            role = UserRoleEnum.ADMIN;
        }

        // ์‚ฌ์šฉ์ž ๋“ฑ๋ก
        User user = new User(username, password, email, role);
        userRepository.save(user);
    }

    public void login(LoginRequestDto requestDto, HttpServletResponse res) {
        String username = requestDto.getUsername();
        String password = requestDto.getPassword();

        // ์‚ฌ์šฉ์ž ํ™•์ธ
        User user = userRepository.findByUsername(username).orElseThrow(
                () -> new IllegalArgumentException("๋“ฑ๋ก๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
        );

        // ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new IllegalArgumentException("๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
        }

        // JWT ์ƒ์„ฑ ๋ฐ ์ฟ ํ‚ค์— ์ €์žฅ ํ›„ Response ๊ฐ์ฒด์— ์ถ”๊ฐ€
        String token = jwtUtil.createToken(user.getUsername(), user.getRole());
        jwtUtil.addJwtToCookie(token, res);
    }
}

2) ํ•„ํ„ฐ

Filter์˜ ์œ„์น˜

 

- web application ์—์„œ ๊ด€๋ฆฌ๋˜๋Š” ์˜์—ญ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์˜ค๋Š” ์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•œ ์ตœ์ดˆ/์ตœ์ข… ๋‹จ๊ณ„์— ์žˆ๋‹ค.

- ๊ทธ๋ฆผ์—์„œ๋„ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ DispatcherServlet ๋ณด๋‹ค ์•ž์— ์œ„์น˜ํ•œ๋‹ค.

- ๊ตณ์ด ํ•„ํ„ฐ๋ฅผ ์“ฐ๋Š” ์ด์œ ๋Š”? ์ธ์ฆ/์ธ๊ฐ€์™€ ๊ด€๋ จํ•œ ๋‚ด์šฉ์„ ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง(Service)์—์„œ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ!

- ํ•„ํ„ฐ๋Š” ๋”ฑ ํ•œ๊ฐœ๋งŒ ์กด์žฌํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ๊ฐœ ์กด์žฌ ๊ฐ€๋Šฅ. ์—ฌ๋Ÿฌ๊ฐœ๋ฅผ chain ํ˜•ํƒœ๋กœ ๋ฌถ์–ด ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•จ.

 

<ํ•„ํ„ฐ ์‚ฌ์šฉ ์˜ˆ>

# LoggingFilter: URL ๋กœ๊น… filter (1)

# AuthFilter: ์ธ์ฆ/์ธ๊ฐ€ ์ฒ˜๋ฆฌ filter (2)

- ์ฒด์ธ์œผ๋กœ ๋ฌถ์—ฌ์žˆ๋Š” ํ˜•ํƒœ๋กœ ๊ตฌํ˜„ํ•˜๋ฉฐ @Order(1) ํ•ด๋‹น ์• ๋„ˆํ…Œ์ด์…˜๊ณผ ๋ฒˆํ˜ธ๋ฅผ ๋„ฃ์–ด ํ•„ํ„ฐ์— ์ˆœ์„œ๋ฅผ ๋ถ€์—ฌํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

- ๋‘ ๊ฐ€์ง€ ๋ชจ๋‘ Filter interface๋ฅผ implements ํ•œ๋‹ค. ์ˆ˜๋™์œผ๋กœ bean ๋“ฑ๋กํ•ด์ค€๋‹ค.(@Component ์‚ฌ์šฉ)

 

1. LoggingFilter

@Slf4j(topic = "LoggingFilter")
@Component
@Order(1)
public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // ์ „์ฒ˜๋ฆฌ
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String url = httpServletRequest.getRequestURI();
        log.info(url);

        chain.doFilter(request, response); // ๋‹ค์Œ Filter ๋กœ ์ด๋™

        // ํ›„์ฒ˜๋ฆฌ
        log.info("๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์™„๋ฃŒ");
    }
}

 

2. AuthFilter

@Slf4j(topic = "AuthFilter")
@Component
@Order(2)
public class AuthFilter implements Filter {

    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;

    public AuthFilter(UserRepository userRepository, JwtUtil jwtUtil) {
        this.userRepository = userRepository;
        this.jwtUtil = jwtUtil;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String url = httpServletRequest.getRequestURI();

        if (StringUtils.hasText(url) &&
                (url.startsWith("/api/user") || url.startsWith("/css") || url.startsWith("/js"))
        ) {
            // ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ ๊ด€๋ จ API ๋Š” ์ธ์ฆ ํ•„์š”์—†์ด ์š”์ฒญ ์ง„ํ–‰
            chain.doFilter(request, response); // ๋‹ค์Œ Filter ๋กœ ์ด๋™
        } else {
            // ๋‚˜๋จธ์ง€ API ์š”์ฒญ์€ ์ธ์ฆ ์ฒ˜๋ฆฌ ์ง„ํ–‰
            // ํ† ํฐ ํ™•์ธ
            String tokenValue = jwtUtil.getTokenFromRequest(httpServletRequest);

            if (StringUtils.hasText(tokenValue)) { // ํ† ํฐ์ด ์กด์žฌํ•˜๋ฉด ๊ฒ€์ฆ ์‹œ์ž‘
                // JWT ํ† ํฐ substring
                String token = jwtUtil.substringToken(tokenValue);

                // ํ† ํฐ ๊ฒ€์ฆ
                if (!jwtUtil.validateToken(token)) {
                    throw new IllegalArgumentException("Token Error");
                }

                // ํ† ํฐ์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
                Claims info = jwtUtil.getUserInfoFromToken(token);

                User user = userRepository.findByUsername(info.getSubject()).orElseThrow(() ->
                        new NullPointerException("Not Found User")
                );

                request.setAttribute("user", user);
                chain.doFilter(request, response); // ๋‹ค์Œ Filter ๋กœ ์ด๋™
            } else {
                throw new IllegalArgumentException("Not Found Token");
            }
        }
    }

}

 

3) Spring security

 

* ์ธ์ฆ/์ธ๊ฐ€ ํŽธ๋ฆฌํ•˜๊ฒŒ ํ•ด์ค€๋‹ค.

* session ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘!

* ๋””ํดํŠธ ๋กœ๊ทธ์ธ ์ œ๊ณต

* filter chain ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘

 

<์‚ฌ์šฉ๋ฐฉ๋ฒ•>

- spring-security ํ”„๋ ˆ์ž„์›Œํฌ ์ถ”๊ฐ€ 

- spring-security ์„ค์ •

package com.sparta.springauth.config;

import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity // Spring Security ์ง€์›์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•จ
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF ์„ค์ •
        http.csrf((csrf) -> csrf.disable());

        http.authorizeHttpRequests((authorizeHttpRequests) ->
                authorizeHttpRequests
                        .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources ์ ‘๊ทผ ํ—ˆ์šฉ 
                        .anyRequest().authenticated() // ๊ทธ ์™ธ ๋ชจ๋“  ์š”์ฒญ ์ธ์ฆ์ฒ˜๋ฆฌ
        );

        // ๋กœ๊ทธ์ธ ์‚ฌ์šฉ
        http.formLogin(Customizer.withDefaults());

        return http.build();
    }
}

 

- ์•ž์„  filter์™€ ๋น„๊ตํ•ด๋ณด๋ฉด ์ธ์ฆ, ์ธ๊ฐ€๋ฅผ ์•„์ฃผ ๊ฐ„ํŽธํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ๋‹ค.

 

^ Spring security - filter chain

  • Spring์—์„œ ๋ชจ๋“  ํ˜ธ์ถœ์€ DispatcherrServlet์„ ํ†ต๊ณผ ํ›„ -> Controller๋กœ ๋ถ„๋ฐฐ
  • ์ด๋•Œ ๊ฐ ์š”์ฒญ์— ๋Œ€ํ•ด ๊ณตํ†ต์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์•ผํ•  ๋•Œ DispatcherrServlet ์ด์ „ ๋‹จ๊ณ„๊ฐ€ ํ•„์š” -> ๊ทธ๊ฒƒ์ด Filter

 

Spring security์˜ filter

- Spring security๋„ ์ธ์ฆ ๋ฐ ์ธ๊ฐ€ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด filter ์‚ฌ์šฉ!

  • Spring security๋Š” FilterChainProxy๋ฅผ ํ†ตํ•ด ์ƒ์„ธ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค!

 

1) ๋กœ๊ทธ์ธ(์„ธ์…˜)

 

 

2) ๋กœ๊ทธ์ธ(JWT) -  ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง๊ณผ ์ธ์ฆ/์ธ๊ฐ€๋ฅผ ๋ถ„๋ฆฌํ•ด์ฃผ๊ธฐ

 

2-1) JwtAuthenticaionFilter: ๋กœ๊ทธ์ธ ์ง„ํ–‰ ๋ฐ JWT ์ƒ์„ฑ 

: Dispatcher Servlet ์ง€๋‚˜ Controller ์ดํ›„์—์„œ business logic ์žˆ๋Š” ๊ณณ์—์„œ ์ธ์ฆ/์ธ๊ฐ€ ํ•˜๋Š”๊ฒŒ ์•„๋‹Œ filter ๋‹จ๊ณ„์—์„œ ์ธ์ฆ/์ธ๊ฐ€ ์ฒ˜๋ฆฌ

@Slf4j(topic = "๋กœ๊ทธ์ธ ๋ฐ JWT ์ƒ์„ฑ")
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private final JwtUtil jwtUtil;

    public JwtAuthenticationFilter(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
        setFilterProcessesUrl("/api/user/login");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        log.info("๋กœ๊ทธ์ธ ์‹œ๋„");
        try {
            LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);

            return getAuthenticationManager().authenticate(
                    new UsernamePasswordAuthenticationToken(
                            requestDto.getUsername(),
                            requestDto.getPassword(),
                            null
                    )
            );
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        log.info("๋กœ๊ทธ์ธ ์„ฑ๊ณต ๋ฐ JWT ์ƒ์„ฑ");
        String username = ((UserDetailsImpl) authResult.getPrincipal()).getUsername();
        UserRoleEnum role = ((UserDetailsImpl) authResult.getPrincipal()).getUser().getRole();

        String token = jwtUtil.createToken(username, role);
        jwtUtil.addJwtToCookie(token, response);
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        log.info("๋กœ๊ทธ์ธ ์‹คํŒจ");
        response.setStatus(401);
    }
}

 

 

2-2) JwtAuthorizationFilter: API์— ์ „๋‹ฌ๋˜๋Š” JWT ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๋ฐ ์ธ๊ฐ€ ์ฒ˜๋ฆฌ -> ๋“ค์–ด์˜จ JWT์„ ๊ฒ€์ฆ

@Slf4j(topic = "JWT ๊ฒ€์ฆ ๋ฐ ์ธ๊ฐ€")
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;
    private final UserDetailsServiceImpl userDetailsService;

    public JwtAuthorizationFilter(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService) {
        this.jwtUtil = jwtUtil;
        this.userDetailsService = userDetailsService;
    }

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

        String tokenValue = jwtUtil.getTokenFromRequest(req);

        if (StringUtils.hasText(tokenValue)) {
            // JWT ํ† ํฐ substring
            tokenValue = jwtUtil.substringToken(tokenValue);
            log.info(tokenValue);

            if (!jwtUtil.validateToken(tokenValue)) {
                log.error("Token Error");
                return;
            }

            Claims info = jwtUtil.getUserInfoFromToken(tokenValue);

            try {
                 setAuthentication(info.getSubject());
            } catch (Exception e) {
                log.error(e.getMessage());
                return;
            }
        }

        filterChain.doFilter(req, res);
    }

    // ์ธ์ฆ ์ฒ˜๋ฆฌ
    public void setAuthentication(String username) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        Authentication authentication = createAuthentication(username);
        context.setAuthentication(authentication);

        SecurityContextHolder.setContext(context);
    }

    // ์ธ์ฆ ๊ฐ์ฒด ์ƒ์„ฑ
    private Authentication createAuthentication(String username) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    }
}

 

 

-> ์ด ์‹ค์Šต์€ token์„ ์ง์ ‘ ๊ฒ€์ฆํ•˜๊ณ  ์ง์ ‘ ์ธ์ฆ ๊ฐ์ฒด ๋งŒ๋“ค์–ด์„œ SecurityController ์— ๋„ฃ์–ด์ค˜์•ผํ•จ...

 

=> ์ฝ”๋“œ ๋ถ„์„์„ ์—ด์‹ฌํžˆ ํ•˜์ž ์˜์ฐจ ์˜์ฐจ ...

'Spring' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

TIL Spring #3-3  (0) 2023.12.18
TIL Spring #3-1  (0) 2023.12.14
TIL Spring #2-5  (0) 2023.12.13
TIL Spring #2-4  (0) 2023.12.12
TIL Spring #2-3  (0) 2023.12.12