๐Ÿ“š atdd-subway-path ๋ฏธ์…˜ ์ •๋ฆฌ


STEP1, 2

JwtTokenProvider

  • jwt ํ† ํฐ ์ƒ์„ฑ, ํ† ํฐ ๊ฒ€์ฆ, ํ† ํฐ์—์„œ ์ธ์ฆ ์ •๋ณด ์ถ”์ถœํ•˜๋Š” ์œ ํ‹ธ ํด๋ž˜์Šค

AuthorizationExtractor

  • HTTP์˜ Authorization Header์—์„œ Bearer ํƒ€์ž…์ธ ๊ฒฝ์šฐ Access Token์„ ์ถ”์ถœํ•˜๋Š” ์œ ํ‹ธ ํด๋ž˜์Šค

AuthenticationPrincipalConfig

@Configuration
public class AuthenticationPrincipalConfig implements WebMvcConfigurer {
    private final AuthService authService;

    public AuthenticationPrincipalConfig(AuthService authService) {
        this.authService = authService;
    }

    @Override
    public void addArgumentResolvers(List argumentResolvers) {
        argumentResolvers.add(createAuthenticationPrincipalArgumentResolver());
    }

    @Bean
    public AuthenticationPrincipalArgumentResolver createAuthenticationPrincipalArgumentResolver() {
        return new AuthenticationPrincipalArgumentResolver(authService);
    }
}
  • AuthenticationPrincipalArgumentResolver๋Š” ๋นˆ ๋“ฑ๋ก์ด ๋˜์–ด์žˆ์ง€ ์•Š์Œ
  • ํ•ด๋‹น ๋ฆฌ์กธ๋ฒ„๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋“ฑ๋กํ•ด์ฃผ๋Š” java config
  • AuthenticationPrincipalArgumentResolver๋ฅผ ๋งŒ๋“ค๊ณ  ๋“ฑ๋ก

AuthenticationPrincipalArgumentResolver

public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
    private final AuthService authService;

    public AuthenticationPrincipalArgumentResolver(AuthService authService) {
        this.authService = authService;
    }

    // resolveArgument ๋ฉ”์„œ๋“œ๊ฐ€ ๋™์ž‘ํ•˜๋Š” ์กฐ๊ฑด์„ ์ •์˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // ํŒŒ๋ผ๋ฏธํ„ฐ ์ค‘ @AuthenticationPrincipal์ด ๋ถ™์€ ๊ฒฝ์šฐ ๋™์ž‘ํ•˜๊ฒŒ ์„ค์ •
        return parameter.hasParameterAnnotation(AuthenticationPrincipal.class);
    }

    // supportsParameter๊ฐ€ true์ธ ๊ฒฝ์šฐ ๋™์ž‘
    @Override
    public Member resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        String token = AuthorizationExtractor.extract((HttpServletRequest) webRequest.getNativeRequest());
        return authService.findMemberByToken(token);
    }
}
  • ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ์—์„œ ํŠน์ • ์กฐ๊ฑด์— ๋งž๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ ์›ํ•˜๋Š” ๊ฐ’์„ ๋ฐ”์ธ๋”ฉํ•ด์ฃผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค

  • resolveArgument ์—์„œ Member ๋„๋ฉ”์ธ ์ž์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ–ˆ์—ˆ์Œ

๊ด€๋ จ ์ •๋ฆฌ

AuthInterceptor

@Component
public class AuthInterceptor implements HandlerInterceptor {
    private final AuthService authService;

