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์ชฝ์ ํ๋ก์ ์๋ฒ๋ฅผ ๋์ด ํด๋น ์ด์๊ฐ ๋ฐ์ํ์ง ์๋๋ก ํ์์
์ ๋ฆฌ
Cookie vs LocalStorage
- ํ ํฐ์ ์ด๋์ ์ ์ฅํ ๊น? ๊ธ์ ๋ณด๊ณ ํ ํฐ์ ์ฟ ํค์ ์ ์ฅ
- ์ฌ์ฐ๋ง์ ๋ฆฌ๋ทฐ๋ก ์ฟ ํค๋ฅผ ์ฌ์ฉํ์ ๋์ ๋ฌธ์ ์ ์ ์์๋ด
- 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);
}