From 66dcdb74c85fb902239def630bb453165dba4e44 Mon Sep 17 00:00:00 2001 From: Seung-Wan Jeong Date: Fri, 18 Mar 2022 01:03:31 +0900 Subject: [PATCH 01/10] Allow only logged-in users to read and write articles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * LoginRequiredInterceptor가 /questions/** 경로에 대한 요청을 가로채 로그인 상태 여부를 검사한다. --- README.md | 22 +++++++++++++++++++ .../kr/codesquad/cafe/system/MvcConfig.java | 14 ++++++++++++ .../intercepter/LoginRequiredInterceptor.java | 19 ++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 src/main/java/kr/codesquad/cafe/system/intercepter/LoginRequiredInterceptor.java diff --git a/README.md b/README.md index 345bc166c..8d384473b 100644 --- a/README.md +++ b/README.md @@ -143,3 +143,25 @@ - 로그아웃은 `GET` 요청으로도 구현 가능하나 로그아웃은 상태를 바꾸는 행위이므로 `POST`가 더 적절하다고 판단하였다. a 태그는 `POST` 요청을 보낼 수 없기 때문에 JS 함수를 onclick 이벤트로 삽입하여 구현하였다. - 에러 페이지를 표시할 때 적절한 HTTP status code를 응답에 포함시키면 더 좋겠다. - 지금은 로그인되지 않은 경우(401)와 로그인되어 있지만 다른 사용자의 회원정보수정 페이지에 접근하는 경우(403)를 구분하여 처리하기 어려워 생략하였다. + +## 미션 5 - 게시글 권한부여 + +### 요구사항 + +- [x] 로그인한 사용자만 게시글의 세부내용을 볼 수 있다. + - 로그인하지 않은 사용자는 게시글의 목록만 볼 수 있다. + - 로그인하지 않은 사용자가 게시글의 내용에 접근하면 로그인 페이지로 이동한다. +- [ ] 로그인한 사용자만 게시글을 작성할 수 있다. + - 게시물의 글쓴이 정보는 사용자 이름(name)을 사용한다. 게시물 작성 양식에서 글쓴이 필드는 제거한다. + - 로그인하지 않은 사용자가 게시물 작성 페이지에 접근하면 로그인 페이지로 이동한다. +- [ ] 로그인한 사용자는 자신의 글을 수정할 수 있다. + - 글 수정 요청은 @PutMapping으로 매핑한다. + - 수정하기 폼 과 수정하기 기능은 로그인 사용자와 글쓴이의 사용자 아이디가 같은 경우에만 가능하다. + - 상황에 따라 "다른 사람의 글을 수정할 수 없다."와 같은 에러 메시지를 출력하는 페이지로 이동하도록 구현한다. +- [ ] 로그인한 사용자는 자신의 글을 삭제할 수 있다. + - 글 삭제 요청은 @DeleteMapping으로 매핑한다. + - 삭제하기는 로그인 사용자와 글쓴이의 사용자 아이디가 같은 경우에만 가능하다. + - 상황에 따라 "다른 사람의 글을 수정할 수 없다."와 같은 에러 메시지를 출력하는 페이지로 이동하도록 구현한다. +- [ ] 스프링 부트, 웹 MVC로 구현한다. + - API로 구현하지 않고, 템플릿 기반으로 구현한다. + - HttpSession을 활용해서 구현한다. diff --git a/src/main/java/kr/codesquad/cafe/system/MvcConfig.java b/src/main/java/kr/codesquad/cafe/system/MvcConfig.java index cffaa4aae..492292b71 100644 --- a/src/main/java/kr/codesquad/cafe/system/MvcConfig.java +++ b/src/main/java/kr/codesquad/cafe/system/MvcConfig.java @@ -1,7 +1,10 @@ package kr.codesquad.cafe.system; +import kr.codesquad.cafe.system.intercepter.LoginRequiredInterceptor; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -16,4 +19,15 @@ public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/join").setViewName("users/form"); registry.addViewController("/questions/new").setViewName("qna/form"); } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(loginRequiredInterceptor()) + .addPathPatterns("/questions/**"); + } + + @Bean + public LoginRequiredInterceptor loginRequiredInterceptor() { + return new LoginRequiredInterceptor(); + } } diff --git a/src/main/java/kr/codesquad/cafe/system/intercepter/LoginRequiredInterceptor.java b/src/main/java/kr/codesquad/cafe/system/intercepter/LoginRequiredInterceptor.java new file mode 100644 index 000000000..3e2c844c5 --- /dev/null +++ b/src/main/java/kr/codesquad/cafe/system/intercepter/LoginRequiredInterceptor.java @@ -0,0 +1,19 @@ +package kr.codesquad.cafe.system.intercepter; + +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class LoginRequiredInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (request.getSession().getAttribute("currentUser") == null) { + response.sendRedirect("/login"); + return false; + } + + return true; + } +} From c09e7b6a745866ccc8df32345617fdc2723f1d09 Mon Sep 17 00:00:00 2001 From: Seung-Wan Jeong Date: Fri, 18 Mar 2022 01:50:23 +0900 Subject: [PATCH 02/10] Move user authentication from service to interceptor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * UserService에서 HttpSession 객체를 받아 URL path variable로 전달된 userId와 현재 로그인 중인 유저의 userId를 비교하는 검증 로직을 제거하였다. * 에러 페이지로 이동하는 로직에 try-catch 대신 UserAuthenticationInterceptor를 사용하였다. --- .../kr/codesquad/cafe/system/MvcConfig.java | 13 ++++++++- .../UserAuthenticationInterceptor.java | 27 +++++++++++++++++++ .../codesquad/cafe/user/UserController.java | 6 +---- .../kr/codesquad/cafe/user/UserService.java | 17 ------------ 4 files changed, 40 insertions(+), 23 deletions(-) create mode 100644 src/main/java/kr/codesquad/cafe/system/intercepter/UserAuthenticationInterceptor.java diff --git a/src/main/java/kr/codesquad/cafe/system/MvcConfig.java b/src/main/java/kr/codesquad/cafe/system/MvcConfig.java index 492292b71..b1a9b72d5 100644 --- a/src/main/java/kr/codesquad/cafe/system/MvcConfig.java +++ b/src/main/java/kr/codesquad/cafe/system/MvcConfig.java @@ -1,6 +1,7 @@ package kr.codesquad.cafe.system; import kr.codesquad.cafe.system.intercepter.LoginRequiredInterceptor; +import kr.codesquad.cafe.system.intercepter.UserAuthenticationInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; @@ -18,16 +19,26 @@ public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/join").setViewName("users/form"); registry.addViewController("/questions/new").setViewName("qna/form"); + registry.addViewController("/badRequest").setViewName("badRequest"); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginRequiredInterceptor()) - .addPathPatterns("/questions/**"); + .addPathPatterns("/questions/**") + .addPathPatterns("/users/**"); + + registry.addInterceptor(userAuthenticationInterceptor()) + .addPathPatterns("/users/*/form"); } @Bean public LoginRequiredInterceptor loginRequiredInterceptor() { return new LoginRequiredInterceptor(); } + + @Bean + public UserAuthenticationInterceptor userAuthenticationInterceptor() { + return new UserAuthenticationInterceptor(); + } } diff --git a/src/main/java/kr/codesquad/cafe/system/intercepter/UserAuthenticationInterceptor.java b/src/main/java/kr/codesquad/cafe/system/intercepter/UserAuthenticationInterceptor.java new file mode 100644 index 000000000..12e498305 --- /dev/null +++ b/src/main/java/kr/codesquad/cafe/system/intercepter/UserAuthenticationInterceptor.java @@ -0,0 +1,27 @@ +package kr.codesquad.cafe.system.intercepter; + +import kr.codesquad.cafe.user.User; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.HandlerMapping; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +public class UserAuthenticationInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + Map pathVariable = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + String targetUserId = (String) pathVariable.get("userId"); + User currentUser = (User) request.getSession().getAttribute("currentUser"); + + if (currentUser.userIdIs(targetUserId)) { + return true; + } + + response.sendRedirect("/badRequest"); + + return false; + } +} diff --git a/src/main/java/kr/codesquad/cafe/user/UserController.java b/src/main/java/kr/codesquad/cafe/user/UserController.java index 3876be94e..ee02f0c4e 100644 --- a/src/main/java/kr/codesquad/cafe/user/UserController.java +++ b/src/main/java/kr/codesquad/cafe/user/UserController.java @@ -61,15 +61,11 @@ public String viewUserProfile(@PathVariable("userId") String userId, Model model } @GetMapping("/users/{userId}/form") - public String viewUpdateForm(@PathVariable("userId") String userId, Model model, HttpSession session) { + public String viewUpdateForm(@PathVariable("userId") String userId, Model model) { try { - service.validateCurrentUser(userId, session); model.addAttribute("user", service.findByUserId(userId)); return "users/updateForm"; - } catch (IllegalStateException e) { - - return "badRequest"; } catch (NoSuchElementException e) { //noinspection SpringMVCViewInspection diff --git a/src/main/java/kr/codesquad/cafe/user/UserService.java b/src/main/java/kr/codesquad/cafe/user/UserService.java index e4d1a8c0e..3508f59b6 100644 --- a/src/main/java/kr/codesquad/cafe/user/UserService.java +++ b/src/main/java/kr/codesquad/cafe/user/UserService.java @@ -6,7 +6,6 @@ import javax.servlet.http.HttpSession; import java.util.List; import java.util.NoSuchElementException; -import java.util.Optional; @Service public class UserService { @@ -94,20 +93,4 @@ public User findByUserId(String userId) { .orElseThrow(() -> new NoSuchElementException("존재하지 않는 회원입니다.")); } - public void validateCurrentUser(String userId, HttpSession session) { - if (getCurrentUser(session).userIdIs(userId)) { - return; - } - - throw new IllegalStateException("사용자 ID가 일치하지 않습니다."); - } - - public User getCurrentUser(HttpSession session) { - return (User) Optional.ofNullable(session.getAttribute(CURRENT_USER)).orElseThrow( - () -> { - throw new IllegalStateException("로그인 상태가 아닙니다."); - } - ); - } - } From 7a3ed8f0a90e1055aabea7ec583b8a7cb02e68b0 Mon Sep 17 00:00:00 2001 From: Seung-Wan Jeong Date: Fri, 18 Mar 2022 02:18:44 +0900 Subject: [PATCH 03/10] Redirect to last requested page after login MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * LoginRequiredInterceptor에서 로그인 페이지로 리다이렉트를 발생시킨 요청의 경로를 세션에 저장해두었다가 성공적으로 로그인하고 나면 저장된 경로로 리다이렉트시킨다. 저장된 경로가 없다면 인덱스 페이지로 리다이렉트시킨다. --- .../cafe/system/intercepter/LoginRequiredInterceptor.java | 7 ++++++- src/main/java/kr/codesquad/cafe/user/LoginController.java | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/kr/codesquad/cafe/system/intercepter/LoginRequiredInterceptor.java b/src/main/java/kr/codesquad/cafe/system/intercepter/LoginRequiredInterceptor.java index 3e2c844c5..42f776969 100644 --- a/src/main/java/kr/codesquad/cafe/system/intercepter/LoginRequiredInterceptor.java +++ b/src/main/java/kr/codesquad/cafe/system/intercepter/LoginRequiredInterceptor.java @@ -4,13 +4,18 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; public class LoginRequiredInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - if (request.getSession().getAttribute("currentUser") == null) { + HttpSession session = request.getSession(); + + if (session.getAttribute("currentUser") == null) { + session.setAttribute("destinationAfterLogin", request.getRequestURI()); response.sendRedirect("/login"); + return false; } diff --git a/src/main/java/kr/codesquad/cafe/user/LoginController.java b/src/main/java/kr/codesquad/cafe/user/LoginController.java index df1d0c8f9..a49bddd5e 100644 --- a/src/main/java/kr/codesquad/cafe/user/LoginController.java +++ b/src/main/java/kr/codesquad/cafe/user/LoginController.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.PostMapping; import javax.servlet.http.HttpSession; +import java.util.Optional; @Controller public class LoginController { @@ -27,12 +28,17 @@ public String processLogin(String userId, String password, HttpSession session) try { userService.login(userId, password, session); - return "redirect:/"; + return "redirect:" + getDestination(session); } catch (Exception e) { return "users/login_failed"; } } + private String getDestination(HttpSession session) { + return (String) Optional.ofNullable(session.getAttribute("destinationAfterLogin")) + .orElse("/"); + } + @PostMapping("/logout") public String processLogout(HttpSession session) { userService.logout(session); From 6c10e6740f432c5b45568fde1bfff64105d036df Mon Sep 17 00:00:00 2001 From: Seung-Wan Jeong Date: Fri, 18 Mar 2022 10:17:49 +0900 Subject: [PATCH 04/10] Add writer's userId to Article object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 작성자를 식별하기 위해 작성자의 userId 정보 추가 * Article * ArticleRepository * schema.sql * sample_data.sql * Article 생성 시 작성자의 userId와 name은 세션의 currentUser를 참조한다. * 글 작성 페이지 및 ArticleCreationForm에서 글쓴이 입력 항목 제거. * Article 정보를 표시하는 각 페이지의 템플릿 수정 * 글쓴이 이름을 클릭하면 글쓴이의 프로필 페이지로 이동 --- README.md | 2 +- .../kr/codesquad/cafe/article/Article.java | 21 +++++++++++++------ .../cafe/article/ArticleController.java | 9 ++++++-- .../cafe/article/ArticleCreationForm.java | 9 -------- .../cafe/article/ArticleRepository.java | 8 ++++--- src/main/resources/sql/sample_data.sql | 9 ++++++-- src/main/resources/sql/schema.sql | 11 +++++----- src/main/resources/templates/index.html | 2 +- src/main/resources/templates/qna/form.html | 4 ---- src/main/resources/templates/qna/show.html | 2 +- 10 files changed, 43 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 8d384473b..525af7019 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ - [x] 로그인한 사용자만 게시글의 세부내용을 볼 수 있다. - 로그인하지 않은 사용자는 게시글의 목록만 볼 수 있다. - 로그인하지 않은 사용자가 게시글의 내용에 접근하면 로그인 페이지로 이동한다. -- [ ] 로그인한 사용자만 게시글을 작성할 수 있다. +- [x] 로그인한 사용자만 게시글을 작성할 수 있다. - 게시물의 글쓴이 정보는 사용자 이름(name)을 사용한다. 게시물 작성 양식에서 글쓴이 필드는 제거한다. - 로그인하지 않은 사용자가 게시물 작성 페이지에 접근하면 로그인 페이지로 이동한다. - [ ] 로그인한 사용자는 자신의 글을 수정할 수 있다. diff --git a/src/main/java/kr/codesquad/cafe/article/Article.java b/src/main/java/kr/codesquad/cafe/article/Article.java index 2aa22cc4f..4bf2049d3 100644 --- a/src/main/java/kr/codesquad/cafe/article/Article.java +++ b/src/main/java/kr/codesquad/cafe/article/Article.java @@ -8,7 +8,8 @@ public class Article { private Long id; private LocalDateTime timestamp; - private String writer; + private String writerUserId; + private String writerName; private String title; private String contents; @@ -28,13 +29,21 @@ public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; } - public String getWriter() { - return writer; + public String getWriterUserId() { + return writerUserId; } - public void setWriter(String writer) { - Assert.hasText(writer, "작성자는 공백이어선 안 됩니다."); - this.writer = writer; + public String getWriterName() { + return writerName; + } + + public void setWriterName(String writerName) { + this.writerName = writerName; + } + + public void setWriterUserId(String writerUserId) { + Assert.hasText(writerUserId, "작성자는 공백이어선 안 됩니다."); + this.writerUserId = writerUserId; } public String getTitle() { diff --git a/src/main/java/kr/codesquad/cafe/article/ArticleController.java b/src/main/java/kr/codesquad/cafe/article/ArticleController.java index f3d9cb3df..42b83d83b 100644 --- a/src/main/java/kr/codesquad/cafe/article/ArticleController.java +++ b/src/main/java/kr/codesquad/cafe/article/ArticleController.java @@ -1,5 +1,6 @@ package kr.codesquad.cafe.article; +import kr.codesquad.cafe.user.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -7,6 +8,8 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import javax.servlet.http.HttpSession; + @Controller public class ArticleController { @@ -25,9 +28,11 @@ public String viewQuestions(Model model) { } @PostMapping("/questions") - public String processCreationForm(ArticleCreationForm form) { + public String processCreationForm(ArticleCreationForm form, HttpSession session) { Article article = new Article(); - article.setWriter(form.getWriter()); + User writer = (User) session.getAttribute("currentUser"); + article.setWriterUserId(writer.getUserId()); + article.setWriterName(writer.getName()); article.setTitle(form.getTitle()); article.setContents(form.getContents()); service.post(article); diff --git a/src/main/java/kr/codesquad/cafe/article/ArticleCreationForm.java b/src/main/java/kr/codesquad/cafe/article/ArticleCreationForm.java index ab533d751..40abab24f 100644 --- a/src/main/java/kr/codesquad/cafe/article/ArticleCreationForm.java +++ b/src/main/java/kr/codesquad/cafe/article/ArticleCreationForm.java @@ -2,18 +2,9 @@ public class ArticleCreationForm { - private String writer; private String title; private String contents; - public String getWriter() { - return writer; - } - - public void setWriter(String writer) { - this.writer = writer; - } - public String getTitle() { return title; } diff --git a/src/main/java/kr/codesquad/cafe/article/ArticleRepository.java b/src/main/java/kr/codesquad/cafe/article/ArticleRepository.java index 65ff8a761..8e4ce9813 100644 --- a/src/main/java/kr/codesquad/cafe/article/ArticleRepository.java +++ b/src/main/java/kr/codesquad/cafe/article/ArticleRepository.java @@ -12,7 +12,8 @@ @Repository public class ArticleRepository { - private static final String SQL_SAVE_ARTICLE = "INSERT INTO ARTICLE(WRITER, TITLE, CONTENTS) VALUES(?, ?, ?)"; + private static final String SQL_SAVE_ARTICLE = + "INSERT INTO ARTICLE(WRITER_USERID, WRITER_NAME, TITLE, CONTENTS) VALUES(?, ?, ?, ?)"; private static final String SQL_FIND_ARTICLE = "SELECT * FROM ARTICLE WHERE ID = ?"; private static final String SQL_FIND_ARTICLE_ALL = "SELECT * FROM ARTICLE"; private final JdbcTemplate jdbcTemplate; @@ -24,7 +25,7 @@ public ArticleRepository(JdbcTemplate jdbcTemplate) { public void save(Article article) { jdbcTemplate.update(SQL_SAVE_ARTICLE, - article.getWriter(), article.getTitle(), article.getContents()); + article.getWriterUserId(), article.getWriterName(), article.getTitle(), article.getContents()); } public Optional
findOne(long id) { @@ -40,7 +41,8 @@ private RowMapper
articleRowMapper() { Article article = new Article(); article.setId(rs.getLong("id")); article.setTimestamp(rs.getTimestamp("timestamp").toLocalDateTime()); - article.setWriter(rs.getString("writer")); + article.setWriterUserId(rs.getString("writer_userid")); + article.setWriterName(rs.getString("writer_name")); article.setTitle(rs.getString("title")); article.setContents(rs.getString("contents")); diff --git a/src/main/resources/sql/sample_data.sql b/src/main/resources/sql/sample_data.sql index 3a3377635..d6e8b82ff 100644 --- a/src/main/resources/sql/sample_data.sql +++ b/src/main/resources/sql/sample_data.sql @@ -1,6 +1,11 @@ -- noinspection SqlResolveForFile -INSERT INTO ARTICLE(writer, title, contents) VALUES ('테스터', '샘플 게시물', '이 게시물은 샘플입니다.'); -INSERT INTO ARTICLE(writer, title, contents) VALUES ('테스터', '샘플 게시물 두번째', '이 게시물은 두번째 샘플입니다.'); +INSERT INTO ARTICLE(writer_userId, writer_name, title, contents) +VALUES ('tester', '테스터', '샘플 게시물', '이 게시물은 샘플입니다.'); + +INSERT INTO ARTICLE(writer_userId, writer_name, title, contents) +VALUES ('dorayaki', '도라에몽', '샘플 게시물 두번째', '이 게시물은 두번째 샘플입니다.'); + INSERT INTO CAFE_USER(userid, password, name, email) VALUES ('tester', 'aaa', '테스터', 'tester@testers.com'); + INSERT INTO CAFE_USER(userid, password, name, email) VALUES ('dorayaki', 'aaa', '도라에몽', 'help-me@dorae.mon'); diff --git a/src/main/resources/sql/schema.sql b/src/main/resources/sql/schema.sql index 909aa46ca..23f65e090 100644 --- a/src/main/resources/sql/schema.sql +++ b/src/main/resources/sql/schema.sql @@ -3,11 +3,12 @@ DROP TABLE IF EXISTS CAFE_USER; CREATE TABLE ARTICLE ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, - timestamp TIMESTAMP DEFAULT NOW(), - writer VARCHAR(255), - title VARCHAR(255), - contents VARCHAR(65535) + id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, + timestamp TIMESTAMP DEFAULT NOW(), + writer_userid VARCHAR(255), + writer_name VARCHAR(255), + title VARCHAR(255), + contents VARCHAR(65535) ); CREATE TABLE CAFE_USER diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 927120da2..193f8ddf0 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -26,7 +26,7 @@ {{#formattedAsArticleTimestamp}} {{timestamp}} {{/formattedAsArticleTimestamp}} - {{writer}} + {{writerName}}
diff --git a/src/main/resources/templates/qna/form.html b/src/main/resources/templates/qna/form.html index be6128c80..596a74f2e 100644 --- a/src/main/resources/templates/qna/form.html +++ b/src/main/resources/templates/qna/form.html @@ -13,10 +13,6 @@
-
- - -
diff --git a/src/main/resources/templates/qna/show.html b/src/main/resources/templates/qna/show.html index bd422d735..e5fc74642 100644 --- a/src/main/resources/templates/qna/show.html +++ b/src/main/resources/templates/qna/show.html @@ -24,7 +24,7 @@

{{title}}

class="article-author-thumb" alt="">
- + {{#formattedAsArticleTimestamp}} {{timestamp}} From 0dc9111ae8b1a816bb1d944966088e0909beb620 Mon Sep 17 00:00:00 2001 From: Seung-Wan Jeong Date: Fri, 18 Mar 2022 14:22:18 +0900 Subject: [PATCH 05/10] Allow user to modify article posted by self MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 로그인되지 않은 사용자가 수정 페이지에 접근하면 로그인 페이지로 리다이렉트 * 다른 사용자가 쓴 글의 수정 페이지에 접근하면 에러 페이지로 리다이렉트 * 다른 사용자가 쓴 글에 POST 요청을 보내면 에러 페이지로 리다이렉트 * 정상적으로 수정되었을 경우 Article의 id값을 기준으로 DB테이블에 UPDATE 쿼리 전송 --- .../kr/codesquad/cafe/article/Article.java | 4 ++ .../cafe/article/ArticleController.java | 26 +++++++++++++ .../cafe/article/ArticleRepository.java | 7 ++++ .../cafe/article/ArticleService.java | 4 ++ .../kr/codesquad/cafe/system/MvcConfig.java | 9 +++++ .../WriterAuthenticationInterceptor.java | 23 +++++++++++ .../resources/templates/qna/updateForm.html | 39 +++++++++++++++++++ 7 files changed, 112 insertions(+) create mode 100644 src/main/java/kr/codesquad/cafe/system/intercepter/WriterAuthenticationInterceptor.java create mode 100644 src/main/resources/templates/qna/updateForm.html diff --git a/src/main/java/kr/codesquad/cafe/article/Article.java b/src/main/java/kr/codesquad/cafe/article/Article.java index 4bf2049d3..03aab161f 100644 --- a/src/main/java/kr/codesquad/cafe/article/Article.java +++ b/src/main/java/kr/codesquad/cafe/article/Article.java @@ -63,4 +63,8 @@ public void setContents(String contents) { Assert.hasText(contents, "글 내용은 공백이어선 안 됩니다."); this.contents = contents; } + + public boolean hasId() { + return id != null; + } } diff --git a/src/main/java/kr/codesquad/cafe/article/ArticleController.java b/src/main/java/kr/codesquad/cafe/article/ArticleController.java index 42b83d83b..f1b9f46ff 100644 --- a/src/main/java/kr/codesquad/cafe/article/ArticleController.java +++ b/src/main/java/kr/codesquad/cafe/article/ArticleController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import javax.servlet.http.HttpSession; @@ -47,4 +48,29 @@ public String viewQuestion(@PathVariable("id") long id, Model model) { return "qna/show"; } + @GetMapping("/questions/{id}/form") + public String viewUpdateForm(@PathVariable("id") long id, Model model) { + model.addAttribute("article", service.retrieve(id)); + + return "qna/updateForm"; + } + + @PutMapping("/questions/{id}/update") + public String processUpdateForm(@PathVariable("id") long id, ArticleCreationForm form, HttpSession session) { + User currentUser = (User) session.getAttribute("currentUser"); + String writerUserId = service.retrieve(id).getWriterUserId(); + + if (!currentUser.userIdIs(writerUserId)) { + return "redirect:/badRequest"; + } + + Article article = new Article(); + article.setId(id); + article.setWriterName(currentUser.getName()); + article.setTitle(form.getTitle()); + article.setContents(form.getContents()); + service.update(article); + + return "redirect:/questions/{id}"; + } } diff --git a/src/main/java/kr/codesquad/cafe/article/ArticleRepository.java b/src/main/java/kr/codesquad/cafe/article/ArticleRepository.java index 8e4ce9813..d059bfcdd 100644 --- a/src/main/java/kr/codesquad/cafe/article/ArticleRepository.java +++ b/src/main/java/kr/codesquad/cafe/article/ArticleRepository.java @@ -14,6 +14,8 @@ public class ArticleRepository { private static final String SQL_SAVE_ARTICLE = "INSERT INTO ARTICLE(WRITER_USERID, WRITER_NAME, TITLE, CONTENTS) VALUES(?, ?, ?, ?)"; + private static final String SQL_UPDATE_ARTICLE = + "UPDATE ARTICLE SET WRITER_NAME=?, TITLE=?, CONTENTS=? WHERE ID=?"; private static final String SQL_FIND_ARTICLE = "SELECT * FROM ARTICLE WHERE ID = ?"; private static final String SQL_FIND_ARTICLE_ALL = "SELECT * FROM ARTICLE"; private final JdbcTemplate jdbcTemplate; @@ -24,6 +26,11 @@ public ArticleRepository(JdbcTemplate jdbcTemplate) { } public void save(Article article) { + if (article.hasId()) { + jdbcTemplate.update(SQL_UPDATE_ARTICLE, + article.getWriterName(), article.getTitle(), article.getContents(), article.getId()); + return; + } jdbcTemplate.update(SQL_SAVE_ARTICLE, article.getWriterUserId(), article.getWriterName(), article.getTitle(), article.getContents()); } diff --git a/src/main/java/kr/codesquad/cafe/article/ArticleService.java b/src/main/java/kr/codesquad/cafe/article/ArticleService.java index 24531ca72..bbbb68b8d 100644 --- a/src/main/java/kr/codesquad/cafe/article/ArticleService.java +++ b/src/main/java/kr/codesquad/cafe/article/ArticleService.java @@ -20,6 +20,10 @@ public void post(Article article) { repository.save(article); } + public void update(Article article) { + repository.save(article); + } + public Article retrieve(long id) { return repository.findOne(id) .orElseThrow(() -> new NoSuchElementException("존재하지 않는 게시물입니다.")); diff --git a/src/main/java/kr/codesquad/cafe/system/MvcConfig.java b/src/main/java/kr/codesquad/cafe/system/MvcConfig.java index b1a9b72d5..3d116955b 100644 --- a/src/main/java/kr/codesquad/cafe/system/MvcConfig.java +++ b/src/main/java/kr/codesquad/cafe/system/MvcConfig.java @@ -2,6 +2,7 @@ import kr.codesquad.cafe.system.intercepter.LoginRequiredInterceptor; import kr.codesquad.cafe.system.intercepter.UserAuthenticationInterceptor; +import kr.codesquad.cafe.system.intercepter.WriterAuthenticationInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; @@ -30,6 +31,9 @@ public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(userAuthenticationInterceptor()) .addPathPatterns("/users/*/form"); + + registry.addInterceptor(writerAuthenticationInterceptor()) + .addPathPatterns("/questions/*/form"); } @Bean @@ -41,4 +45,9 @@ public LoginRequiredInterceptor loginRequiredInterceptor() { public UserAuthenticationInterceptor userAuthenticationInterceptor() { return new UserAuthenticationInterceptor(); } + + @Bean + public WriterAuthenticationInterceptor writerAuthenticationInterceptor() { + return new WriterAuthenticationInterceptor(); + } } diff --git a/src/main/java/kr/codesquad/cafe/system/intercepter/WriterAuthenticationInterceptor.java b/src/main/java/kr/codesquad/cafe/system/intercepter/WriterAuthenticationInterceptor.java new file mode 100644 index 000000000..249920ee3 --- /dev/null +++ b/src/main/java/kr/codesquad/cafe/system/intercepter/WriterAuthenticationInterceptor.java @@ -0,0 +1,23 @@ +package kr.codesquad.cafe.system.intercepter; + +import kr.codesquad.cafe.article.Article; +import kr.codesquad.cafe.user.User; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class WriterAuthenticationInterceptor implements HandlerInterceptor { + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { + Article article = (Article) modelAndView.getModelMap().getAttribute("article"); + String writerUserId = article.getWriterUserId(); + User currentUser = (User) request.getSession().getAttribute("currentUser"); + + if (!currentUser.userIdIs(writerUserId)) { + response.sendRedirect("/badRequest"); + } + } +} diff --git a/src/main/resources/templates/qna/updateForm.html b/src/main/resources/templates/qna/updateForm.html new file mode 100644 index 000000000..0d58224d5 --- /dev/null +++ b/src/main/resources/templates/qna/updateForm.html @@ -0,0 +1,39 @@ + + + + {{> partial/head}} + 게시판 + + + +{{> partial/navbar-top}} +{{> partial/navbar-subnav}} + +
+
+
+ {{#article}} + + +
+ + +
+
+ + +
+ +
+ + {{/article}} +
+
+
+ + +{{> partial/script-references}} + + From 86293629b6bf936d0f2750ff1b25f8a69b3cdbe0 Mon Sep 17 00:00:00 2001 From: Seung-Wan Jeong Date: Fri, 18 Mar 2022 14:23:46 +0900 Subject: [PATCH 06/10] Use MERGE instead of INSERT to save User MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 키(userId)가 존재하면 업데이트하고 존재하지 않으면 새 행을 추가하도록 하였다. --- src/main/java/kr/codesquad/cafe/user/UserRepository.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/kr/codesquad/cafe/user/UserRepository.java b/src/main/java/kr/codesquad/cafe/user/UserRepository.java index 83dd5a5ee..d77b90425 100644 --- a/src/main/java/kr/codesquad/cafe/user/UserRepository.java +++ b/src/main/java/kr/codesquad/cafe/user/UserRepository.java @@ -13,7 +13,8 @@ @Repository public class UserRepository { - private static final String SQL_SAVE_USER = "INSERT INTO CAFE_USER VALUES (?, ?, ?, ?)"; + private static final String SQL_SAVE_USER = + "MERGE INTO CAFE_USER (USERID, PASSWORD, NAME, EMAIL) VALUES (?, ?, ?, ?)"; private static final String SQL_FIND_USER_BY = "SELECT * FROM CAFE_USER WHERE %s = ?"; private static final String SQL_FIND_USER_BY_USERID = String.format(SQL_FIND_USER_BY, "USERID"); private static final String SQL_FIND_USER_BY_NAME = String.format(SQL_FIND_USER_BY, "NAME"); From 4bbc60df2e3588f40c59f8555ea16ba6aabc9700 Mon Sep 17 00:00:00 2001 From: Seung-Wan Jeong Date: Fri, 18 Mar 2022 14:39:47 +0900 Subject: [PATCH 07/10] Fix unintended redirection after login MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 로그인 페이지로 리다이렉트시킨 경로를 저장했다가 로그인 후 저장된 경로로 다시 돌려보내는 로직에서, 저장된 경로가 계속 보존되어 로그아웃했다가 로그인했을 때 해당 경로로 다시 리다이렉트되는 현상을 수정. --- .../kr/codesquad/cafe/user/LoginController.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/kr/codesquad/cafe/user/LoginController.java b/src/main/java/kr/codesquad/cafe/user/LoginController.java index a49bddd5e..f046dbe94 100644 --- a/src/main/java/kr/codesquad/cafe/user/LoginController.java +++ b/src/main/java/kr/codesquad/cafe/user/LoginController.java @@ -28,15 +28,23 @@ public String processLogin(String userId, String password, HttpSession session) try { userService.login(userId, password, session); - return "redirect:" + getDestination(session); + String destination = getDestination(session); + return "redirect:" + destination; } catch (Exception e) { return "users/login_failed"; } } private String getDestination(HttpSession session) { - return (String) Optional.ofNullable(session.getAttribute("destinationAfterLogin")) - .orElse("/"); + String destination = (String) session.getAttribute("destinationAfterLogin"); + + if (destination == null) { + return "/"; + } + + session.removeAttribute("destinationAfterLogin"); + + return destination; } @PostMapping("/logout") From 403d18aa68c320e4dd433fd1779dbf85b898d68d Mon Sep 17 00:00:00 2001 From: Seung-Wan Jeong Date: Fri, 18 Mar 2022 14:57:26 +0900 Subject: [PATCH 08/10] Allow User to delete article posted by self MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 다른 사용자가 작성한 글에 대해 DELETE 요청을 전송하면 에러 페이지로 리다이렉트 --- README.md | 6 +++--- .../cafe/article/ArticleController.java | 19 +++++++++++++++---- .../cafe/article/ArticleRepository.java | 5 +++++ .../cafe/article/ArticleService.java | 4 ++++ src/main/resources/templates/badRequest.html | 2 +- 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 525af7019..335cf62f9 100644 --- a/README.md +++ b/README.md @@ -154,14 +154,14 @@ - [x] 로그인한 사용자만 게시글을 작성할 수 있다. - 게시물의 글쓴이 정보는 사용자 이름(name)을 사용한다. 게시물 작성 양식에서 글쓴이 필드는 제거한다. - 로그인하지 않은 사용자가 게시물 작성 페이지에 접근하면 로그인 페이지로 이동한다. -- [ ] 로그인한 사용자는 자신의 글을 수정할 수 있다. +- [x] 로그인한 사용자는 자신의 글을 수정할 수 있다. - 글 수정 요청은 @PutMapping으로 매핑한다. - 수정하기 폼 과 수정하기 기능은 로그인 사용자와 글쓴이의 사용자 아이디가 같은 경우에만 가능하다. - 상황에 따라 "다른 사람의 글을 수정할 수 없다."와 같은 에러 메시지를 출력하는 페이지로 이동하도록 구현한다. -- [ ] 로그인한 사용자는 자신의 글을 삭제할 수 있다. +- [x] 로그인한 사용자는 자신의 글을 삭제할 수 있다. - 글 삭제 요청은 @DeleteMapping으로 매핑한다. - 삭제하기는 로그인 사용자와 글쓴이의 사용자 아이디가 같은 경우에만 가능하다. - 상황에 따라 "다른 사람의 글을 수정할 수 없다."와 같은 에러 메시지를 출력하는 페이지로 이동하도록 구현한다. -- [ ] 스프링 부트, 웹 MVC로 구현한다. +- [x] 스프링 부트, 웹 MVC로 구현한다. - API로 구현하지 않고, 템플릿 기반으로 구현한다. - HttpSession을 활용해서 구현한다. diff --git a/src/main/java/kr/codesquad/cafe/article/ArticleController.java b/src/main/java/kr/codesquad/cafe/article/ArticleController.java index f1b9f46ff..342cfea9a 100644 --- a/src/main/java/kr/codesquad/cafe/article/ArticleController.java +++ b/src/main/java/kr/codesquad/cafe/article/ArticleController.java @@ -4,10 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpSession; @@ -73,4 +70,18 @@ public String processUpdateForm(@PathVariable("id") long id, ArticleCreationForm return "redirect:/questions/{id}"; } + + @DeleteMapping("/questions/{id}") + public String deleteArticle(@PathVariable("id") long id, HttpSession session) { + User currentUser = (User) session.getAttribute("currentUser"); + String writerUserId = service.retrieve(id).getWriterUserId(); + + if (!currentUser.userIdIs(writerUserId)) { + return "redirect:/badRequest"; + } + + service.deleteById(id); + + return "redirect:/"; + } } diff --git a/src/main/java/kr/codesquad/cafe/article/ArticleRepository.java b/src/main/java/kr/codesquad/cafe/article/ArticleRepository.java index d059bfcdd..87b89c950 100644 --- a/src/main/java/kr/codesquad/cafe/article/ArticleRepository.java +++ b/src/main/java/kr/codesquad/cafe/article/ArticleRepository.java @@ -18,6 +18,7 @@ public class ArticleRepository { "UPDATE ARTICLE SET WRITER_NAME=?, TITLE=?, CONTENTS=? WHERE ID=?"; private static final String SQL_FIND_ARTICLE = "SELECT * FROM ARTICLE WHERE ID = ?"; private static final String SQL_FIND_ARTICLE_ALL = "SELECT * FROM ARTICLE"; + private static final String SQL_DELETE_ARTICLE = "DELETE FROM ARTICLE WHERE ID=?"; private final JdbcTemplate jdbcTemplate; @Autowired @@ -56,4 +57,8 @@ private RowMapper
articleRowMapper() { return article; }); } + + public void deleteById(long id) { + jdbcTemplate.update(SQL_DELETE_ARTICLE, id); + } } diff --git a/src/main/java/kr/codesquad/cafe/article/ArticleService.java b/src/main/java/kr/codesquad/cafe/article/ArticleService.java index bbbb68b8d..b3072cbb7 100644 --- a/src/main/java/kr/codesquad/cafe/article/ArticleService.java +++ b/src/main/java/kr/codesquad/cafe/article/ArticleService.java @@ -32,4 +32,8 @@ public Article retrieve(long id) { public List
retrieveAll() { return repository.findAll(); } + + public void deleteById(long id) { + repository.deleteById(id); + } } diff --git a/src/main/resources/templates/badRequest.html b/src/main/resources/templates/badRequest.html index a40186b2b..c0d57135a 100644 --- a/src/main/resources/templates/badRequest.html +++ b/src/main/resources/templates/badRequest.html @@ -14,7 +14,7 @@
에러 이미지 -

"다른 사람의 정보를 수정하려고 하잖아요."

+

"다른 사람의 권한을 침범하려고 하잖아요."

From fcfdcfbdd05ca248714c5cf8d35cb43697d9df69 Mon Sep 17 00:00:00 2001 From: Seung-Wan Jeong Date: Mon, 28 Mar 2022 23:23:03 +0900 Subject: [PATCH 09/10] [Feedback] Move login/logout logic to LoginController MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * HttpSession 객체를 UserService로 전달하지 않고 LoginController에서 login 및 logout을 처리하도록 변경하였다. --- README.md | 4 ++++ build.gradle | 2 +- .../kr/codesquad/cafe/user/LoginController.java | 8 +++++--- .../java/kr/codesquad/cafe/user/UserService.java | 15 +-------------- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 335cf62f9..117a0c060 100644 --- a/README.md +++ b/README.md @@ -165,3 +165,7 @@ - [x] 스프링 부트, 웹 MVC로 구현한다. - API로 구현하지 않고, 템플릿 기반으로 구현한다. - HttpSession을 활용해서 구현한다. + +### 전 단계 피드백 반영 + +- [x] login 및 logout을 처리할 때 HttpSession 객체를 UserService 계층까지 전달하지 않도록 한다. diff --git a/build.gradle b/build.gradle index 6a97e22ee..0acad6a3c 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id 'java' } -group = 'com.kakao' +group = 'kr.codesquad' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' diff --git a/src/main/java/kr/codesquad/cafe/user/LoginController.java b/src/main/java/kr/codesquad/cafe/user/LoginController.java index f046dbe94..782d4e438 100644 --- a/src/main/java/kr/codesquad/cafe/user/LoginController.java +++ b/src/main/java/kr/codesquad/cafe/user/LoginController.java @@ -6,11 +6,11 @@ import org.springframework.web.bind.annotation.PostMapping; import javax.servlet.http.HttpSession; -import java.util.Optional; @Controller public class LoginController { + private static final String CURRENT_USER = "currentUser"; private final UserService userService; @Autowired @@ -26,7 +26,9 @@ public String viewLogin() { @PostMapping("/login") public String processLogin(String userId, String password, HttpSession session) { try { - userService.login(userId, password, session); + User user = userService.findByUserId(userId); + userService.validatePassword(user, password); + session.setAttribute(CURRENT_USER, user); String destination = getDestination(session); return "redirect:" + destination; @@ -49,7 +51,7 @@ private String getDestination(HttpSession session) { @PostMapping("/logout") public String processLogout(HttpSession session) { - userService.logout(session); + session.removeAttribute(CURRENT_USER); return "redirect:/"; } diff --git a/src/main/java/kr/codesquad/cafe/user/UserService.java b/src/main/java/kr/codesquad/cafe/user/UserService.java index 3508f59b6..0089e6bdb 100644 --- a/src/main/java/kr/codesquad/cafe/user/UserService.java +++ b/src/main/java/kr/codesquad/cafe/user/UserService.java @@ -3,14 +3,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import javax.servlet.http.HttpSession; import java.util.List; import java.util.NoSuchElementException; @Service public class UserService { - private static final String CURRENT_USER = "currentUser"; private final UserRepository repository; @Autowired @@ -35,17 +33,7 @@ public void update(User user, String oldPassword) { repository.save(user); } - public void login(String userId, String password, HttpSession session) { - User user = findByUserId(userId); - validatePassword(user, password); - session.setAttribute(CURRENT_USER, user); - } - - public void logout(HttpSession session) { - session.removeAttribute(CURRENT_USER); - } - - private void validatePassword(User user, String oldPassword) { + public void validatePassword(User user, String oldPassword) { if (findByUserId(user.getUserId()).passwordIs(oldPassword)) { return; } @@ -92,5 +80,4 @@ public User findByUserId(String userId) { return repository.findByUserId(userId) .orElseThrow(() -> new NoSuchElementException("존재하지 않는 회원입니다.")); } - } From 784adfef44f4a58d06fffbfc34a50b425117ecbe Mon Sep 17 00:00:00 2001 From: Seung-Wan Jeong Date: Mon, 28 Mar 2022 23:28:01 +0900 Subject: [PATCH 10/10] [Fix] Enable POST /users request without logging in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 인터셉터가 로그인 상태가 아닐 때 회원가입까지 차단하는 문제를 수정하였다. --- .../cafe/system/intercepter/LoginRequiredInterceptor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/kr/codesquad/cafe/system/intercepter/LoginRequiredInterceptor.java b/src/main/java/kr/codesquad/cafe/system/intercepter/LoginRequiredInterceptor.java index 42f776969..b050922a5 100644 --- a/src/main/java/kr/codesquad/cafe/system/intercepter/LoginRequiredInterceptor.java +++ b/src/main/java/kr/codesquad/cafe/system/intercepter/LoginRequiredInterceptor.java @@ -13,6 +13,10 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons HttpSession session = request.getSession(); if (session.getAttribute("currentUser") == null) { + if ("POST".equals(request.getMethod())) { + return true; + } + session.setAttribute("destinationAfterLogin", request.getRequestURI()); response.sendRedirect("/login");