    public AuthInterceptor(AuthService authService) {
        this.authService = authService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = AuthorizationExtractor.extract(request);
        authService.validateToken(token);
        return true;
    }
}
  • ์ธ์ฆ / ์ธ๊ฐ€์— ๋Œ€ํ•œ ํ† ํฐ ๊ฒ€์ฆ์„ ๋‹ด๋‹น

  • ํ† ํฐ์ด ์œ ํšจํ•˜๋ฉด true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก

  • AuthorizationExtractor.extract() ๊ฐ€ ๋ฆฌ์กธ๋ฒ„๋ž‘ ์ค‘๋ณต์ด ๋˜๋Š”๋ฐ?

    • ์–ด์ฉ” ์ˆ˜ ์—†์Œ. ์ด ๋‘˜์€ ์„œ๋กœ ๋ชจ๋ฅด๋Š” ๊ด€๊ณ„๋กœ ๋…๋ฆฝ์ ์ด๋‹ˆ
    • ๋ณ€๊ฒฝ์˜ ์˜ํ–ฅ๋„ ์ตœ์†Œํ™”๋ฅผ ์œ„ํ•ด JwtProvider๊ฐ€ ์•„๋‹Œ JwtProvider๋ฅผ ํฌํ•จํ•œ AuthService๋ฅผ ๊ฐ€์ง€๋„๋ก

STEP3

Graph - ๊ทธ๋ž˜ํ”„ ์—ญํ• ์„ ํ•˜๋Š” ๋„๋ฉ”์ธ

public class Graph {
    private final WeightedMultigraph<Station, DefaultWeightedEdge> graph = new WeightedMultigraph(DefaultWeightedEdge.class);

    public Graph(List<Section> sections) {
        initPath(sections);
    }

    private void initPath(List<Section> sections) {
        for (Section section : sections) {
            initEdge(section);
        }
    }

    private void initEdge(Section section) {
        addVertex(section.getUpStation());
        addVertex(section.getDownStation());
        graph.setEdgeWeight(graph.addEdge(section.getUpStation(), section.getDownStation()), section.getDistance());
    }

    private void addVertex(Station station) {
        if (!graph.containsVertex(station)) {
            graph.addVertex(station);
        }
    }

    public Path shortestPath(Station source, Station target) {
        DijkstraShortestPath<Station, DefaultWeightedEdge> dijkstraShortestPath = new DijkstraShortestPath<>(graph);
        return new Path(dijkstraShortestPath.getPath(source, target).getVertexList(), dijkstraShortestPath.getPathWeight(source, target));
    }
}

Path - ์‚ฌ์‹ค์ƒ DTO ๋Š๋‚Œโ€ฆ

public class Path {
    private final List<Station> stations;
    private final double distance;

    public Path(List<Station> stations, double distance) {
        this.stations = stations;
        this.distance = distance;
    }

    public List<Station> stations() {
        return stations;
    }

    public double distance() {
        return distance;
    }
}

์ฒ˜์Œ ๊ตฌํ˜„ํ•œ ๋กœ์ง - ๋ชจ๋“  Line์„ ๋ถˆ๋Ÿฌ์™€ ์ตœ๋‹จ ๊ฑฐ๋ฆฌ ์กฐํšŒ

public PathResponse findPath(Long sourceId, Long targetId) {
        List<Line> lines = lineDao.findAll();
        Graph graph = new Graph(lines);
        Path path = graph.shortestPath(stationDao.findById(sourceId), stationDao.findById(targetId));
        return new PathResponse(StationResponse.listOf(path.stations()), (int) path.distance());
    }

  • ์‚ฌ์‹ค ์ฒ˜์Œ์—๋Š” sections๋งŒ ๊ฐ€์ ธ์™€์„œ ๊ทธ๋ž˜ํ”„๋ฅผ ๊ทธ๋ ธ์œผ๋‚˜, ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ํ•ด๋‹น ์—ญ์˜ ์ด๋ฆ„๋“ค์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์—ˆ์Œ
  • ๊ธฐ์กด ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ์ผ๋‹จ LineDao๋ฅผ ์ด์šฉํ–ˆ์—ˆ๋Š”๋ฐ, ์žฌ์—ฐ๋ง์˜ ๋ฆฌ๋ทฐ๋Œ€๋กœ ๋ชจ๋“  Line์„ ์กฐํšŒํ•˜๊ธฐ ๋ณด๋‹ค ํ•„์š”ํ•œ Sections๋งŒ ์กฐํšŒํ•˜๋„๋ก ๋ฆฌํŒฉํ† ๋ง

  • ์ด ๋ถ€๋ถ„์€ DM์œผ๋กœ ์ด์•ผ๊ธฐ๋ฅผ ๋‚˜๋ˆด๋Š”๋ฐ,
