{"id":19815253,"url":"https://github.com/dev-connor/spring-notice","last_synced_at":"2026-05-16T22:02:35.181Z","repository":{"id":108945037,"uuid":"457648077","full_name":"dev-connor/Spring-Notice","owner":"dev-connor","description":"스프링으로 로그인, 회원가입, 게시판의 CRUD 를 구현했습니다.","archived":false,"fork":false,"pushed_at":"2022-03-06T14:32:23.000Z","size":215,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-28T19:27:09.636Z","etag":null,"topics":["spring"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dev-connor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-02-10T05:50:04.000Z","updated_at":"2022-02-18T03:40:41.000Z","dependencies_parsed_at":null,"dependency_job_id":"96e48b2d-e703-4d11-9178-afd30ce358a3","html_url":"https://github.com/dev-connor/Spring-Notice","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dev-connor/Spring-Notice","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-connor%2FSpring-Notice","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-connor%2FSpring-Notice/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-connor%2FSpring-Notice/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-connor%2FSpring-Notice/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dev-connor","download_url":"https://codeload.github.com/dev-connor/Spring-Notice/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-connor%2FSpring-Notice/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269983025,"owners_count":24507510,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-11T02:00:10.019Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["spring"],"created_at":"2024-11-12T10:05:19.496Z","updated_at":"2026-05-16T22:02:30.160Z","avatar_url":"https://github.com/dev-connor.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 게시판 프로젝트\n\n주제: 게시판\n\n툴: Spring framework, STS, Oracle, SQL Developer, Git\u0026GitHub\n\n개발인원: 개인\n\n개발기간: 2022년 2월 10일 ~ 2022년 2월 16일 (일주일)\n\n\n1. 컨트롤러, 서비스, 뷰의 MVC 패턴 구현\n2. MyBatis 를 활용한 DB 연결\n3. Spring security 를 활용한 로그인\n4. 시큐리티의 BCryptPasswordEncoder 를 활용한 비밀번호 암호화\n5. 게시판의 CRUD\n6. 게시판의 페이징블록 처리\n\n\n---\n\n### 목차\n1. 환경설정\n    - JDBC\n3. 회원가입\n    - 비밀번호 암호화 처리\n4. 글 목록\n5. 글 작성 \u0026 로그인\n    - Spring security 의 CSRF\n    - 로그인 권한처리\n    - 로그인상태에 따른 동적 화면구현\n6. 글 수정 \u0026 글 삭제\n    - 권한에 따른 수정/삭제\n7. 페이징처리\n\n### 적용된 다른 기능\n\n\n1. 로그인과 DB 연결\n9. 로그아웃 기능\n\n---\n\n### 1. 환경설정\n\n#### JDBC\n\n이번 프로젝트는 mybatis 의 커넥션 풀의 한 종류인 Hikari CP 를 사용합니다.\n\n\u003cbr\u003e\n\n**WEB-INF/spring/root-context.xml**\n\n```xml\n\t\u003cbean id=\"hikariConfig\" class=\"com.zaxxer.hikari.HikariConfig\"\u003e\n\t    \u003cproperty name=\"driverClassName\" value=\"net.sf.log4jdbc.sql.jdbcapi.DriverSpy\"\u003e\u003c/property\u003e\n\t    \u003cproperty name=\"jdbcUrl\" value=\"jdbc:log4jdbc:oracle:thin:@localhost:1521:xe\"\u003e\u003c/property\u003e\n\t    \u003cproperty name=\"username\" value=\"board\"\u003e\u003c/property\u003e\n\t    \u003cproperty name=\"password\" value=\"tiger\"\u003e\u003c/property\u003e\n\t\u003c/bean\u003e\n\t\u003cbean id=\"dataSource\" class=\"com.zaxxer.hikari.HikariDataSource\" destroy-method=\"close\"\u003e\n\t    \u003cconstructor-arg ref=\"hikariConfig\"\u003e\u003c/constructor-arg\u003e\n\t\u003c/bean\u003e\t\n\t\u003cbean id=\"sqlSessionFactory\" class=\"org.mybatis.spring.SqlSessionFactoryBean\"\u003e\n\t    \u003cproperty name=\"dataSource\" ref=\"dataSource\"\u003e\u003c/property\u003e\n\t    \u003cproperty name=\"typeAliases\"\u003e\n\t        \u003clist\u003e \n\t            \u003cvalue\u003eio.github.dev_connor.domain.BoardVO\u003c/value\u003e\n\t            \u003cvalue\u003eio.github.dev_connor.domain.MemberVO\u003c/value\u003e\n\t            \u003cvalue\u003eio.github.dev_connor.domain.AuthVO\u003c/value\u003e\n\t        \u003c/list\u003e\n\t    \u003c/property\u003e\t\t    \n\t\u003c/bean\u003e\n\t\u003cmybatis-spring:scan base-package=\"io.github.dev_connor.mapper\"/\u003e\t\n```\n\n### 2. 회원가입\n\n\u003cimg width=\"750\" alt=\"회원가입\" src=\"https://user-images.githubusercontent.com/70655507/154285678-6a0d63c2-87a7-4144-92a1-851d1084f5d6.PNG\"\u003e\n\n\u003e 아이디에 ryan, 비밀번호에 aa1234 로 회원가입을 합니다.\n\u003cbr\u003e\n\n\u003cimg width=\"750\" alt=\"암호화\" src=\"https://user-images.githubusercontent.com/70655507/154287334-3076ed3e-8f7b-42ec-9286-3598defcf03d.PNG\"\u003e\n\n\u003e 비밀번호가 암호화되어 DB 에 저장됩니다.\n\u003cbr\u003e\n\n**WEB-INF/spring/security-context.xml**\n```java\n\u003cbean id=\"bcryptPasswordEncoder\" class=\"org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder\"\u003e\u003c/bean\u003e\n\n\u003csecurity:authentication-manager\u003e \n\t\u003csecurity:authentication-provider user-service-ref=\"customUserDetailsService\"\u003e\n\t\t\u003csecurity:password-encoder ref=\"bcryptPasswordEncoder\"/\u003e\n\t\u003c/security:authentication-provider\u003e\n\u003c/security:authentication-manager\u003e\n```\n\n`BCryptPasswordEncoder` 스프링프레임워크의 패스워드 인코더를 사용합니다.\n\n\u003cbr\u003e\n\n**컨트롤러**\n\n```java\n@Controller\n@Log4j\n@AllArgsConstructor\npublic class MemberController {\n\tprivate static final Logger logger = LoggerFactory.getLogger(BoardController.class);\n\tprivate MemberService service;\n\n\t@GetMapping(\"/join\")\n\tpublic void joinGET() {\n\t}\n\t\n\t@PostMapping(\"/join\")\n\tpublic String joinPost(MemberVO member) {\n\t\tservice.join(member);\n\t\treturn \"redirect:/board/list\";\n\t}\n}\n```\n\u003cbr\u003e\n\n**서비스**\n\n```java\n@Log4j\n@Service\n@AllArgsConstructor\npublic class MemberServiceImpl implements MemberService {\n\n\t@Setter(onMethod_ = @Autowired)\n\tprivate MemberMapper mapper;\n\t\n\t@Setter(onMethod_ = @Autowired)\n\tprivate PasswordEncoder pwencoder;\n\n\t@Override\n\tpublic void join(MemberVO member) {\n\t\tString encodedPW = pwencoder.encode(member.getUserpw());\n\t\tmember.setUserpw(encodedPW);\n\t\tmapper.join(member);\n\t}\n}\n```\n\n`pwencoder.encode` 비밀번호를 암호화합니다.\n\n\n### 3. 글 목록\n\u003cimg width=\"750\" alt=\"DB\" src=\"https://user-images.githubusercontent.com/70655507/154282823-82103c36-9517-4951-919b-292de6ca30fa.PNG\"\u003e\n\u003cimg width=\"750\" alt=\"글목록\" src=\"https://user-images.githubusercontent.com/70655507/154280388-1352b509-7277-48cb-9d4b-1a59d42e953a.PNG\"\u003e\n\u003cbr\u003e\n\n**컨트롤러**\n\n```java\n@Controller\n@Log4j\n@RequestMapping(\"/board/*\")\n@AllArgsConstructor\npublic class BoardController {\n\tprivate static final Logger logger = LoggerFactory.getLogger(BoardController.class);\n\tprivate BoardService service;\n\n\t@GetMapping(\"/list\")\n\tpublic void list(Criteria cri, Model model) {\n\t\tint total = service.getTotal(cri);\n\t\t\n\t\tmodel.addAttribute(\"list\", service.getList(cri));\n\t\tmodel.addAttribute(\"pageMaker\", new PageDTO(cri, total));\n\t}\n}  \n```\n\n\u003cbr\u003e\n\n**서비스**\n\n```java\n@Log4j\n@Service\n@AllArgsConstructor\npublic class BoardServiceImpl implements BoardService {\n\n\t@Override\n\tpublic List\u003cBoardVO\u003e getList(Criteria cri) {\n\t\treturn mapper.getListWithPaging(cri);\n\t}\n}\n```\n\n\u003cbr\u003e\n\n**MyBatis**\n\n```xml\n\u003cselect id=\"getListWithPaging\" resultType=\"BoardVO\"\u003e \n\u003c![CDATA[ \n  SELECT bno, title, content, writer, regdate, updatedate  \n  FROM (  \n    SELECT /*+INDEX_DESC(tbl_board pk_board) */  \n      rownum rn, bno, title, content, writer, regdate, updatedate  \n    FROM tbl_board  \n    WHERE rownum \u003c= #{pageNum} * #{amount}  \n    )  \n  WHERE rn \u003e (#{pageNum} -1) * #{amount}  \n]]\u003e  \n\u003c/select\u003e\n```\n\nINDEX 로 정렬하는 작업 없이 글목록을 불러옵니다.\n\n### 4. 글 작성 \u0026 로그인\n\u003cimg width=\"750\" alt=\"로그인\" src=\"https://user-images.githubusercontent.com/70655507/154280439-58771c56-d740-419e-8f4b-b092d478125c.PNG\"\u003e\n\n\u003e 로그인이 되어있지 않다면 글쓰기버튼을 클릭 시 로그인페이지로 연결됩니다.\n\u003cbr\u003e\n\n#### - 글 작성 시 로그인 페이지로\n\n\u003cbr\u003e\n\n**WEB-INF/spring/appServlet/servlet-context.xml**\n\n```xml\n\u003csecurity:global-method-security pre-post-annotations=\"enabled\" secured-annotations=\"enabled\"/\u003e\n```\n\n**컨트롤러**\n\n```java\n@GetMapping(\"/register\")\n@PreAuthorize(\"isAuthenticated()\")\npublic void register() {}\n\n@PostMapping(\"/register\")\n@PreAuthorize(\"isAuthenticated()\")\npublic String register(BoardVO board, RedirectAttributes rttr) {\n\tservice.register(board);\n\trttr.addFlashAttribute(\"result\", board.getBno());\n\treturn \"redirect:/board/list\";\n}\n```\n\n`@PreAuthorize(\"isAuthenticated()\")` 어노테이션으로 로그인한 사용자만 글작성을 하도록 합니다.\n\n\u003cbr\u003e\n\n#### - 로그인 CSRF\n\n```jsp\n\u003cform method='post' action=\"/login\"\u003e\n\t\u003cinput type=\"hidden\" name=\"${_csrf.parameterName}\" value=\"${_csrf.token}\" /\u003e\n\n\t\u003cdiv\u003e\u003cinput type='text' name='username' autofocus\u003e\u003c/div\u003e\n\t\u003cdiv\u003e\u003cinput type='password' name='password'\u003e\u003c/div\u003e\n\t\u003cdiv\u003e\u003cinput type='checkbox' name='remember-me'\u003e Remember Me\u003c/div\u003e\n\n\t\u003cdiv\u003e\u003cinput type='submit'\u003e\u003c/div\u003e\n\u003c/form\u003e\n```\n\n\u003e Spring security 에서는 모든 POST 방식의 form 태그에 CSRF 토큰이 필요합니다.\n\n\u003cimg width=\"750\" alt=\"글작성\" src=\"https://user-images.githubusercontent.com/70655507/154280449-79be4a67-2fb5-4ec0-80cb-1f96240eb7ef.PNG\"\u003e\n\n\u003e 아까 가입한 ryan/aa1234 로 로그인합니다. \u003cbr\u003e\n\u003e 암호화된 비밀번호는 디코드되어 비밀번호가 일치하는지 확인한 후 로그인됩니다. \u003cbr\u003e\n\u003e 로그인 후에는 바로 글작성페이지로 이동됩니다.\n\u003cbr\u003e\n\n**MyBatis**\n\n```xml\n\u003cinsert id=\"insertSelectKey\"\u003e\n\u003cselectKey keyProperty=\"bno\" order=\"BEFORE\" resultType=\"long\"\u003e\n select seq_board.nextval from dual\n\u003c/selectKey\u003e\ninsert into tbl_board (bno,title,content, writer)\nvalues (#{bno}, #{title}, #{content}, #{writer})\n\u003c/insert\u003e    \n```\n\n\u003cimg width=\"750\" alt=\"글작성_완료\" src=\"https://user-images.githubusercontent.com/70655507/154280458-4dfe496c-5013-4cf5-9ec6-3226a4f67bdb.PNG\"\u003e\n\u003cimg width=\"750\" alt=\"글작성_완료2\" src=\"https://user-images.githubusercontent.com/70655507/154280468-403cfaf6-c44d-47c3-8056-9a5aab89dcc9.PNG\"\u003e\n\n\u003e 글 작성이 정상적으로 된 것을 확인할 수 있습니다. \u003cbr\u003e\n\u003e 로그인 후에는 '로그인', '회원가입' 버튼이 '로그아웃' 버튼으로 바뀐 것을 볼 수 있습니다.\n\u003cbr\u003e\n\n### 5. 글 수정 \u0026 글 삭제\n\u003cimg width=\"750\" alt=\"글상세_타인\" src=\"https://user-images.githubusercontent.com/70655507/154280602-9d202877-f7bf-4f6e-b72e-4d9327bbed62.PNG\"\u003e\n\n\u003e 다른사람의 글은 수정, 삭제할 수 없습니다.\n\u003cbr\u003e\n\n\u003cimg width=\"750\" alt=\"글상세\" src=\"https://user-images.githubusercontent.com/70655507/154280671-80690767-686c-4546-ba39-3f16ba8a1925.PNG\"\u003e\n\n\u003e 자신의 글에는 수정, 삭제버튼이 생긴 것을 볼 수 있습니다.\n\u003cbr\u003e\n\n**글상세.jsp**\n\n```jsp\n\u003csec:authentication property=\"principal\" var=\"p\"/\u003e\n\u003csec:authorize access=\"isAuthenticated()\"\u003e\n\t\u003cc:if test=\"${p.username eq board.writer }\"\u003e\n\t\t\u003cbutton data-oper='modify' class=\"btn btn-default\"\u003e\n\t\t\t\u003ca href=\"/board/modify?bno=\u003cc:out value='${board.bno}'/\u003e\"\u003e수정\u003c/a\u003e\n\t\t\u003c/button\u003e\t\t\t\n\t\t\u003cform role=\"form\" action=\"/board/remove?bno=${board.bno }\" method=\"post\"\u003e\n\t\t\t\u003cinput type=\"hidden\" name=\"${_csrf.parameterName}\" value=\"${_csrf.token}\" /\u003e\n\t\t\t\u003cbutton type=\"submit\" data-oper='remove' class=\"btn btn-danger\"\u003e삭제\u003c/button\u003e\n\t\t\u003c/form\u003e\n\t\u003c/c:if\u003e\n\u003c/sec:authorize\u003e\n```\n\n\u003cimg width=\"750\" alt=\"글수정\" src=\"https://user-images.githubusercontent.com/70655507/154280701-5d7d7d53-43f9-4fb9-be8b-c312cbbfda1b.PNG\"\u003e\n\u003cimg width=\"750\" alt=\"글수정_완료\" src=\"https://user-images.githubusercontent.com/70655507/154280706-140540ab-4c89-4a6d-8d0c-d52ff8305dcd.PNG\"\u003e\n\n\u003e 글 수정이 완료된 후에는 글상세페이지로 이동됩니다.\n\u003cbr\u003e\n\n\u003cimg width=\"750\" alt=\"글삭제\" src=\"https://user-images.githubusercontent.com/70655507/154280806-18761a56-e675-4d01-9a12-424d2643adff.PNG\"\u003e\n\n\u003e 글삭제가 정상적으로 된 것을 볼 수 있습니다.\n\u003cbr\u003e\n\n### 6. 페이징처리\n\u003cimg width=\"750\" alt=\"글작성_완료2\" src=\"https://user-images.githubusercontent.com/70655507/154280853-cbd6e8a7-aa44-4227-ab3b-5a268244ef12.PNG\"\u003e\n\u003cimg width=\"750\" alt=\"페이징처리_중간\" src=\"https://user-images.githubusercontent.com/70655507/154280867-d8d24300-951b-4c11-b59c-5a6ddf34d154.PNG\"\u003e\n\n\u003e 페이지의 중간에서는 이전버튼 (Previous) 과 다음버튼 (Next) 가 모두 보이는 것을 볼 수 있습니다.\n\u003cbr\u003e\n\n\u003cimg width=\"750\" alt=\"페이징처리_끝\" src=\"https://user-images.githubusercontent.com/70655507/154280861-f784ab9c-5bab-4e47-9393-3004525c6133.PNG\"\u003e\n\n\u003e 반면에 처음과 마지막페이지에서는 이전버튼이나 다음버튼이 없도록 처리했습니다.\n\u003cbr\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdev-connor%2Fspring-notice","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdev-connor%2Fspring-notice","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdev-connor%2Fspring-notice/lists"}