@Service
public class PathService {
    private final SectionDao sectionDao;
    private final StationDao stationDao;

    public PathService(SectionDao sectionDao, StationDao stationDao) {
        this.sectionDao = sectionDao;
        this.stationDao = stationDao;
    }

    public PathResponse findPath(Long sourceId, Long targetId) {
        List<Section> sections = sectionDao.findByStationIds(Arrays.asList(sourceId, targetId));
        Graph graph = new Graph(sections);
        Path path = graph.shortestPath(new Station(sourceId), new Station(targetId));
        return new PathResponse(StationResponse.listOf(combineStationById(path)), (int) path.distance());
    }

    private List<Station> combineStationById(Path path) {
        List<Long> stationIds = path.stations()
                .stream()
                .map(Station::getId)
                .collect(Collectors.toList());
        return stationDao.findByIds(stationIds).sortedStation(stationIds);
    }
}

CORS ์ด์Šˆ ํ•ด๊ฒฐ ๋ฒ•

  • ๋‚˜๊ฐ™์€ ๊ฒฝ์šฐ๋Š” front์ชฝ์— ํ”„๋ก์‹œ ์„œ๋ฒ„๋ฅผ ๋‘์–ด ํ•ด๋‹น ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ํ•˜์˜€์Œ

์ •๋ฆฌ

  • ํ† ํฐ์„ ์–ด๋””์— ์ €์žฅํ• ๊นŒ? ๊ธ€์„ ๋ณด๊ณ  ํ† ํฐ์„ ์ฟ ํ‚ค์— ์ €์žฅ
  • ์žฌ์—ฐ๋ง์˜ ๋ฆฌ๋ทฐ๋กœ ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ์˜ ๋ฌธ์ œ์ ์„ ์•Œ์•„๋ด„
  • XSS ๊ณต๊ฒฉ์„ ๋ง‰์„ ์ˆ˜ ์žˆ๋Š” HTTP-Only ์ฟ ํ‚ค๋ฅผ ๊ณ ๋ ค
  • ํ•˜์ง€๋งŒ ์ด๋Š” JS์—์„œ ๊บผ๋‚ด ์“ธ ์ˆ˜ ์—†์–ด ํ—ค๋”์— ํ† ํฐ์„ ์‹ค์–ด ๋ณด๋‚ผ ์ˆ˜ ์—†๋Š” ์ด์Šˆ
  • (๋ฏธ์…˜ ์š”๊ตฌ์‚ฌํ•ญ๊ณผ OAuth ํ‘œ์ค€ ๋“ฑ์„ ๊ณ ๋ คํ•ด) ์žฌ์—ฐ๋ง๊ณผ ๋‚˜๋ˆˆ DM ๋‚ด์šฉ๊ณผ ๊ฐ™์ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๋“ค๋กœ ์ฟ ํ‚ค ๋Œ€์‹  LocalStorage์— ํ† ํฐ์„ ์ €์žฅํ•˜๋„๋ก ๋ณ€๊ฒฝ
* ์›น ์ด์™ธ์˜ ํด๋ผ์ด์–ธํŠธ๊นŒ์ง€ ๊ณ ๋ คํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ์ƒ๊ฐ
* OAuth ํ‘œ์ค€์„ ์ง€ํ‚ค๊ธฐ ์œ„ํ•จ
* ์‚ฌ์‹ค HttpOnly๋ฅผ ํ†ตํ•ด ๋ณดํ˜ธํ•˜์—ฌ๋„, ํ•˜์ด์žฌํ‚น ๋“ฑ ๋งŽ์€ ๋ณด์•ˆ์  ์œ„ํ—˜์ด ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์•…์˜์ ์ธ ์˜๋„๋ฅผ ๊ฐ€์ง„ ์‚ฌ๋žŒ์ด ๋ถˆํŽธํ•ด์งˆ ๋ฟ ๋ณด์•ˆ์ ์œผ๋กœ ์™„๋ฒฝํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์Œ
* MDN์—์„œ๋„ HTML5 ์ดํ›„๋ถ€ํ„ฐ Cookie๋ฅผ ์ €์žฅ์†Œ์™€ ๊ฐ™์€ ์šฉ๋„๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๊ฒƒ์„ ๊ถŒ์žฅ

์ •๋ฆฌ

Front

  • vue.config๋ฅผ ํ†ตํ•ด ์ค‘๋ณต๋˜๋Š” URL์— ๋Œ€ํ•œ ์„ค์ •
  • ์ค‘๋ณต๋˜๋Š” fetch๋Š” ๋ชจ๋“ˆ๋กœ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์„œ(fetch.js) ์‚ฌ์šฉํ•˜๋„๋ก ๋ณ€๊ฒฝ

ArgumentResolver์™€ AuthInterceptor

ArgumentResolver

Strategy interface for resolving method parameters into argument values in the context of a given request.

  • ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ๋ฆฌ์กธ๋น™
  • ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉ ํ•ด์ฃผ๋Š” ์—ญํ• 

    AuthInterceptor

    Workflow interface that allows for customized handler execution chains. Applications can register any number of existing or custom interceptors for certain groups of handlers, to add common preprocessing behavior without needing to modify each handler implementation. A HandlerInterceptor gets called before the appropriate HandlerAdapter triggers the execution of the handler itself. This mechanism can be used for a large field of preprocessing aspects, e.g. for authorization checks, or common handler behavior like locale or theme changes. Its main purpose is to allow for factoring out repetitive handler code.

  • ๊ถŒํ•œ ํ™•์ธ ๋˜๋Š” ๋กœ์ผ€์ผ ๋˜๋Š” ํ…Œ๋งˆ ๋ณ€๊ฒฝ๊ณผ ๊ฐ™์€ ์ผ๋ฐ˜์ ์ธ ํ•ธ๋“ค๋Ÿฌ ๋™์ž‘๊ณผ ๊ฐ™์€ ์ „์ฒ˜๋ฆฌ ์ธก๋ฉด์˜ ๋„“์€ ๋ถ„์•ผ์— ์‚ฌ์šฉ
  • ์ธ์ฆ / ์ธ๊ฐ€์— ๋Œ€ํ•œ ๊ฒ€์‚ฌ
  • ์ฃผ์š” ๋ชฉ์ ์€ ๋ฐ˜๋ณต์  ์ธ ํ•ธ๋“ค๋Ÿฌ ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ
  • Spring doc์—์„œ ๊ณต์‹์ ์œผ๋กœ authorization checks๋ฅผ ํ•˜๋Š” ์• ๋ผ๊ณ  ์ง€์ •ํ•ด์ค€ ์กด์žฌ
  • ๊ณต์‹๋ฌธ์„œ

์ธํ„ฐ์…‰ํ„ฐ์™€ ๋ฆฌ์กธ๋ฒ„๋Š” ์„œ๋กœ๋ฅผ ๋ชจ๋ฅด๋Š” ๊ด€๊ณ„์ด๊ณ  ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐ€์•ผํ•จ

์ฐธ๊ณ ํ•  ๊ธ€

์ •๋ฆฌ

๋‹ค์‹œ ๊ตฌํ˜„ํ•œ๋‹ค๋ฉด?

ArgumentResolver ์—์„œ DTO์ธ LoginMember๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ํ• ๊ฑธ!

  • ๋„๋ฉ”์ธ์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ทฐ์™€ ์ปจํŠธ๋กค๋Ÿฌ์— ๋…ธ์ถœ์‹œํ‚ค๊ณ  ๋„๋ฉ”์ธ์„ ์กฐ์ž‘ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Œ
  • ๋˜ ๋ถˆํ•„์š”ํ•œ pw ํ•„๋“œ๋„ ๊ฐ€์ง€๊ฒŒ ๋จ

๋น„๋ฒˆ ์•”ํ˜ธํ™”์™€ member ๊ฐ์ฒด์—์„œ ๋น„๋ฒˆ์„ ํ™•์ธํ•˜๊ฒŒ ํ• ๊ฑธ!

public TokenResponse createToken(TokenRequest tokenRequest) {
    Member member = memberDao.findByEmailAndPassword(tokenRequest.getEmail(), tokenRequest.getPassword())
            .orElseThrow(() -> new AuthorizationException("๋กœ๊ทธ์ธ ์‹คํŒจ์ž…๋‹ˆ๋‹ค."));
    String accessToken = jwtTokenProvider.createToken(String.valueOf(member.getId()));
    return new TokenResponse(accessToken);
}
  • ๋น„๋ฒˆ์„ ์•”ํ˜ธํ™” ํ•  ๋•Œ๋Š” Spring Security์˜ BCryptPasswordEncoder() ๋ฅผ ์‚ฌ์šฉ
  • ํšŒ์›๊ฐ€์ž…ํ•  ๋•Œ ์ž…๋ ฅํ•œ ๋น„๋ฒˆ์„ ์ธ์ฝ”๋”ฉํ•˜์—ฌ ์ €์žฅ
  • ๊ทธ๋Ÿฐ๋ฐ ๋งŒ์•ฝ ์—ฌ๊ธฐ์„œ ๋กœ๊ทธ์ธํ•  ๋•Œ ์ž…๋ ฅํ•œ ๋น„๋ฒˆ์„ encoder.encode() ํ•˜์—ฌ ์กฐํšŒํ•˜๋ฉด ์‹คํŒจ
  • ์™œ๋ƒ๋ฉด ๋™์ผํ•œ ์›๋ฌธ์ด์–ด๋„ ๊ฐ๊ธฐ ๋‹ค๋ฅธ ์ธ์ฝ”๋”ฉ๋œ ๊ฐ’์„ ๋‚ด๋ฑ‰๊ธฐ ๋•Œ๋ฌธ
PasswordEncoder encoder = new BCryptPasswordEncoder();
String ์›๋ฌธ = "qwe123";
String ์•”ํ˜ธํ™”๋œ_์›๋ฌธ = encoder.encode(์›๋ฌธ);
String ์•”ํ˜ธํ™”๋œ_์›๋ฌธ_2 = encoder.encode(์›๋ฌธ);

encorder.matches(์›๋ฌธ, ์•”ํ˜ธํ™”๋œ ์›๋ฌธ); // true
์•”ํ˜ธํ™”๋œ_์›๋ฌธ.equals(์•”ํ˜ธํ™”๋œ_์›๋ฌธ2); //false
  • ๋•Œ๋ฌธ์— ์ฟผ๋ฆฌ๋กœ Member๋ฅผ ๋ฐ›์•„์˜ค๊ณ  ์ด ๊ฐ์ฒด์—์„œ encorder.matches()๋ฅผ ์ด์šฉํ•ด ๋น„๋ฒˆ์„ ํ™•์ธํ•˜์ž

์ตœ๋‹จ๊ฑฐ๋ฆฌ ์ฐพ๋Š” ๋กœ์ง์„ ์ „๋žต ํŒจํ„ด์œผ๋กœ ๊ตฌํ˜„ํ• ๊ฑธ!

  • ๋„๋ฉ”์ธ์— ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ์˜ณ์€๊ฐ€?
  • ๋งŒ์•ฝ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ๋ณ€๊ฒฝ๋˜๋ฉด ๋„๋ฉ”์ธ์—๋„ ์˜ํ–ฅ์ด ๊ฐ
  • ์ „๋žตํŒจํ„ด์œผ๋กœ Dijkstra ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์ฃผ์ž…ํ•˜์ž

2021-06-16 ์œ„ ์‚ฌํ•ญ๋“ค์„ ๋ฐ˜์˜ํ•˜์—ฌ ๋‹ค์‹œ ๊ตฌํ˜„

ArgumentResolver์—์„œ DTO์ธ LoginMember ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๋ฆฌํŒฉํ† ๋ง

public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
    private final AuthService authService;

    public AuthenticationPrincipalArgumentResolver(AuthService authService) {
        this.authService = authService;
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(AuthenticationPrincipal.class);
    }

    @Override
    public LoginMember resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        String token = AuthorizationExtractor.extract((HttpServletRequest) webRequest.getNativeRequest());
        return authService.findLoginMemberByToken(token);
    }
}

LoginMember

public class LoginMember {
    private Long id;
    private String email;
    private int age;

    public LoginMember(Member member) {
        this(member.getId(), member.getEmail(), member.getAge());
    }
  • ๋กœ์ง์— ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ๋‹ด๊ณ  ์žˆ๋Š” DTO ์ƒ์„ฑ

  • ์•„ ์ฐธ๊ณ ๋กœ Interceptor์—์„œ๋Š” ํ† ํฐ ๊ฒ€์ฆ๋งŒ ํ•˜๋„๋ก ํ•จ
    • ์ฐจํ”ผ ์ด๋ฏธ ๋กœ๊ทธ์ธ์œผ๋กœ ์•„์ด๋”” ๋น„๋ฒˆ์„ ๊ฒ€์ฆํ–ˆ์ž–์•„?
  • ๋ฆฌ์กธ๋ฒ„์—์„œ๋Š” ํ† ํฐ์„ ํ†ตํ•ด ์ง„์งœ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ฆฌ์กธ๋น™๋งŒ ๋‹ด๋‹น

member ๊ฐ์ฒด์—์„œ ๋น„๋ฒˆ์„ ํ™•์ธํ•˜๋„๋ก

AuthService

public TokenResponse createToken(TokenRequest tokenRequest) {
    Member member = findMember(tokenRequest);
    if (member.isInvalidPassword(tokenRequest.getPassword())) {
        throw new AuthorizationException("๋กœ๊ทธ์ธ ์‹คํŒจ์ž…๋‹ˆ๋‹ค.");
    }
    String accessToken = jwtTokenProvider.createToken(String.valueOf(member.getId()));
    return new TokenResponse(accessToken);
}

private Member findMember(TokenRequest tokenRequest) {
    return memberDao.findByEmail(tokenRequest.getEmail())
            .orElseThrow(() -> new AuthorizationException("๋กœ๊ทธ์ธ ์‹คํŒจ์ž…๋‹ˆ๋‹ค."));
}
  • ์ถ”ํ›„ ๋‹จ๋ฐฉํ–ฅ ๋น„๋ฒˆ ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•ด ๊ฐ์ฒด์—์„œ ๋น„๋ฒˆ์„ ๋น„๊ตํ•˜๋„๋ก ๋ณ€๊ฒฝ

์ตœ๋‹จ๊ฑฐ๋ฆฌ ์ฐพ๋Š” ๋กœ์ง์„ ์ „๋žต ํŒจํ„ด์œผ๋กœ

  • ์ตœ๋‹จ ๊ฒฝ๋กœ ์ฐพ๊ธฐ ์ „๋žต์„ ์ธํ„ฐํŽ˜์ด์Šค๋กœ, ์ด๋ฅผ ๋‹ค์ต์ŠคํŠธ๋ผ๋กœ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ฆ
  • ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์ฝ”๋“œ์˜ ์˜ํ–ฅ์—†์ด ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋„๋ก

SubwayGraph

public class SubwayGraph {
    private final WeightedMultigraph<Station, DefaultWeightedEdge> graph;

    public SubwayGraph(WeightedMultigraph<Station, DefaultWeightedEdge> graph, List<Section> sections) {
        this.graph = graph;
        initPath(sections);
    }
   // ...
  • ๊ทธ๋ž˜ํ”„ ๋„๋ฉ”์ธ

ShortestPathStrategy ์ธํ„ฐํŽ˜์ด์Šค

public interface ShortestPathStrategy {
    List<Station> getVertexList(Station source, Station target);

    double getPathWeight(Station source, Station target);
}
  • ์ „๋žต๋งˆ๋‹ค getVertexList() ์™€ getPathWeight() ๋ฅผ ๊ณ„์‚ฐํ•˜๋„๋ก ๋ช…์„ธ

DijkstraShortestPathStrategy

public class DijkstraShortestPathStrategy implements ShortestPathStrategy {
    private final ShortestPathAlgorithm<Station, DefaultWeightedEdge> shortestPathAlgorithm;

    public DijkstraShortestPathStrategy(SubwayGraph subwayGraph) {
        this.shortestPathAlgorithm = new DijkstraShortestPath<>(subwayGraph.getGraph());
    }

    @Override
    public List<Station> getVertexList(Station source, Station target) {
        return shortestPathAlgorithm.getPath(source, target).getVertexList();
    }

    @Override
    public double getPathWeight(Station source, Station target) {
        return shortestPathAlgorithm.getPathWeight(source, target);
    }
}
  • SubwayGraph๋ฅผ ๋ฐ›์•„์„œ ์ตœ๋‹จ ๊ฒฝ๋กœ๋ฅผ ๊ตฌํ•  ์ˆ˜ ์žˆ์Œ

ShortestPathFinder

public class ShortestPathFinder {
    private final ShortestPathStrategy shortestPathStrategy;

    public ShortestPathFinder(ShortestPathStrategy shortestPathStrategy) {
        this.shortestPathStrategy = shortestPathStrategy;
    }

    public Path findShortestPath(Station source, Station target) {
        return new Path(shortestPathStrategy.getVertexList(source, target), shortestPathStrategy.getPathWeight(source, target));
    }
}
  • ์ตœ๋‹จ ๊ฒฝ๋กœ๋ฅผ ์ฐพ๋Š” ์—ญํ• 
  • ์ „๋žต์„ ์ฃผ์ž…๋ฐ›์•„ ์ตœ๋‹จ ๊ฒฝ๋กœ๋ฅผ ๊ตฌํ•˜๊ณ  ์‹ถ์–ด ๋„๋ฉ”์ธ์„ ๋ถ„๋ฆฌํ•˜์˜€์Œโ€ฆ

๋ณ€๊ฒฝ๋œ PathService

public PathResponse findPath(Long sourceId, Long targetId) {
    List<Section> sections = sectionDao.findByStationIds(Arrays.asList(sourceId, targetId));
    SubwayGraph subwayGraph = new SubwayGraph(new WeightedMultigraph<>(DefaultWeightedEdge.class), sections);
    ShortestPathFinder shortestPathFinder = new ShortestPathFinder(new DijkstraShortestPathStrategy(subwayGraph));
    Path shortestPath = shortestPathFinder.findShortestPath(new Station(sourceId), new Station(targetId));
    return new PathResponse(StationResponse.listOf(combineStationById(shortestPath)), (int) shortestPath.distance());
}

private List<Station> combineStationById(Path path) {
    List<Long> stationIds = path.stations()
            .stream()
            .map(Station::getId)
            .collect(Collectors.toList());
    return stationDao.findByIds(stationIds).sortedStation(stationIds);
}

๋ฐ˜์˜๋œ ์ฝ”๋“œ๋Š” ์—ฌ๊ธฐ์— ๐Ÿ‘ป