├── README.md ├── sp-board ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── sp │ │ └── sec │ │ ├── board │ │ ├── controller │ │ │ └── BoardController.java │ │ ├── domain │ │ │ ├── Comment.java │ │ │ ├── SpBoard.java │ │ │ └── SpBoardSummary.java │ │ ├── repository │ │ │ └── SpBoardRepository.java │ │ └── service │ │ │ └── SpBoardService.java │ │ └── config │ │ └── SpBoardModule.java │ └── test │ └── java │ └── com │ └── sp │ └── sec │ └── board │ ├── SpBoardTestApplication.java │ └── service │ ├── SpBoardServiceTest.java │ └── SpBoardTestHelper.java ├── sp-jwt-security ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sp │ │ │ └── sec │ │ │ ├── config │ │ │ └── SpJwtSecurityModule.java │ │ │ └── web │ │ │ └── config │ │ │ ├── JWTCheckFilter.java │ │ │ ├── JWTLoginFilter.java │ │ │ ├── JWTUtil.java │ │ │ ├── RefreshableJWTLoginFilter.java │ │ │ ├── SpJwtProperties.java │ │ │ ├── UserLogin.java │ │ │ └── VerifyResult.java │ └── resources │ │ └── META-INF │ │ └── spring-configuration-metadata.json │ └── test │ └── java │ └── com │ └── sp │ └── sec │ └── web │ ├── SpIntegrationTest.java │ ├── SpJwtRefreshableTwoUserIntegrationTest.java │ ├── SpJwtTwoUserIntegrationTest.java │ ├── SpJwtUserAdminIntegrationTest.java │ ├── SpRefreshableIntegrationTest.java │ └── Tokens.java ├── sp-web-util ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── sp │ └── sec │ └── web │ └── util │ └── RestResponsePage.java ├── user-authority ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── sp │ │ └── sec │ │ ├── config │ │ └── UserAuthorityModule.java │ │ └── user │ │ ├── controller │ │ └── UserController.java │ │ ├── domain │ │ ├── Authority.java │ │ └── User.java │ │ ├── repository │ │ └── UserRepository.java │ │ └── service │ │ └── UserService.java │ └── test │ ├── java │ └── com │ │ └── sp │ │ └── sec │ │ └── user │ │ ├── UserAuthorityJpaTest.java │ │ ├── UserAuthorityTestApplication.java │ │ ├── UserTestHelper.java │ │ └── WithUserTest.java │ └── resources │ └── applicaiton-test.yml ├── user-oauth2-support ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── sp │ │ └── sec │ │ ├── config │ │ └── UserOAuth2SupportModule.java │ │ └── user │ │ └── oauth2 │ │ ├── domain │ │ ├── ExtendedUser.java │ │ └── ProvidedOAuth2User.java │ │ ├── repository │ │ ├── ExtendedUserRepository.java │ │ └── ProvidedOAuth2UserRepository.java │ │ └── service │ │ ├── ExtendedUserService.java │ │ └── ProvidedOAuth2UserService.java │ └── test │ └── java │ └── com │ └── sp │ └── sec │ └── user │ └── oauth2 │ ├── ExtendedUserServiceTest.java │ ├── ExtendedUserTestHelper.java │ ├── FacebookUserLoginTest.java │ ├── GoogleUserLoginTest.java │ ├── KakaoUserLoginTest.java │ ├── NaverUserLoginTest.java │ ├── OAuth2UserSample.java │ ├── UserOAuth2SupportApp.java │ └── WithExtendedUserTest.java └── web ├── auth-server-1 ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sp │ │ │ └── sec │ │ │ └── web │ │ │ ├── AuthServer1Application.java │ │ │ └── config │ │ │ ├── DBInit.java │ │ │ └── SecurityConfig.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── sp │ └── sec │ └── web │ └── AuthServer1ApplicationTests.java ├── google-client-4 ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sp │ │ │ └── sec │ │ │ └── web │ │ │ ├── GoogleClient4Application.java │ │ │ ├── config │ │ │ ├── SecurityConfig.java │ │ │ ├── SpGoogleUser.java │ │ │ └── SpGoogleUserToMyUserFilter.java │ │ │ └── controller │ │ │ ├── HomeController.java │ │ │ └── SecuredService.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── sp │ └── sec │ └── web │ └── GoogleClient4ApplicationTests.java ├── jwt-refresh-token-test ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sp │ │ │ └── sec │ │ │ └── web │ │ │ ├── JwtRefreshTokenTestApplication.java │ │ │ └── config │ │ │ └── SecurityConfig.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── sp │ └── sec │ └── web │ └── RefreshTokenTest.java ├── jwt-user-web ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sp │ │ │ └── sec │ │ │ └── web │ │ │ ├── JwtUserWebApplication.java │ │ │ └── config │ │ │ └── SecurityConfig.java │ └── resources │ │ ├── application-test.yml │ │ └── application.yml │ └── test │ └── java │ └── com │ └── sp │ └── sec │ └── web │ ├── JWTLoginFilterTest.java │ ├── JWTTokenTest.java │ ├── JwtUserWebApplicationTests.java │ └── UserControllerIntegrationTest.java ├── oauth2-client-test ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sp │ │ │ └── sec │ │ │ └── web │ │ │ ├── Oauth2ClientTestApplication.java │ │ │ ├── config │ │ │ ├── SecurityConfig.java │ │ │ └── SpOidcUserToSiteUserFilter.java │ │ │ ├── controller │ │ │ └── HomeController.java │ │ │ └── service │ │ │ ├── SpOAuth2UserService.java │ │ │ └── SpOidcUserService.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── sp │ └── sec │ └── web │ └── Oauth2ClientTestApplicationTests.java ├── resource-server-1 ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sp │ │ │ └── sec │ │ │ └── web │ │ │ ├── ResourceServer1Application.java │ │ │ └── config │ │ │ └── SecurityConfig.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── sp │ └── sec │ └── web │ ├── AuthResourceTest.java │ └── ResourceServer1ApplicationTests.java ├── security-basic ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sp │ │ │ └── sec │ │ │ ├── basic │ │ │ ├── HomeController.java │ │ │ ├── SecurityBasicApplication.java │ │ │ ├── SecurityMessage.java │ │ │ ├── TestController.java │ │ │ └── config │ │ │ │ └── SecurityConfig.java │ │ │ └── user │ │ │ └── UserAuthorityApplication.java │ └── resources │ │ ├── application.properties │ │ ├── application.yml │ │ ├── static │ │ ├── login.css │ │ └── style.css │ │ └── templates │ │ ├── index.html │ │ └── loginForm.html │ └── test │ └── java │ └── com │ └── sp │ └── sec │ ├── basic │ ├── SecurityBasicApplicationTests.java │ └── UserAccessTest.java │ └── user │ └── UserAuthorityApplicationTests.java ├── sp-board-web ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sp │ │ │ └── sec │ │ │ └── web │ │ │ ├── SpBoardWebApplication.java │ │ │ └── config │ │ │ └── SecurityConfig.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── sp │ └── sec │ └── web │ ├── SpBoardWebApplicationTests.java │ └── board │ ├── BoardControllerCommentIntegrationTest.java │ └── BoardControllerIntegrationTest.java └── user-authority-test-web ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── sp │ │ └── sec │ │ └── web │ │ ├── UserAuthorityTestWebApplication.java │ │ ├── config │ │ ├── DBInit.java │ │ ├── MongoConfig.java │ │ └── SecurityConfig.java │ │ └── controller │ │ ├── HomeController.java │ │ ├── SecurityMessage.java │ │ └── TestController.java └── resources │ ├── application.yml │ ├── static │ ├── login.css │ └── style.css │ └── templates │ ├── index.html │ └── loginForm.html └── test └── java └── com └── sp └── sec └── web ├── UserAccessTest.java ├── UserAuthorityTestWebApplicationTests.java └── UserControllerTest.java /README.md: -------------------------------------------------------------------------------- 1 | # 스프링 Security를 유닛테스트 하라! (Do Unit Test Spring Security!) 2 | 3 | 4 | 이 프로젝트는 스프링 Security 의 주요 기능을 유닛테스트 하기 위해 만든 샘플 프로젝트 입니다. 5 | 이 프로젝트의 코드를 만드는 과정은 Youtube 에서 시리즈로 제공하고 있습니다. 6 | 7 | 이 과정을 만든 목적은 다음과 같습니다. 8 | * 유닛테스트를 통해 스프링 부트 시큐리티를 이해한다. 9 | * 모듈 중심 설계 10 | * MSA 시스템 구성 하기 좋은 설계 11 | * OAuth2 시스템 구성과 설계 12 | 13 | ## Episode1 : DB 없이 테스트 하기 14 | 15 | 영상 : https://www.youtube.com/watch?v=MNEgiFeUy_U 16 | 17 | 18 | ## Episode2 : User 모듈 만들고 테스트 하기 19 | 20 | * 도메인, 서비스 구현/테스트 : https://www.youtube.com/watch?v=WcF95nNbh7o 21 | * 컨트롤러 구현/테스트 : https://www.youtube.com/watch?v=2ljhDwMdTP8 22 | 23 | ## Episode3 : JWT 토큰 테스트 24 | 25 | * JWT 토큰 구현/테스트 : https://www.youtube.com/watch?v=w8wY2x5ezyU 26 | * 소스 정리 : https://www.youtube.com/watch?v=ctEUUScmH1I 27 | 28 | ## Episode4 : 게시판 모듈 만들고 테스트 하기 29 | 30 | * 도메인, 서비스 구현/테스트 : https://www.youtube.com/watch?v=0cNzhs405Ug 31 | * 컨트롤러 구현/테스트 : https://www.youtube.com/watch?v=grrR-wdNVgU 32 | 33 | 34 | ## Episode5 : Refresh Token 방식 테스트 35 | 36 | * Refresh Token 구현/테스트 : https://www.youtube.com/watch?v=4an8SrfvXSo 37 | * 번외편 : https://www.youtube.com/watch?v=NCglUWoXqm4 38 | 39 | ## Episode6 : OAuth2 인증 40 | 41 | * 인증서버 토큰으로 MSA 구현 : 42 | -------------------------------------------------------------------------------- /sp-board/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | spring-boot-dependencies 8 | org.springframework.boot 9 | 2.4.0 10 | 11 | com.sp.sec 12 | sp-board 13 | 1.0.0 14 | 15 | 16 | 11 17 | ${java.version} 18 | ${java.version} 19 | UTF-8 20 | UTF-8 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-data-mongodb 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-security 31 | 32 | 33 | 34 | org.projectlombok 35 | lombok 36 | true 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-test 41 | test 42 | 43 | 44 | org.junit.vintage 45 | junit-vintage-engine 46 | 47 | 48 | 49 | 50 | de.flapdoodle.embed 51 | de.flapdoodle.embed.mongo 52 | test 53 | 54 | 55 | 56 | org.springframework.security 57 | spring-security-test 58 | test 59 | 60 | 61 | com.fasterxml.jackson.core 62 | jackson-annotations 63 | 64 | 65 | 66 | 67 | com.sp.sec 68 | user-authority 69 | 1.0.0 70 | 71 | 72 | 73 | com.sp.sec 74 | user-authority 75 | 1.0.0 76 | test-jar 77 | test 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-jar-plugin 87 | 88 | 89 | 90 | test-jar 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /sp-board/src/main/java/com/sp/sec/board/controller/BoardController.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.board.controller; 2 | 3 | import com.sp.sec.board.domain.Comment; 4 | import com.sp.sec.board.domain.SpBoard; 5 | import com.sp.sec.board.domain.SpBoardSummary; 6 | import com.sp.sec.board.service.SpBoardService; 7 | import com.sp.sec.user.domain.User; 8 | import com.sp.sec.web.util.RestResponsePage; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.security.access.AccessDeniedException; 11 | import org.springframework.security.access.prepost.PreAuthorize; 12 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 13 | import org.springframework.util.StringUtils; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | import java.util.Optional; 17 | 18 | @RestController 19 | @RequestMapping("/board") 20 | @RequiredArgsConstructor 21 | public class BoardController { 22 | 23 | private final SpBoardService boardService; 24 | 25 | @PreAuthorize("isAuthenticated() and (#board.boardId == null or #board.writerId == #user.userId)") 26 | @PostMapping("/save") 27 | public SpBoard save( 28 | @RequestBody SpBoard board, 29 | @AuthenticationPrincipal User user 30 | ){ 31 | if(StringUtils.isEmpty(board.getBoardId())){ 32 | board.setWriterId(user.getUserId()); 33 | } 34 | return boardService.save(board); 35 | } 36 | 37 | @PreAuthorize("isAuthenticated()") 38 | @GetMapping("/list") 39 | public RestResponsePage list( 40 | @RequestParam(defaultValue = "1") Integer pageNum, 41 | @RequestParam(defaultValue = "10") Integer size 42 | ){ 43 | return RestResponsePage.of(boardService.list(pageNum, size)); 44 | } 45 | 46 | @PreAuthorize("isAuthenticated()") 47 | @GetMapping("/{boardId}") 48 | public Optional getBoard(@PathVariable String boardId){ 49 | return boardService.findBoard(boardId); 50 | } 51 | 52 | @PreAuthorize("isAuthenticated()") 53 | @DeleteMapping("/{boardId}") 54 | public Optional remove( 55 | @PathVariable String boardId, 56 | @AuthenticationPrincipal User user 57 | ){ 58 | return boardService.findBoard(boardId).map(board->{ 59 | if(board.getWriterId().equals(user.getUserId())){ 60 | boardService.removeBoard(boardId); 61 | }else{ 62 | throw new AccessDeniedException("게시자만 삭제할 수 있습니다."); 63 | } 64 | return board; 65 | }); 66 | } 67 | 68 | @PreAuthorize("isAuthenticated()") 69 | @PutMapping("/{boardId}/comment") 70 | public Comment addComment( 71 | @PathVariable String boardId, 72 | @RequestBody String comment, 73 | @AuthenticationPrincipal User user 74 | ){ 75 | return boardService.addComment(boardId, Comment.builder() 76 | .userId(user.getUserId()) 77 | .userName(user.getName()) 78 | .comment(comment) 79 | .build()); 80 | } 81 | 82 | @PreAuthorize("isAuthenticated()") 83 | @DeleteMapping("/{boardId}/comment/{commentId}") 84 | public Optional removeComment( 85 | @PathVariable String boardId, 86 | @PathVariable String commentId, 87 | @AuthenticationPrincipal User user 88 | ){ 89 | return boardService.findBoard(boardId).map(board->{ 90 | if(board.getCommentList() == null) return false; 91 | Optional comment = board.getCommentList().stream().filter(c->c.getCommentId().equals(commentId)).findFirst(); 92 | if(comment.isPresent()){ 93 | if(comment.get().getUserId().equals(user.getUserId())){ 94 | boardService.removeComment(boardId, commentId); 95 | return true; 96 | }else{ 97 | throw new AccessDeniedException("댓글을 생성한 사람만 삭제할 수 있습니다."); 98 | } 99 | } 100 | return false; 101 | }); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /sp-board/src/main/java/com/sp/sec/board/domain/Comment.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.board.domain; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.time.LocalDateTime; 10 | 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Builder 15 | public class Comment { 16 | 17 | private String commentId; 18 | 19 | private String comment; 20 | private String userId; 21 | private String userName; 22 | 23 | private LocalDateTime created; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /sp-board/src/main/java/com/sp/sec/board/domain/SpBoard.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.board.domain; 2 | 3 | 4 | import com.sp.sec.user.domain.User; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.springframework.data.annotation.Id; 10 | import org.springframework.data.annotation.Transient; 11 | import org.springframework.data.mongodb.core.mapping.Document; 12 | 13 | import java.time.LocalDateTime; 14 | import java.util.List; 15 | 16 | @Document(collection = "sp_board") 17 | @Data 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @Builder 21 | public class SpBoard { 22 | 23 | @Id 24 | private String boardId; 25 | 26 | private String title; 27 | private String content; 28 | 29 | private String writerId; 30 | 31 | @Transient 32 | private User writer; 33 | 34 | private List commentList; 35 | 36 | private LocalDateTime created; 37 | private LocalDateTime updated; 38 | 39 | private boolean open; // ready, open 40 | 41 | } 42 | -------------------------------------------------------------------------------- /sp-board/src/main/java/com/sp/sec/board/domain/SpBoardSummary.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.board.domain; 2 | 3 | 4 | import com.sp.sec.user.domain.User; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.springframework.data.annotation.Transient; 10 | 11 | import java.time.LocalDateTime; 12 | import java.util.List; 13 | 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | @Builder 18 | public class SpBoardSummary { 19 | 20 | private String boardId; 21 | private String title; 22 | private String writerId; 23 | 24 | @Transient 25 | private User writer; 26 | 27 | private int commentCount; 28 | 29 | private LocalDateTime created; 30 | private LocalDateTime updated; 31 | 32 | public static SpBoardSummary of(SpBoard board, User writer){ 33 | return SpBoardSummary.builder() 34 | .boardId(board.getBoardId()) 35 | .title(board.getTitle()) 36 | .writerId(board.getWriterId()) 37 | .writer(writer) 38 | .commentCount(board.getCommentList() == null ? 0 : board.getCommentList().size()) 39 | .created(board.getCreated()) 40 | .updated(board.getUpdated()) 41 | .build(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /sp-board/src/main/java/com/sp/sec/board/repository/SpBoardRepository.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.board.repository; 2 | 3 | import com.sp.sec.board.domain.SpBoard; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.mongodb.repository.MongoRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | @Repository 10 | public interface SpBoardRepository extends MongoRepository { 11 | 12 | Page findAllByOpenOrderByCreatedDesc(boolean open, Pageable pageable); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /sp-board/src/main/java/com/sp/sec/config/SpBoardModule.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 6 | 7 | @Configuration 8 | @ComponentScan("com.sp.sec.board") 9 | @EnableMongoRepositories(basePackages = { 10 | "com.sp.sec.board.repository" 11 | }) 12 | public class SpBoardModule { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /sp-board/src/test/java/com/sp/sec/board/SpBoardTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.board; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 8 | 9 | @SpringBootApplication 10 | public class SpBoardTestApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(SpBoardTestApplication.class, args); 14 | } 15 | 16 | @Profile("board-test") 17 | @Configuration 18 | @EnableMongoRepositories(basePackages = { 19 | "com.sp.sec.user.repository", 20 | "com.sp.sec.board.repository" 21 | }) 22 | class MongoConfig{ 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /sp-board/src/test/java/com/sp/sec/board/service/SpBoardTestHelper.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.board.service; 2 | 3 | import com.sp.sec.board.domain.Comment; 4 | import com.sp.sec.board.domain.SpBoard; 5 | import com.sp.sec.board.domain.SpBoardSummary; 6 | import com.sp.sec.user.domain.User; 7 | import lombok.AllArgsConstructor; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.assertNotNull; 11 | 12 | @AllArgsConstructor 13 | public class SpBoardTestHelper { 14 | 15 | private final SpBoardService boardService; 16 | 17 | public static SpBoard makeBoard(User user, String title, String content){ 18 | return SpBoard.builder() 19 | .writerId(user.getUserId()) 20 | .open(true) 21 | .title(title) 22 | .content(content) 23 | .build(); 24 | } 25 | 26 | public SpBoard createBoard(User user, String title, String content){ 27 | return boardService.save(makeBoard(user, title, content)); 28 | } 29 | 30 | public static void assertBoard(SpBoard board, User user, String title, String content){ 31 | assertNotNull(board.getBoardId()); 32 | assertNotNull(board.getCreated()); 33 | assertNotNull(board.getUpdated()); 34 | assertEquals(user.getUserId(), board.getWriterId()); 35 | assertEquals(title, board.getTitle()); 36 | assertEquals(content, board.getContent()); 37 | } 38 | 39 | public static Comment makeComment(User user, String commentStr){ 40 | return Comment.builder() 41 | .userId(user.getUserId()) 42 | .userName(user.getName()) 43 | .comment(commentStr) 44 | .build(); 45 | } 46 | 47 | public Comment createComment(String boardId, User user, String commentStr){ 48 | return boardService.addComment(boardId, makeComment(user, commentStr)); 49 | } 50 | 51 | public static void assertComment(Comment comment, User user, String commentStr){ 52 | assertNotNull(comment.getCreated()); 53 | assertNotNull(comment.getCommentId()); 54 | assertEquals(user.getUserId(), comment.getUserId()); 55 | assertEquals(user.getName(), comment.getUserName()); 56 | assertEquals(commentStr, comment.getComment()); 57 | } 58 | 59 | 60 | public static void assertBoardSummary(SpBoardSummary summary, 61 | SpBoard board, int count){ 62 | assertEquals(board.getBoardId(), summary.getBoardId()); 63 | assertEquals(board.getTitle(), summary.getTitle()); 64 | assertEquals(board.getWriterId(), summary.getWriterId()); 65 | assertEquals(board.getWriter().getName(), summary.getWriter().getName()); 66 | assertEquals(count, summary.getCommentCount()); 67 | assertNotNull(summary.getCreated()); 68 | assertNotNull(summary.getUpdated()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /sp-jwt-security/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | spring-boot-dependencies 8 | org.springframework.boot 9 | 2.4.0 10 | 11 | com.sp.sec 12 | sp-jwt-security 13 | 1.0.0 14 | 15 | 16 | 11 17 | ${java.version} 18 | ${java.version} 19 | UTF-8 20 | UTF-8 21 | 22 | 23 | 24 | 25 | 26 | com.sp.sec 27 | user-authority 28 | 1.0.0 29 | 30 | 31 | 32 | com.sp.sec 33 | user-authority 34 | 1.0.0 35 | test-jar 36 | test 37 | 38 | 39 | 40 | javax.servlet 41 | javax.servlet-api 42 | 43 | 44 | com.fasterxml.jackson.core 45 | jackson-databind 46 | 47 | 48 | org.projectlombok 49 | lombok 50 | 51 | 52 | com.auth0 53 | java-jwt 54 | 3.11.0 55 | compile 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-jar-plugin 65 | 66 | 67 | 68 | test-jar 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /sp-jwt-security/src/main/java/com/sp/sec/config/SpJwtSecurityModule.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.config; 2 | 3 | import com.sp.sec.web.config.SpJwtProperties; 4 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @EnableConfigurationProperties({SpJwtProperties.class}) 9 | public class SpJwtSecurityModule { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /sp-jwt-security/src/main/java/com/sp/sec/web/config/JWTCheckFilter.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.sp.sec.user.domain.User; 4 | import com.sp.sec.user.service.UserService; 5 | import org.springframework.security.authentication.AuthenticationManager; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 9 | 10 | import javax.servlet.FilterChain; 11 | import javax.servlet.ServletException; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | import java.io.IOException; 15 | 16 | public class JWTCheckFilter extends BasicAuthenticationFilter { 17 | 18 | private final UserService userService; 19 | private final JWTUtil jwtUtil; 20 | 21 | public JWTCheckFilter(AuthenticationManager authenticationManager, UserService userService, JWTUtil jwtUtil) { 22 | super(authenticationManager); 23 | this.userService = userService; 24 | this.jwtUtil = jwtUtil; 25 | } 26 | 27 | @Override 28 | protected void doFilterInternal( 29 | HttpServletRequest request, 30 | HttpServletResponse response, 31 | FilterChain chain) throws IOException, ServletException 32 | { 33 | String token = request.getHeader(JWTUtil.AUTH_HEADER); 34 | if(token == null || !token.startsWith(JWTUtil.BEARER)){ 35 | chain.doFilter(request, response); 36 | return; 37 | } 38 | VerifyResult result = jwtUtil.verify(token.substring(JWTUtil.BEARER.length())); 39 | if(result.isResult()){ 40 | // TODO : user cacher .... 41 | User user = userService.findUser(result.getUserId()).get(); 42 | SecurityContextHolder.getContext().setAuthentication( 43 | new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()) 44 | ); 45 | } 46 | chain.doFilter(request, response); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sp-jwt-security/src/main/java/com/sp/sec/web/config/JWTLoginFilter.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sp.sec.user.domain.User; 5 | import lombok.SneakyThrows; 6 | import org.springframework.security.authentication.AuthenticationManager; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.core.AuthenticationException; 10 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 11 | 12 | import javax.servlet.FilterChain; 13 | import javax.servlet.ServletException; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.io.IOException; 17 | 18 | public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter { 19 | 20 | private final AuthenticationManager authenticationManager; 21 | private final JWTUtil jwtUtil; 22 | private final ObjectMapper objectMapper; 23 | 24 | public JWTLoginFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil, ObjectMapper objectMapper){ 25 | this.authenticationManager = authenticationManager; 26 | this.jwtUtil = jwtUtil; 27 | this.objectMapper = objectMapper; 28 | setFilterProcessesUrl("/login"); 29 | } 30 | 31 | @SneakyThrows 32 | @Override 33 | public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { 34 | UserLogin userLogin = objectMapper.readValue(request.getInputStream(), UserLogin.class); 35 | UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( 36 | userLogin.getUsername(), userLogin.getPassword(), null 37 | ); 38 | return authenticationManager.authenticate(authToken); 39 | } 40 | 41 | @Override 42 | protected void successfulAuthentication( 43 | HttpServletRequest request, 44 | HttpServletResponse response, 45 | FilterChain chain, 46 | Authentication authResult) throws IOException, ServletException 47 | { 48 | User user = (User)authResult.getPrincipal(); 49 | response.addHeader(JWTUtil.AUTH_HEADER, JWTUtil.BEARER+jwtUtil.generate(user.getUserId())); 50 | // super.successfulAuthentication(request, response, chain, authResult); 51 | } 52 | 53 | @Override 54 | protected void unsuccessfulAuthentication( 55 | HttpServletRequest request, 56 | HttpServletResponse response, 57 | AuthenticationException failed) throws IOException, ServletException 58 | { 59 | System.out.println(failed.getMessage()); 60 | super.unsuccessfulAuthentication(request, response, failed); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /sp-jwt-security/src/main/java/com/sp/sec/web/config/JWTUtil.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.algorithms.Algorithm; 5 | import com.auth0.jwt.exceptions.JWTVerificationException; 6 | import com.auth0.jwt.interfaces.DecodedJWT; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.time.Instant; 10 | 11 | @Component 12 | public class JWTUtil { 13 | 14 | public static final String AUTH_HEADER = "Authentication"; 15 | public static final String REFRESH_HEADER = "refresh-token"; 16 | public static final String BEARER = "Bearer "; 17 | 18 | private Algorithm AL; 19 | public static enum TokenType { 20 | access, 21 | refresh 22 | } 23 | 24 | SpJwtProperties properties; 25 | 26 | public SpJwtProperties getProperties() { 27 | return properties; 28 | } 29 | 30 | public JWTUtil(SpJwtProperties properties){ 31 | this.properties = properties; 32 | this.AL = Algorithm.HMAC512(properties.getSecret()); 33 | } 34 | 35 | public String generate(String userId){ 36 | return generate(userId, TokenType.access); 37 | } 38 | 39 | public String generate(String userId, TokenType type){ 40 | return JWT.create().withSubject(userId) 41 | .withClaim("exp", Instant.now().getEpochSecond()+ getLifeTime(type)) 42 | .sign(AL); 43 | } 44 | 45 | private long getLifeTime(TokenType type) { 46 | switch(type){ 47 | case refresh: 48 | return this.properties.getTokenRefreshTime(); 49 | case access: 50 | default: 51 | return this.properties.getTokenLifeTime(); 52 | } 53 | } 54 | 55 | public VerifyResult verify(String token){ 56 | try{ 57 | DecodedJWT decode = JWT.require(AL).build().verify(token); 58 | return VerifyResult.builder().userId(decode.getSubject()).result(true).build(); 59 | }catch(JWTVerificationException ex){ 60 | DecodedJWT decode = JWT.decode(token); 61 | return VerifyResult.builder().userId(decode.getSubject()).result(false).build(); 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /sp-jwt-security/src/main/java/com/sp/sec/web/config/RefreshableJWTLoginFilter.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.auth0.jwt.exceptions.TokenExpiredException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.sp.sec.user.domain.User; 6 | import com.sp.sec.user.service.UserService; 7 | import lombok.SneakyThrows; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.security.core.AuthenticationException; 12 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 13 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 14 | import org.springframework.util.StringUtils; 15 | 16 | import javax.servlet.FilterChain; 17 | import javax.servlet.ServletException; 18 | import javax.servlet.http.HttpServletRequest; 19 | import javax.servlet.http.HttpServletResponse; 20 | import java.io.IOException; 21 | 22 | public class RefreshableJWTLoginFilter extends UsernamePasswordAuthenticationFilter { 23 | 24 | private final AuthenticationManager authenticationManager; 25 | private final UserService userService; 26 | private final JWTUtil jwtUtil; 27 | private final ObjectMapper objectMapper; 28 | 29 | public RefreshableJWTLoginFilter(AuthenticationManager authenticationManager, UserService userService, JWTUtil jwtUtil, ObjectMapper objectMapper){ 30 | this.authenticationManager = authenticationManager; 31 | this.userService = userService; 32 | this.jwtUtil = jwtUtil; 33 | this.objectMapper = objectMapper; 34 | setFilterProcessesUrl("/login"); 35 | } 36 | 37 | @SneakyThrows 38 | @Override 39 | public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { 40 | UserLogin userLogin = objectMapper.readValue(request.getInputStream(), UserLogin.class); 41 | 42 | // id password login 43 | if(userLogin.getType().equals(UserLogin.Type.login)){ 44 | UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( 45 | userLogin.getUsername(), userLogin.getPassword(), null 46 | ); 47 | return authenticationManager.authenticate(authToken); 48 | }else if(userLogin.getType().equals(UserLogin.Type.refresh)){ 49 | // refresh token 50 | if(StringUtils.isEmpty(userLogin.getRefreshToken())) 51 | throw new IllegalArgumentException("리프레쉬 토큰이 필요함. : "+userLogin.getRefreshToken()); 52 | 53 | VerifyResult result = jwtUtil.verify(userLogin.getRefreshToken()); 54 | if(result.isResult()){ 55 | User user = userService.findUser(result.getUserId()).orElseThrow(() -> new UsernameNotFoundException("알 수 없는 사용자 : " + result.getUserId())); 56 | return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); 57 | }else{ 58 | throw new TokenExpiredException("리프레쉬 토큰 만료"); 59 | } 60 | }else{ 61 | throw new IllegalArgumentException("알 수 없는 타입 : "+userLogin.getType()); 62 | } 63 | } 64 | 65 | @Override 66 | protected void successfulAuthentication( 67 | HttpServletRequest request, 68 | HttpServletResponse response, 69 | FilterChain chain, 70 | Authentication authResult) throws IOException, ServletException 71 | { 72 | User user = (User)authResult.getPrincipal(); 73 | response.addHeader(JWTUtil.AUTH_HEADER, JWTUtil.BEARER+jwtUtil.generate(user.getUserId(), JWTUtil.TokenType.access)); 74 | response.addHeader(JWTUtil.REFRESH_HEADER, jwtUtil.generate(user.getUserId(), JWTUtil.TokenType.refresh)); 75 | } 76 | 77 | @Override 78 | protected void unsuccessfulAuthentication( 79 | HttpServletRequest request, 80 | HttpServletResponse response, 81 | AuthenticationException failed) throws IOException, ServletException 82 | { 83 | System.out.println(failed.getMessage()); 84 | super.unsuccessfulAuthentication(request, response, failed); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /sp-jwt-security/src/main/java/com/sp/sec/web/config/SpJwtProperties.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | 4 | import lombok.Data; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | 7 | @Data 8 | @ConfigurationProperties(prefix = "sp.jwt") 9 | public class SpJwtProperties { 10 | 11 | private String secret = "default-secret-value"; 12 | private long tokenLifeTime = 600; 13 | private long tokenRefreshTime = 24*60*60; // 86400 14 | 15 | } 16 | -------------------------------------------------------------------------------- /sp-jwt-security/src/main/java/com/sp/sec/web/config/UserLogin.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | @Builder 13 | public class UserLogin { 14 | 15 | public enum Type { 16 | login, 17 | refresh 18 | } 19 | private Type type; 20 | private String username; 21 | private String password; 22 | private String refreshToken; 23 | } 24 | -------------------------------------------------------------------------------- /sp-jwt-security/src/main/java/com/sp/sec/web/config/VerifyResult.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | @Builder 12 | public class VerifyResult { 13 | 14 | private String userId; 15 | private boolean result; 16 | } 17 | -------------------------------------------------------------------------------- /sp-jwt-security/src/main/resources/META-INF/spring-configuration-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [{ 3 | "name": "sp.jwt", 4 | "type": "com.sp.sec.web.config.SpJwtProperties" 5 | }], 6 | "properties": [{ 7 | "name": "sp.jwt.secret", 8 | "type": "java.lang.String" 9 | }, { 10 | "name": "sp.jwt.token-life-time", 11 | "type": "java.lang.Long" 12 | }, { 13 | "name": "sp.jwt.token-refresh-time", 14 | "type": "java.lang.Long" 15 | }], 16 | "hints": [] 17 | } -------------------------------------------------------------------------------- /sp-jwt-security/src/test/java/com/sp/sec/web/SpIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sp.sec.web.config.JWTUtil; 5 | import com.sp.sec.web.config.UserLogin; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.web.server.LocalServerPort; 8 | import org.springframework.http.HttpEntity; 9 | import org.springframework.http.HttpHeaders; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.client.RestTemplate; 13 | 14 | import java.net.URI; 15 | import java.net.URISyntaxException; 16 | 17 | import static java.lang.String.format; 18 | 19 | public class SpIntegrationTest { 20 | 21 | @LocalServerPort 22 | protected int port; 23 | 24 | @Autowired 25 | protected ObjectMapper objectMapper; 26 | 27 | protected RestTemplate restTemplate = new RestTemplate(); 28 | 29 | 30 | protected URI uri(String path) throws URISyntaxException { 31 | return new URI(format("http://localhost:%d%s", port, path)); 32 | } 33 | 34 | protected URI uri(String path, String... args) throws URISyntaxException { 35 | return uri(format(path, args)); 36 | } 37 | 38 | protected Tokens getToken(String username, String password) throws URISyntaxException { 39 | UserLogin login = UserLogin.builder().type(UserLogin.Type.login).username(username).password(password).build(); 40 | HttpEntity body = new HttpEntity<>(login); 41 | ResponseEntity response = restTemplate.exchange(uri("/login"), 42 | HttpMethod.POST, body, String.class); 43 | return Tokens.builder().accessToken(getAccessToken(response)).build(); 44 | } 45 | 46 | protected String getAccessToken(ResponseEntity response) { 47 | return response.getHeaders().get(JWTUtil.AUTH_HEADER).get(0) 48 | .substring(JWTUtil.BEARER.length()); 49 | } 50 | 51 | protected HttpEntity getAuthHeaderEntity(String accessToken) { 52 | return getPostAuthHeaderEntity(accessToken, null); 53 | } 54 | 55 | protected HttpEntity getPostAuthHeaderEntity(String accessToken, Object object) { 56 | HttpHeaders headers = new HttpHeaders(); 57 | headers.add(JWTUtil.AUTH_HEADER, JWTUtil.BEARER+ accessToken); 58 | HttpEntity entity = new HttpEntity(object, headers); 59 | return entity; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /sp-jwt-security/src/test/java/com/sp/sec/web/SpJwtRefreshableTwoUserIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import com.sp.sec.user.UserTestHelper; 4 | import com.sp.sec.user.domain.Authority; 5 | import com.sp.sec.user.domain.User; 6 | import com.sp.sec.user.service.UserService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | 10 | public class SpJwtRefreshableTwoUserIntegrationTest extends SpRefreshableIntegrationTest { 11 | 12 | @Autowired 13 | protected UserService userService; 14 | 15 | @Autowired 16 | private PasswordEncoder passwordEncoder; 17 | protected UserTestHelper userTestHelper; 18 | 19 | protected User USER1; 20 | protected User USER2; 21 | 22 | protected void prepareTwoUsers(){ 23 | userService.clearUsers(); 24 | this.userTestHelper = new UserTestHelper(userService, passwordEncoder); 25 | this.USER1 = this.userTestHelper.createUser("user1", Authority.ROLE_USER); 26 | this.USER2 = this.userTestHelper.createUser("user2", Authority.ROLE_USER); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /sp-jwt-security/src/test/java/com/sp/sec/web/SpJwtTwoUserIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import com.sp.sec.user.UserTestHelper; 4 | import com.sp.sec.user.domain.Authority; 5 | import com.sp.sec.user.domain.User; 6 | import com.sp.sec.user.service.UserService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | 10 | public class SpJwtTwoUserIntegrationTest extends SpIntegrationTest{ 11 | 12 | @Autowired 13 | protected UserService userService; 14 | 15 | @Autowired 16 | private PasswordEncoder passwordEncoder; 17 | protected UserTestHelper userTestHelper; 18 | 19 | protected User USER1; 20 | protected User USER2; 21 | 22 | protected void prepareTwoUsers(){ 23 | userService.clearUsers(); 24 | this.userTestHelper = new UserTestHelper(userService, passwordEncoder); 25 | this.USER1 = this.userTestHelper.createUser("user1", Authority.ROLE_USER); 26 | this.USER2 = this.userTestHelper.createUser("user2", Authority.ROLE_USER); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /sp-jwt-security/src/test/java/com/sp/sec/web/SpJwtUserAdminIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import com.sp.sec.user.UserTestHelper; 4 | import com.sp.sec.user.domain.Authority; 5 | import com.sp.sec.user.domain.User; 6 | import com.sp.sec.user.service.UserService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | 10 | public class SpJwtUserAdminIntegrationTest extends SpIntegrationTest{ 11 | 12 | @Autowired 13 | protected UserService userService; 14 | 15 | @Autowired 16 | private PasswordEncoder passwordEncoder; 17 | protected UserTestHelper userTestHelper; 18 | 19 | protected User USER1; 20 | protected User ADMIN; 21 | 22 | protected void prepareUserAdmin(){ 23 | userService.clearUsers(); 24 | this.userTestHelper = new UserTestHelper(userService, passwordEncoder); 25 | this.USER1 = this.userTestHelper.createUser("user1", Authority.ROLE_USER); 26 | this.ADMIN = this.userTestHelper.createUser("admin", Authority.ROLE_ADMIN); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /sp-jwt-security/src/test/java/com/sp/sec/web/SpRefreshableIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import com.sp.sec.web.config.JWTUtil; 4 | import com.sp.sec.web.config.UserLogin; 5 | import org.springframework.http.HttpEntity; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.http.ResponseEntity; 8 | 9 | import java.net.URISyntaxException; 10 | 11 | public class SpRefreshableIntegrationTest extends SpIntegrationTest { 12 | 13 | 14 | protected Tokens getToken(String username, String password) throws URISyntaxException { 15 | UserLogin login = UserLogin.builder().type(UserLogin.Type.login).username(username).password(password).build(); 16 | HttpEntity body = new HttpEntity<>(login); 17 | ResponseEntity response = restTemplate.exchange(uri("/login"), 18 | HttpMethod.POST, body, String.class); 19 | return Tokens.builder() 20 | .accessToken(getAccessToken(response)) 21 | .refreshToken(getRefreshToken(response)) 22 | .build(); 23 | } 24 | 25 | protected Tokens getRefreshToken(String refreshToken) throws URISyntaxException { 26 | UserLogin login = UserLogin.builder().type(UserLogin.Type.refresh) 27 | .refreshToken(refreshToken).build(); 28 | HttpEntity body = new HttpEntity<>(login); 29 | ResponseEntity response = restTemplate.exchange(uri("/login"), 30 | HttpMethod.POST, body, String.class); 31 | return Tokens.builder() 32 | .accessToken(getAccessToken(response)) 33 | .refreshToken(getRefreshToken(response)) 34 | .build(); 35 | } 36 | 37 | protected String getRefreshToken(ResponseEntity response) { 38 | return response.getHeaders().get(JWTUtil.REFRESH_HEADER).get(0); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /sp-jwt-security/src/test/java/com/sp/sec/web/Tokens.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | @Builder 12 | public class Tokens { 13 | 14 | private String accessToken; 15 | private String refreshToken; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /sp-web-util/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | spring-boot-dependencies 8 | org.springframework.boot 9 | 2.4.0 10 | 11 | com.sp.sec 12 | sp-web-util 13 | 1.0.0 14 | 15 | 16 | 11 17 | ${java.version} 18 | ${java.version} 19 | UTF-8 20 | UTF-8 21 | 22 | 23 | 24 | 25 | 26 | com.fasterxml.jackson.core 27 | jackson-annotations 28 | 29 | 30 | org.springframework.data 31 | spring-data-commons 32 | 33 | 34 | com.fasterxml.jackson.core 35 | jackson-databind 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /sp-web-util/src/main/java/com/sp/sec/web/util/RestResponsePage.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.util; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.PageImpl; 8 | import org.springframework.data.domain.PageRequest; 9 | import org.springframework.data.domain.Pageable; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | import java.util.stream.Stream; 15 | 16 | public class RestResponsePage extends PageImpl { 17 | @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) 18 | public RestResponsePage(@JsonProperty("content") List content, 19 | @JsonProperty("number") int number, 20 | @JsonProperty("size") int size, 21 | @JsonProperty("totalElements") Long totalElements, 22 | @JsonProperty("pageable") JsonNode pageable, 23 | @JsonProperty("last") boolean last, 24 | @JsonProperty("totalPages") int totalPages, 25 | @JsonProperty("sort") JsonNode sort, 26 | @JsonProperty("first") boolean first, 27 | @JsonProperty("numberOfElements") int numberOfElements) { 28 | 29 | super(content, PageRequest.of(number, size), totalElements); 30 | } 31 | 32 | public static RestResponsePage of(Page page){ 33 | return new RestResponsePage(page); 34 | } 35 | 36 | public RestResponsePage(Page page) { 37 | super(page.getContent(), page.getPageable(), page.getTotalElements()); 38 | } 39 | 40 | public RestResponsePage(List content, Pageable pageable, long total) { 41 | super(content, pageable, total); 42 | } 43 | 44 | public RestResponsePage(List content) { 45 | super(content); 46 | } 47 | 48 | public RestResponsePage(T ... array) { 49 | super(Stream.of(array).collect(Collectors.toList())); 50 | } 51 | 52 | public RestResponsePage() { 53 | super(new ArrayList<>()); 54 | } 55 | } -------------------------------------------------------------------------------- /user-authority/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jongwon/spring-security-junit5-test/8398bcfcc938cf1709496b099cd8e50270ec3dcf/user-authority/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /user-authority/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /user-authority/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-dependencies 8 | 2.4.0 9 | 10 | com.sp.sec 11 | user-authority 12 | 1.0.0 13 | user-authority 14 | 사용자 모듈 15 | 16 | 17 | 11 18 | ${java.version} 19 | ${java.version} 20 | UTF-8 21 | UTF-8 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-data-mongodb 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-security 32 | 33 | 34 | 35 | org.projectlombok 36 | lombok 37 | true 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | test 43 | 44 | 45 | org.junit.vintage 46 | junit-vintage-engine 47 | 48 | 49 | 50 | 51 | de.flapdoodle.embed 52 | de.flapdoodle.embed.mongo 53 | test 54 | 55 | 56 | org.springframework.security 57 | spring-security-test 58 | test 59 | 60 | 61 | com.fasterxml.jackson.core 62 | jackson-annotations 63 | 64 | 65 | com.sp.sec 66 | sp-web-util 67 | 1.0.0 68 | compile 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.apache.maven.plugins 76 | maven-jar-plugin 77 | 78 | 79 | 80 | test-jar 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /user-authority/src/main/java/com/sp/sec/config/UserAuthorityModule.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.config; 2 | 3 | 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 7 | 8 | @Configuration 9 | @ComponentScan("com.sp.sec.user") 10 | @EnableMongoRepositories(basePackages = { 11 | "com.sp.sec.user.repository" 12 | }) 13 | public class UserAuthorityModule { 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /user-authority/src/main/java/com/sp/sec/user/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.controller; 2 | 3 | 4 | import com.sp.sec.user.domain.User; 5 | import com.sp.sec.user.service.UserService; 6 | import com.sp.sec.web.util.RestResponsePage; 7 | import lombok.AllArgsConstructor; 8 | import org.springframework.security.access.prepost.PreAuthorize; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.Optional; 12 | 13 | @RestController 14 | @RequestMapping("/user") 15 | @AllArgsConstructor 16 | public class UserController { 17 | 18 | 19 | private final UserService userService; 20 | 21 | // save 22 | @PostMapping("/save") 23 | public User saveUser( 24 | @RequestBody User user 25 | ){ 26 | return userService.save(user); 27 | } 28 | 29 | @GetMapping("/{userId}") 30 | public Optional getUser(@PathVariable String userId){ 31 | return userService.findUser(userId); 32 | } 33 | 34 | // list : page 35 | @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')") 36 | @GetMapping("/list") 37 | public RestResponsePage list( 38 | @RequestParam(defaultValue = "1") Integer page, 39 | @RequestParam(defaultValue = "10") Integer size 40 | ){ 41 | return RestResponsePage.of(userService.listUsers(page, size)); 42 | } 43 | 44 | // add role 45 | @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')") 46 | @PutMapping("/authority/add") 47 | public Optional addAuthority( 48 | @RequestParam String userId, 49 | @RequestParam String authority 50 | ){ 51 | userService.findUser(userId).ifPresent(user->{ 52 | userService.addAuthority(userId, authority); 53 | }); 54 | return userService.findUser(userId); 55 | } 56 | 57 | // remove role 58 | @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')") 59 | @PutMapping("/authority/remove") 60 | public Optional removeAuthority( 61 | @RequestParam String userId, 62 | @RequestParam String authority 63 | ){ 64 | userService.findUser(userId).ifPresent(user->{ 65 | userService.removeAuthority(userId, authority); 66 | }); 67 | return userService.findUser(userId); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /user-authority/src/main/java/com/sp/sec/user/domain/Authority.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.springframework.security.core.GrantedAuthority; 8 | 9 | import java.util.Objects; 10 | 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Builder 15 | public class Authority implements GrantedAuthority { 16 | 17 | public static final String ROLE_USER = "ROLE_USER"; 18 | public static final String ROLE_ADMIN = "ROLE_ADMIN"; 19 | public static final Authority USER = new Authority(ROLE_USER); 20 | public static final Authority ADMIN = new Authority(ROLE_ADMIN); 21 | 22 | private String authority; 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (o == null || getClass() != o.getClass()) return false; 28 | Authority authority1 = (Authority) o; 29 | return Objects.equals(authority, authority1.authority); 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(authority); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /user-authority/src/main/java/com/sp/sec/user/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.springframework.data.annotation.Id; 9 | import org.springframework.data.mongodb.core.index.Indexed; 10 | import org.springframework.data.mongodb.core.mapping.Document; 11 | import org.springframework.security.core.userdetails.UserDetails; 12 | 13 | import java.time.LocalDateTime; 14 | import java.util.Set; 15 | 16 | @Document(collection = "sp_user") 17 | @Data 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @Builder 21 | public class User implements UserDetails { 22 | 23 | @Id 24 | private String userId; 25 | 26 | @Indexed(unique = true) 27 | private String email; 28 | private String name; 29 | private String picUrl; 30 | 31 | @JsonIgnore 32 | private String password; 33 | 34 | private boolean enabled; 35 | 36 | private Set authorities; 37 | 38 | private LocalDateTime created; 39 | private LocalDateTime updated; 40 | 41 | @Override 42 | public String getUsername() { 43 | return email; 44 | } 45 | 46 | @Override 47 | public boolean isAccountNonExpired() { 48 | return enabled; 49 | } 50 | 51 | @Override 52 | public boolean isAccountNonLocked() { 53 | return enabled; 54 | } 55 | 56 | @Override 57 | public boolean isCredentialsNonExpired() { 58 | return enabled; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /user-authority/src/main/java/com/sp/sec/user/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.repository; 2 | 3 | import com.sp.sec.user.domain.User; 4 | import org.springframework.data.mongodb.repository.MongoRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Optional; 8 | 9 | @Repository 10 | public interface UserRepository extends MongoRepository { 11 | 12 | Optional findByEmail(String email); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /user-authority/src/main/java/com/sp/sec/user/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.service; 2 | 3 | 4 | import com.sp.sec.user.domain.Authority; 5 | import com.sp.sec.user.domain.User; 6 | import com.sp.sec.user.repository.UserRepository; 7 | import org.springframework.dao.DuplicateKeyException; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.data.domain.PageRequest; 10 | import org.springframework.data.domain.Sort; 11 | import org.springframework.data.mongodb.core.MongoTemplate; 12 | import org.springframework.data.mongodb.core.index.Index; 13 | import org.springframework.data.mongodb.core.query.Criteria; 14 | import org.springframework.data.mongodb.core.query.Query; 15 | import org.springframework.data.mongodb.core.query.Update; 16 | import org.springframework.security.core.userdetails.UserDetails; 17 | import org.springframework.security.core.userdetails.UserDetailsService; 18 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 19 | import org.springframework.stereotype.Service; 20 | import org.springframework.util.StringUtils; 21 | 22 | import java.time.LocalDateTime; 23 | import java.util.Collection; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | import java.util.Optional; 27 | import java.util.function.Function; 28 | import java.util.stream.Collectors; 29 | import java.util.stream.StreamSupport; 30 | 31 | @Service 32 | public class UserService implements UserDetailsService { 33 | 34 | protected final MongoTemplate mongoTemplate; 35 | protected final UserRepository userRepository; 36 | 37 | public UserService(MongoTemplate mongoTemplate, UserRepository userRepository) { 38 | this.mongoTemplate = mongoTemplate; 39 | this.userRepository = userRepository; 40 | ensureIndex(); 41 | } 42 | 43 | private void ensureIndex() { 44 | mongoTemplate.indexOps(User.class). 45 | ensureIndex(new Index().on("email", Sort.Direction.ASC).unique()); 46 | } 47 | 48 | public User save(User user) throws DuplicateKeyException { 49 | if(StringUtils.isEmpty(user.getUserId())){ 50 | user.setCreated(LocalDateTime.now()); 51 | } 52 | user.setUpdated(LocalDateTime.now()); 53 | // user.setEnabled(true); 54 | return userRepository.save(user); 55 | } 56 | 57 | public Optional findUser(String userId){ 58 | return userRepository.findById(userId); 59 | } 60 | 61 | public boolean updateUserName(String userId, String userName){ 62 | Update update = new Update(); 63 | update.set("name", userName); 64 | update.set("updated", LocalDateTime.now()); 65 | return mongoTemplate.updateFirst(Query.query(Criteria.where("userId").is(userId)), 66 | update, User.class).wasAcknowledged(); 67 | } 68 | 69 | @Override 70 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 71 | return userRepository.findByEmail(username).orElseThrow(()->new UsernameNotFoundException(username+"이 존재하지 않음")); 72 | } 73 | 74 | public boolean addAuthority(String userId, String authority){ 75 | Update update = new Update(); 76 | update.push("authorities", new Authority((authority))); 77 | update.set("updated", LocalDateTime.now()); 78 | return mongoTemplate.updateFirst(Query.query(Criteria.where("userId").is(userId)), 79 | update, User.class).wasAcknowledged(); 80 | } 81 | 82 | public boolean removeAuthority(String userId, String authority){ 83 | Update update = new Update(); 84 | update.pull("authorities", new Authority((authority))); 85 | update.set("updated", LocalDateTime.now()); 86 | return mongoTemplate.updateFirst(Query.query(Criteria.where("userId").is(userId)), 87 | update, User.class).wasAcknowledged(); 88 | } 89 | 90 | public void clearUsers() { 91 | userRepository.deleteAll(); 92 | } 93 | 94 | public Page listUsers(Integer page, Integer size) { 95 | return userRepository.findAll(PageRequest.of(page-1, size)); 96 | } 97 | 98 | public Map getUserMap(Collection userIds){ 99 | if(userIds == null || userIds.isEmpty()) return new HashMap<>(); 100 | return StreamSupport.stream(userRepository.findAllById(userIds).spliterator(), false) 101 | .collect(Collectors.toMap(User::getUserId, Function.identity())); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /user-authority/src/test/java/com/sp/sec/user/UserAuthorityJpaTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user; 2 | 3 | 4 | import com.sp.sec.user.domain.Authority; 5 | import com.sp.sec.user.domain.User; 6 | import com.sp.sec.user.repository.UserRepository; 7 | import com.sp.sec.user.service.UserService; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.DisplayName; 10 | import org.junit.jupiter.api.Test; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; 13 | import org.springframework.dao.DuplicateKeyException; 14 | import org.springframework.data.mongodb.core.MongoTemplate; 15 | import org.springframework.security.crypto.password.NoOpPasswordEncoder; 16 | import org.springframework.test.context.ActiveProfiles; 17 | 18 | import java.util.List; 19 | 20 | import static org.junit.jupiter.api.Assertions.assertEquals; 21 | import static org.junit.jupiter.api.Assertions.assertThrows; 22 | 23 | @ActiveProfiles("test") 24 | @DataMongoTest 25 | public class UserAuthorityJpaTest extends WithUserTest { 26 | 27 | @BeforeEach 28 | void before(){ 29 | prepareUserService(); 30 | } 31 | 32 | @DisplayName("1. 사용자를 생성한다.") 33 | @Test 34 | void test_1() { 35 | userTestHelper.createUser("user1"); 36 | List userList = this.userRepository.findAll(); 37 | 38 | assertEquals(1, userList.size()); 39 | userTestHelper.assertUser(userList.get(0), "user1"); 40 | } 41 | 42 | @DisplayName("2. 사용자의 이름을 수정한다.") 43 | @Test 44 | void test_2() { 45 | User user1 = userTestHelper.createUser("user1"); 46 | userService.updateUserName(user1.getUserId(), "user2"); 47 | 48 | User savedUser = userService.findUser(user1.getUserId()).get(); 49 | assertEquals("user2", savedUser.getName()); 50 | } 51 | 52 | @DisplayName("3. authority를 부여한다.") 53 | @Test 54 | void test_3() { 55 | User user1 = userTestHelper.createUser("user1", Authority.ROLE_USER); 56 | userService.addAuthority(user1.getUserId(), Authority.ROLE_ADMIN); 57 | User savedUser = userService.findUser(user1.getUserId()).get(); 58 | userTestHelper.assertUser(savedUser, "user1", Authority.ROLE_USER, Authority.ROLE_ADMIN); 59 | } 60 | 61 | 62 | @DisplayName("4. authority를 뺏는다.") 63 | @Test 64 | void test_4() { 65 | User user1 = userTestHelper.createUser("admin", Authority.ROLE_USER, Authority.ROLE_ADMIN); 66 | userService.removeAuthority(user1.getUserId(), Authority.ROLE_USER); 67 | User savedUser = userService.findUser(user1.getUserId()).get(); 68 | userTestHelper.assertUser(savedUser, "admin", Authority.ROLE_ADMIN); 69 | } 70 | 71 | @DisplayName("5. email 로 검색이 된다.") 72 | @Test 73 | void test_5() { 74 | User user1 = userTestHelper.createUser("user1"); 75 | User saved = (User) userService.loadUserByUsername("user1@test.com"); 76 | userTestHelper.assertUser(saved, "user1"); 77 | } 78 | 79 | @DisplayName("6. role이 중복되서 추가되지 않는다.") 80 | @Test 81 | void test_6() { 82 | User user1 = userTestHelper.createUser("user1", Authority.ROLE_USER); 83 | userService.addAuthority(user1.getUserId(), Authority.ROLE_USER); 84 | userService.addAuthority(user1.getUserId(), Authority.ROLE_USER); 85 | User savedUser = userService.findUser(user1.getUserId()).get(); 86 | userTestHelper.assertUser(savedUser, "user1", Authority.ROLE_USER); 87 | } 88 | 89 | @DisplayName("7. email이 중복되어서 들어가는가?") 90 | @Test 91 | void test_() { 92 | userTestHelper.createUser("user1"); 93 | assertThrows(DuplicateKeyException.class, ()->{ 94 | userTestHelper.createUser("user1"); 95 | }); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /user-authority/src/test/java/com/sp/sec/user/UserAuthorityTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | @SpringBootApplication 9 | public class UserAuthorityTestApplication { 10 | 11 | public static void main(String[] args){ 12 | SpringApplication.run(UserAuthorityTestApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /user-authority/src/test/java/com/sp/sec/user/UserTestHelper.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user; 2 | 3 | 4 | import com.sp.sec.user.domain.Authority; 5 | import com.sp.sec.user.domain.User; 6 | import com.sp.sec.user.service.UserService; 7 | import lombok.AllArgsConstructor; 8 | import org.springframework.dao.DuplicateKeyException; 9 | import org.springframework.security.crypto.password.PasswordEncoder; 10 | 11 | import java.util.stream.Collectors; 12 | import java.util.stream.Stream; 13 | 14 | import static org.junit.jupiter.api.Assertions.*; 15 | 16 | @AllArgsConstructor 17 | public class UserTestHelper { 18 | 19 | private final UserService userService; 20 | 21 | private final PasswordEncoder passwordEncoder; 22 | 23 | public User createUser(String name) throws DuplicateKeyException { 24 | User user = User.builder() 25 | .name(name) 26 | .email(name+"@test.com") 27 | .password(passwordEncoder.encode(name+"123")) 28 | .enabled(true) 29 | .build(); 30 | return userService.save(user); 31 | } 32 | 33 | public User createUser(String name, String... authorities){ 34 | User user = createUser(name); 35 | Stream.of(authorities).forEach(auth->userService.addAuthority(user.getUserId(), auth)); 36 | return user; 37 | } 38 | 39 | public void assertUser(User user, String name){ 40 | assertNotNull(user.getUserId()); 41 | assertNotNull(user.getCreated()); 42 | assertNotNull(user.getUpdated()); 43 | assertTrue(user.isEnabled()); 44 | assertEquals(name, user.getName()); 45 | assertEquals(name+"@test.com", user.getEmail()); 46 | // assertEquals(name+"123", user.getPassword()); 47 | } 48 | 49 | public void assertUser(User user, String name, String... authorities){ 50 | assertUser(user, name); 51 | assertTrue(user.getAuthorities().containsAll( 52 | Stream.of(authorities).map(auth->new Authority(auth)).collect(Collectors.toList()) 53 | )); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /user-authority/src/test/java/com/sp/sec/user/WithUserTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user; 2 | 3 | import com.sp.sec.user.repository.UserRepository; 4 | import com.sp.sec.user.service.UserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.data.mongodb.core.MongoTemplate; 7 | import org.springframework.security.crypto.password.NoOpPasswordEncoder; 8 | 9 | public class WithUserTest { 10 | 11 | @Autowired 12 | protected MongoTemplate mongoTemplate; 13 | 14 | @Autowired 15 | protected UserRepository userRepository; 16 | 17 | protected UserService userService; 18 | 19 | protected UserTestHelper userTestHelper; 20 | 21 | protected void prepareUserService(){ 22 | this.userRepository.deleteAll(); 23 | this.userService = new UserService(mongoTemplate, userRepository); 24 | this.userTestHelper = new UserTestHelper(userService, NoOpPasswordEncoder.getInstance()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /user-authority/src/test/resources/applicaiton-test.yml: -------------------------------------------------------------------------------- 1 | 2 | spring: 3 | data: 4 | mongodb: 5 | database: user-authority 6 | host: localhost 7 | port: 1111 8 | 9 | -------------------------------------------------------------------------------- /user-oauth2-support/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-dependencies 9 | 2.4.0 10 | 11 | com.sp.sec 12 | user-oauth2-support 13 | 1.0.0 14 | 15 | 16 | 11 17 | ${java.version} 18 | ${java.version} 19 | UTF-8 20 | UTF-8 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-data-mongodb 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-oauth2-client 33 | 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | true 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-test 44 | test 45 | 46 | 47 | org.junit.vintage 48 | junit-vintage-engine 49 | 50 | 51 | 52 | 53 | 54 | de.flapdoodle.embed 55 | de.flapdoodle.embed.mongo 56 | test 57 | 58 | 59 | 60 | org.springframework.security 61 | spring-security-test 62 | test 63 | 64 | 65 | 66 | com.fasterxml.jackson.core 67 | jackson-annotations 68 | 69 | 70 | 71 | com.sp.sec 72 | user-authority 73 | 1.0.0 74 | 75 | 76 | 77 | com.sp.sec 78 | user-authority 79 | 1.0.0 80 | test-jar 81 | test 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-jar-plugin 91 | 92 | 93 | 94 | test-jar 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /user-oauth2-support/src/main/java/com/sp/sec/config/UserOAuth2SupportModule.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.config; 2 | 3 | 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 7 | 8 | @Configuration 9 | @ComponentScan("com.sp.sec.user.oauth2") 10 | @EnableMongoRepositories(basePackages = { 11 | "com.sp.sec.user.oauth2.repository" 12 | }) 13 | public class UserOAuth2SupportModule { 14 | } 15 | -------------------------------------------------------------------------------- /user-oauth2-support/src/main/java/com/sp/sec/user/oauth2/domain/ExtendedUser.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2.domain; 2 | 3 | import com.sp.sec.user.domain.User; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.security.oauth2.core.user.OAuth2User; 7 | 8 | import java.util.Map; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | public class ExtendedUser extends User implements OAuth2User { 13 | 14 | private Map attributes; 15 | 16 | public ExtendedUser(User user){ 17 | super(user.getUserId(), user.getEmail(), user.getName(), user.getPicUrl(), user.getPassword() 18 | , user.isEnabled(), user.getAuthorities(), user.getCreated(), user.getUpdated()); 19 | } 20 | 21 | public static ExtendedUser of(User user){ 22 | return new ExtendedUser(user); 23 | } 24 | 25 | @Override 26 | public A getAttribute(String name) { 27 | return attributes == null ? null : (A) attributes.get(name); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /user-oauth2-support/src/main/java/com/sp/sec/user/oauth2/domain/ProvidedOAuth2User.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2.domain; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.springframework.data.annotation.Id; 9 | import org.springframework.data.mongodb.core.mapping.Document; 10 | import org.springframework.security.oauth2.core.user.OAuth2User; 11 | 12 | import java.time.LocalDateTime; 13 | import java.util.Map; 14 | 15 | import static java.lang.String.format; 16 | 17 | @Data 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @Builder 21 | @Document(collection = "sp_provided_oauth2_user") 22 | public class ProvidedOAuth2User { 23 | 24 | @Id 25 | private String oauth2UserId; 26 | 27 | private String userId; // refer 28 | 29 | private Provider provider; 30 | 31 | private String name; 32 | private String email; 33 | private String picUrl; 34 | 35 | private LocalDateTime registered; 36 | private LocalDateTime lastLoggedIn; 37 | 38 | public static enum Provider { 39 | google { 40 | public ProvidedOAuth2User convert(OAuth2User user){ 41 | return ProvidedOAuth2User.builder() 42 | .oauth2UserId(format("%s_%s", name(), user.getAttribute("sub"))) 43 | .name(user.getAttribute("name")) 44 | .email(user.getAttribute("email")) 45 | .picUrl(user.getAttribute("picture")) 46 | .provider(google) 47 | .build(); 48 | } 49 | }, 50 | facebook{ 51 | public ProvidedOAuth2User convert(OAuth2User user){ 52 | return ProvidedOAuth2User.builder() 53 | .oauth2UserId(format("%s_%s", name(), user.getAttribute("id"))) 54 | .name(user.getAttribute("name")) 55 | .email(user.getAttribute("email")) 56 | // .picUrl() 57 | .provider(facebook) 58 | .build(); 59 | } 60 | }, 61 | naver{ 62 | public ProvidedOAuth2User convert(OAuth2User user){ 63 | Map response = user.getAttribute("response"); 64 | return ProvidedOAuth2User.builder() 65 | .oauth2UserId(format("%s_%s", name(), response.get("id"))) 66 | .name(""+response.get("name")) 67 | .email(""+response.get("email")) 68 | .picUrl(""+response.get("profile_image")) 69 | .provider(naver) 70 | .build(); 71 | } 72 | }, 73 | kakao{ 74 | public ProvidedOAuth2User convert(OAuth2User user){ 75 | Map kakaoAccount = user.getAttribute("kakao_account"); 76 | Map profile = (Map) kakaoAccount.get("profile"); 77 | return ProvidedOAuth2User.builder() 78 | .oauth2UserId(format("%s_%s", name(), user.getAttribute("id"))) 79 | .name(""+profile.get("nickname")) 80 | .email(""+kakaoAccount.get("email")) 81 | .picUrl(""+profile.get("thumbnail_image_url")) 82 | .provider(kakao) 83 | .build(); 84 | } 85 | } 86 | ; 87 | 88 | public abstract ProvidedOAuth2User convert(OAuth2User user); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /user-oauth2-support/src/main/java/com/sp/sec/user/oauth2/repository/ExtendedUserRepository.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2.repository; 2 | 3 | import com.sp.sec.user.oauth2.domain.ExtendedUser; 4 | import org.springframework.data.mongodb.repository.MongoRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface ExtendedUserRepository extends MongoRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /user-oauth2-support/src/main/java/com/sp/sec/user/oauth2/repository/ProvidedOAuth2UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2.repository; 2 | 3 | import com.sp.sec.user.oauth2.domain.ProvidedOAuth2User; 4 | import org.springframework.data.mongodb.repository.MongoRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface ProvidedOAuth2UserRepository extends MongoRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /user-oauth2-support/src/main/java/com/sp/sec/user/oauth2/service/ExtendedUserService.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2.service; 2 | 3 | import com.sp.sec.user.domain.Authority; 4 | import com.sp.sec.user.domain.User; 5 | import com.sp.sec.user.oauth2.domain.ExtendedUser; 6 | import com.sp.sec.user.oauth2.domain.ProvidedOAuth2User; 7 | import com.sp.sec.user.oauth2.repository.ExtendedUserRepository; 8 | import com.sp.sec.user.oauth2.repository.ProvidedOAuth2UserRepository; 9 | import com.sp.sec.user.repository.UserRepository; 10 | import com.sp.sec.user.service.UserService; 11 | import org.springframework.data.mongodb.core.MongoTemplate; 12 | import org.springframework.data.mongodb.core.query.Criteria; 13 | import org.springframework.data.mongodb.core.query.Query; 14 | import org.springframework.security.oauth2.core.user.OAuth2User; 15 | import org.springframework.stereotype.Service; 16 | 17 | import java.time.LocalDateTime; 18 | import java.util.List; 19 | import java.util.Optional; 20 | import java.util.Set; 21 | 22 | @Service 23 | public class ExtendedUserService extends UserService { 24 | 25 | private final ExtendedUserRepository extendedUserRepository; 26 | private final ProvidedOAuth2UserService providedOAuth2UserService; 27 | 28 | public ExtendedUserService(MongoTemplate mongoTemplate, 29 | UserRepository userRepository, 30 | ExtendedUserRepository extendedUserRepository, 31 | ProvidedOAuth2UserService providedOAuth2UserService) { 32 | super(mongoTemplate, userRepository); 33 | this.extendedUserRepository = extendedUserRepository; 34 | this.providedOAuth2UserService = providedOAuth2UserService; 35 | } 36 | 37 | public Optional findExtendedUser(String userId){ 38 | return extendedUserRepository.findById(userId); 39 | } 40 | 41 | 42 | public ExtendedUser registerOAuth2User(final ExtendedUser user, OAuth2User oAuth2User, ProvidedOAuth2User.Provider provider) { 43 | 44 | // 1. ProvidedOAuth2User 를 가져오고 없으면 등록한다. 45 | ProvidedOAuth2User providedOAuth2User = provider.convert(oAuth2User); 46 | ProvidedOAuth2User saved = providedOAuth2UserService.find(providedOAuth2User.getOauth2UserId()).orElseGet(()->{ 47 | if(user == null){ 48 | // 2. user 정보가 없으면 user를 새로 등록하고 리턴한다. 49 | ExtendedUser siteUser = ExtendedUser.of(User.builder() 50 | .name(providedOAuth2User.getName()) 51 | .picUrl(providedOAuth2User.getPicUrl()) 52 | .email(providedOAuth2User.getEmail()) 53 | .authorities(Set.of(Authority.USER)) 54 | .enabled(true) 55 | .build()); 56 | siteUser = (ExtendedUser) save(siteUser); 57 | providedOAuth2User.setUserId(siteUser.getUserId()); 58 | }else{ 59 | providedOAuth2User.setUserId(user.getUserId()); 60 | } 61 | providedOAuth2User.setLastLoggedIn(LocalDateTime.now()); 62 | return providedOAuth2UserService.save(providedOAuth2User); 63 | }); 64 | 65 | if(user == null) return findExtendedUser(saved.getUserId()).get(); 66 | 67 | // 3. user 정보가 있고 user와 같으면 lastLogin 값을 업데이트 하고 user 를 리턴한다. 68 | if(user.getUserId().equals(saved.getUserId())){ 69 | providedOAuth2UserService.updateLastLoggedInTime(saved.getOauth2UserId()); 70 | return user; 71 | } 72 | 73 | // 4. user 정보가 있고 user와 같지 않으면 ... ??? user 로 userId를 치환하고 lastLogin 을 업데이트 하고 user를 리턴한다. 74 | // * 필요하다면 변경 정보를 별도 DB로 관리한다. 75 | // * 해당 provider로 등록된 계정이 없다면 등록한다. 76 | if(providedOAuth2UserService.findProvidedOAuth2User(user.getUserId(), provider).isPresent()){ 77 | throw new IllegalArgumentException("이미 등록된 사용자 정보가 있습니다."); 78 | } 79 | 80 | providedOAuth2UserService.changeUserId(saved.getOauth2UserId(), user.getUserId()); 81 | return user; 82 | } 83 | 84 | 85 | public List getProvidedOAuth2UserList(String userId){ 86 | return mongoTemplate.find(Query.query(Criteria.where("userId").is(userId)), ProvidedOAuth2User.class); 87 | } 88 | 89 | 90 | } 91 | -------------------------------------------------------------------------------- /user-oauth2-support/src/main/java/com/sp/sec/user/oauth2/service/ProvidedOAuth2UserService.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2.service; 2 | 3 | 4 | import com.sp.sec.user.oauth2.domain.ProvidedOAuth2User; 5 | import com.sp.sec.user.oauth2.repository.ProvidedOAuth2UserRepository; 6 | import org.springframework.data.mongodb.core.MongoTemplate; 7 | import org.springframework.data.mongodb.core.query.Criteria; 8 | import org.springframework.data.mongodb.core.query.Query; 9 | import org.springframework.data.mongodb.core.query.Update; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.time.LocalDateTime; 13 | import java.util.Optional; 14 | 15 | @Service 16 | public class ProvidedOAuth2UserService { 17 | 18 | private final MongoTemplate mongoTemplate; 19 | private final ProvidedOAuth2UserRepository repository; 20 | 21 | public ProvidedOAuth2UserService(MongoTemplate mongoTemplate, ProvidedOAuth2UserRepository repository) { 22 | this.mongoTemplate = mongoTemplate; 23 | this.repository = repository; 24 | } 25 | 26 | public ProvidedOAuth2User save(ProvidedOAuth2User providedOAuth2User){ 27 | providedOAuth2User.setRegistered(LocalDateTime.now()); 28 | return repository.save(providedOAuth2User); 29 | } 30 | 31 | public boolean updateLastLoggedInTime(String oauth2UserId){ 32 | return mongoTemplate.updateFirst(Query.query(Criteria.where("oauth2UserId").is(oauth2UserId)), 33 | Update.update("lastLoggedIn", LocalDateTime.now()), ProvidedOAuth2User.class).wasAcknowledged(); 34 | } 35 | 36 | 37 | public Optional find(String oauth2UserId) { 38 | return repository.findById(oauth2UserId); 39 | } 40 | 41 | public boolean changeUserId(String oauth2UserId, String userId) { 42 | Update update = Update.update("lastLoggedIn", LocalDateTime.now()); 43 | update.set("userId", userId); 44 | return mongoTemplate.updateFirst(Query.query(Criteria.where("oauth2UserId").is(oauth2UserId)), 45 | update , ProvidedOAuth2User.class).wasAcknowledged(); 46 | } 47 | 48 | public Optional findProvidedOAuth2User(String userId, ProvidedOAuth2User.Provider provider) { 49 | return Optional.of(mongoTemplate.findOne( 50 | Query.query(Criteria.where("userId").is(userId).and("provider").is(provider)), 51 | ProvidedOAuth2User.class)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /user-oauth2-support/src/test/java/com/sp/sec/user/oauth2/ExtendedUserServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2; 2 | 3 | 4 | import com.sp.sec.user.domain.Authority; 5 | import com.sp.sec.user.oauth2.domain.ExtendedUser; 6 | import com.sp.sec.user.oauth2.repository.ExtendedUserRepository; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; 12 | import org.springframework.dao.DuplicateKeyException; 13 | 14 | import java.util.List; 15 | 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | import static org.junit.jupiter.api.Assertions.assertThrows; 18 | 19 | @DataMongoTest 20 | public class ExtendedUserServiceTest extends WithExtendedUserTest { 21 | 22 | @Autowired 23 | private ExtendedUserRepository extendedUserRepository; 24 | 25 | @BeforeEach 26 | void before(){ 27 | prepareUserService(); 28 | } 29 | 30 | @DisplayName("1. 확장 사용자를 생성한다.") 31 | @Test 32 | void test_1() { 33 | userTestHelper.createUser("user1"); 34 | List userList = this.extendedUserRepository.findAll(); 35 | 36 | assertEquals(1, userList.size()); 37 | userTestHelper.assertUser(userList.get(0), "user1"); 38 | } 39 | 40 | 41 | @DisplayName("2. 확장 사용자의 이름을 수정한다.") 42 | @Test 43 | void test_2() { 44 | ExtendedUser user1 = userTestHelper.createUser("user1"); 45 | userService.updateUserName(user1.getUserId(), "user2"); 46 | 47 | ExtendedUser savedUser = userService.findExtendedUser(user1.getUserId()).get(); 48 | assertEquals("user2", savedUser.getName()); 49 | } 50 | 51 | @DisplayName("3. authority를 부여한다.") 52 | @Test 53 | void test_3() { 54 | ExtendedUser user1 = userTestHelper.createUser("user1", Authority.ROLE_USER); 55 | userService.addAuthority(user1.getUserId(), Authority.ROLE_ADMIN); 56 | ExtendedUser savedUser = userService.findExtendedUser(user1.getUserId()).get(); 57 | userTestHelper.assertUser(savedUser, "user1", Authority.ROLE_USER, Authority.ROLE_ADMIN); 58 | } 59 | 60 | 61 | @DisplayName("4. authority를 뺏는다.") 62 | @Test 63 | void test_4() { 64 | ExtendedUser user1 = userTestHelper.createUser("admin", Authority.ROLE_USER, Authority.ROLE_ADMIN); 65 | userService.removeAuthority(user1.getUserId(), Authority.ROLE_USER); 66 | ExtendedUser savedUser = userService.findExtendedUser(user1.getUserId()).get(); 67 | userTestHelper.assertUser(savedUser, "admin", Authority.ROLE_ADMIN); 68 | } 69 | 70 | @DisplayName("5. email 로 검색이 된다.") 71 | @Test 72 | void test_5() { 73 | ExtendedUser user1 = userTestHelper.createUser("user1"); 74 | ExtendedUser saved = (ExtendedUser) userService.loadUserByUsername("user1@test.com"); 75 | userTestHelper.assertUser(saved, "user1"); 76 | } 77 | 78 | @DisplayName("6. role이 중복되서 추가되지 않는다.") 79 | @Test 80 | void test_6() { 81 | ExtendedUser user1 = userTestHelper.createUser("user1", Authority.ROLE_USER); 82 | userService.addAuthority(user1.getUserId(), Authority.ROLE_USER); 83 | userService.addAuthority(user1.getUserId(), Authority.ROLE_USER); 84 | ExtendedUser savedUser = userService.findExtendedUser(user1.getUserId()).get(); 85 | userTestHelper.assertUser(savedUser, "user1", Authority.ROLE_USER); 86 | } 87 | 88 | @DisplayName("7. email이 중복되어서 들어가는가?") 89 | @Test 90 | void test_() { 91 | userTestHelper.createUser("user1"); 92 | assertThrows(DuplicateKeyException.class, ()->{ 93 | userTestHelper.createUser("user1"); 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /user-oauth2-support/src/test/java/com/sp/sec/user/oauth2/ExtendedUserTestHelper.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2; 2 | 3 | 4 | import com.sp.sec.user.domain.Authority; 5 | import com.sp.sec.user.domain.User; 6 | import com.sp.sec.user.oauth2.domain.ExtendedUser; 7 | import com.sp.sec.user.oauth2.service.ExtendedUserService; 8 | import com.sp.sec.user.service.UserService; 9 | import lombok.AllArgsConstructor; 10 | import org.springframework.dao.DuplicateKeyException; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | 13 | import java.util.Map; 14 | import java.util.stream.Collectors; 15 | import java.util.stream.Stream; 16 | 17 | import static org.junit.jupiter.api.Assertions.*; 18 | 19 | @AllArgsConstructor 20 | public class ExtendedUserTestHelper { 21 | 22 | private final ExtendedUserService userService; 23 | 24 | private final PasswordEncoder passwordEncoder; 25 | 26 | public ExtendedUser createUser(String name) throws DuplicateKeyException { 27 | ExtendedUser user = ExtendedUser.of(User.builder() 28 | .name(name) 29 | .email(name+"@test.com") 30 | .password(passwordEncoder.encode(name+"123")) 31 | .enabled(true) 32 | .build()); 33 | user.setAttributes(Map.of("from", "oauth2_support")); 34 | return (ExtendedUser) userService.save(user); 35 | } 36 | 37 | public ExtendedUser createUser(String name, String... authorities){ 38 | ExtendedUser user = createUser(name); 39 | Stream.of(authorities).forEach(auth->userService.addAuthority(user.getUserId(), auth)); 40 | return user; 41 | } 42 | 43 | public static void assertUser(ExtendedUser user, String name){ 44 | assertNotNull(user.getUserId()); 45 | assertNotNull(user.getCreated()); 46 | assertNotNull(user.getUpdated()); 47 | assertTrue(user.isEnabled()); 48 | assertEquals(name, user.getName()); 49 | assertEquals(name+"@test.com", user.getEmail()); 50 | // assertEquals(name+"123", user.getPassword()); 51 | assertEquals("oauth2_support", user.getAttribute("from")); 52 | } 53 | 54 | public static void assertUser(ExtendedUser user, String name, String... authorities){ 55 | assertUser(user, name); 56 | assertTrue(user.getAuthorities().containsAll( 57 | Stream.of(authorities).map(auth->new Authority(auth)).collect(Collectors.toList()) 58 | )); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /user-oauth2-support/src/test/java/com/sp/sec/user/oauth2/FacebookUserLoginTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2; 2 | 3 | 4 | import com.sp.sec.user.oauth2.domain.ExtendedUser; 5 | import com.sp.sec.user.oauth2.domain.ProvidedOAuth2User; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; 10 | 11 | import java.util.List; 12 | 13 | import static org.junit.jupiter.api.Assertions.*; 14 | 15 | @DataMongoTest 16 | public class FacebookUserLoginTest extends WithExtendedUserTest{ 17 | 18 | ExtendedUser user; 19 | 20 | @BeforeEach 21 | void before(){ 22 | prepareUserService(); 23 | this.user = userService.registerOAuth2User(null, OAuth2UserSample.facebookUser, 24 | ProvidedOAuth2User.Provider.facebook); 25 | } 26 | 27 | @DisplayName("1. 사이트에 가입하지 않은 사용자가 페이스북 사용자로 로그인 하면 사용자가로 등록 된다.") 28 | @Test 29 | void test_1() { 30 | assertNotNull(user.getUserId()); 31 | assertEquals("Jongwon Choi", user.getName()); 32 | assertNull( user.getPicUrl()); 33 | assertEquals("jongwons.choi@gmail.com", user.getEmail()); 34 | 35 | List list = providedOAuth2UserRepository.findAll(); 36 | assertEquals(1, list.size()); 37 | ProvidedOAuth2User facebookUser = list.get(0); 38 | assertEquals("facebook_4000026893357972", facebookUser.getOauth2UserId()); 39 | assertEquals("Jongwon Choi", facebookUser.getName()); 40 | assertNull(facebookUser.getPicUrl()); 41 | assertEquals("jongwons.choi@gmail.com", facebookUser.getEmail()); 42 | 43 | assertNotNull(facebookUser.getRegistered()); 44 | assertNotNull(facebookUser.getLastLoggedIn()); 45 | assertEquals(user.getUserId(), facebookUser.getUserId()); 46 | assertEquals(ProvidedOAuth2User.Provider.facebook, facebookUser.getProvider()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /user-oauth2-support/src/test/java/com/sp/sec/user/oauth2/GoogleUserLoginTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2; 2 | 3 | 4 | import com.sp.sec.user.oauth2.domain.ExtendedUser; 5 | import com.sp.sec.user.oauth2.domain.ProvidedOAuth2User; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; 10 | 11 | import java.util.List; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | import static org.junit.jupiter.api.Assertions.assertNotNull; 15 | 16 | @DataMongoTest 17 | public class GoogleUserLoginTest extends WithExtendedUserTest{ 18 | 19 | ExtendedUser user; 20 | 21 | @BeforeEach 22 | void before(){ 23 | prepareUserService(); 24 | this.user = userService.registerOAuth2User(null, OAuth2UserSample.googleUser, 25 | ProvidedOAuth2User.Provider.google); 26 | } 27 | 28 | @DisplayName("1. 사이트에 가입하지 않은 사용자가 구글 사용자로 로그인 하면 사용자가로 등록 된다.") 29 | @Test 30 | void test_1() { 31 | assertNotNull(user.getUserId()); 32 | assertEquals("옥탑방개발자", user.getName()); 33 | assertEquals("https://lh3.googleusercontent.com/a-/AOh14GgFLv4rMtdDUyBFDgsJggHdCK5IuKSLuOq9OwwLDyc=s96-c", user.getPicUrl()); 34 | assertEquals("jongwons.choi@gmail.com", user.getEmail()); 35 | 36 | List list = providedOAuth2UserRepository.findAll(); 37 | assertEquals(1, list.size()); 38 | ProvidedOAuth2User googleInfo = list.get(0); 39 | assertEquals("google_113976141374150070219", googleInfo.getOauth2UserId()); 40 | assertEquals("옥탑방개발자", googleInfo.getName()); 41 | assertEquals("https://lh3.googleusercontent.com/a-/AOh14GgFLv4rMtdDUyBFDgsJggHdCK5IuKSLuOq9OwwLDyc=s96-c", googleInfo.getPicUrl()); 42 | assertEquals("jongwons.choi@gmail.com", googleInfo.getEmail()); 43 | 44 | assertNotNull(googleInfo.getRegistered()); 45 | assertNotNull(googleInfo.getLastLoggedIn()); 46 | assertEquals(user.getUserId(), googleInfo.getUserId()); 47 | assertEquals(ProvidedOAuth2User.Provider.google, googleInfo.getProvider()); 48 | } 49 | 50 | @DisplayName("2. ProvidedOAuth2User 와 ExtendedUser 가 잘 링크된다.") 51 | @Test 52 | void test_2() { 53 | List providedOAuth2UserList = userService.getProvidedOAuth2UserList(user.getUserId()); 54 | assertEquals(1, providedOAuth2UserList.size()); 55 | assertEquals(ProvidedOAuth2User.Provider.google, providedOAuth2UserList.get(0).getProvider()); 56 | } 57 | 58 | @DisplayName("3. 다시 로그인을 하더라도 새로운 사용자가 등록되지 않는다.") 59 | @Test 60 | void test_3() { 61 | ExtendedUser loginAgain = userService.registerOAuth2User(null, OAuth2UserSample.googleUser, 62 | ProvidedOAuth2User.Provider.google); 63 | assertEquals(user.getUserId(), loginAgain.getUserId()); 64 | } 65 | 66 | 67 | @DisplayName("4. 구글로 로그인한 다음, 해당 유저로 네이버, 카카오, 페이스북을 차례로 로그인해 계정을 연결할 수 있다.") 68 | @Test 69 | void test_4() { 70 | userService.registerOAuth2User(user, OAuth2UserSample.facebookUser, 71 | ProvidedOAuth2User.Provider.facebook); 72 | assertEquals(1, userRepository.findAll().size()); 73 | assertEquals(2, userService.getProvidedOAuth2UserList(user.getUserId()).size()); 74 | 75 | userService.registerOAuth2User(user, OAuth2UserSample.naverUser, 76 | ProvidedOAuth2User.Provider.naver); 77 | assertEquals(1, userRepository.findAll().size()); 78 | assertEquals(3, userService.getProvidedOAuth2UserList(user.getUserId()).size()); 79 | 80 | userService.registerOAuth2User(user, OAuth2UserSample.kakaoUser, 81 | ProvidedOAuth2User.Provider.kakao); 82 | assertEquals(1, userRepository.findAll().size()); 83 | assertEquals(4, userService.getProvidedOAuth2UserList(user.getUserId()).size()); 84 | 85 | } 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /user-oauth2-support/src/test/java/com/sp/sec/user/oauth2/KakaoUserLoginTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2; 2 | 3 | import com.sp.sec.user.oauth2.domain.ExtendedUser; 4 | import com.sp.sec.user.oauth2.domain.ProvidedOAuth2User; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; 9 | 10 | import java.util.List; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertNotNull; 14 | 15 | @DataMongoTest 16 | public class KakaoUserLoginTest extends WithExtendedUserTest{ 17 | 18 | ExtendedUser user; 19 | 20 | @BeforeEach 21 | void before(){ 22 | prepareUserService(); 23 | this.user = userService.registerOAuth2User(null, OAuth2UserSample.kakaoUser, 24 | ProvidedOAuth2User.Provider.kakao); 25 | } 26 | 27 | @DisplayName("1. 사이트에 가입하지 않은 사용자가 구글 사용자로 로그인 하면 사용자가로 등록 된다.") 28 | @Test 29 | void test_1() { 30 | assertNotNull(user.getUserId()); 31 | assertEquals("jongwon", user.getName()); 32 | assertEquals("http://k.kakaocdn.net/dn/XQHgC/btqyj3C5jCQ/KjiijMK462WPrRrnkoOtY0/img_110x110.jpg", user.getPicUrl()); 33 | assertEquals("jongwons.choi@kakao.com", user.getEmail()); 34 | 35 | List list = providedOAuth2UserRepository.findAll(); 36 | assertEquals(1, list.size()); 37 | ProvidedOAuth2User kakaoUser = list.get(0); 38 | assertEquals("kakao_1534230750", kakaoUser.getOauth2UserId()); 39 | assertEquals("jongwon", kakaoUser.getName()); 40 | assertEquals("http://k.kakaocdn.net/dn/XQHgC/btqyj3C5jCQ/KjiijMK462WPrRrnkoOtY0/img_110x110.jpg", kakaoUser.getPicUrl()); 41 | assertEquals("jongwons.choi@kakao.com", kakaoUser.getEmail()); 42 | 43 | assertNotNull(kakaoUser.getRegistered()); 44 | assertNotNull(kakaoUser.getLastLoggedIn()); 45 | assertEquals(user.getUserId(), kakaoUser.getUserId()); 46 | assertEquals(ProvidedOAuth2User.Provider.kakao, kakaoUser.getProvider()); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /user-oauth2-support/src/test/java/com/sp/sec/user/oauth2/NaverUserLoginTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2; 2 | 3 | import com.sp.sec.user.oauth2.domain.ExtendedUser; 4 | import com.sp.sec.user.oauth2.domain.ProvidedOAuth2User; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; 9 | 10 | import java.util.List; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertNotNull; 14 | 15 | 16 | @DataMongoTest 17 | public class NaverUserLoginTest extends WithExtendedUserTest { 18 | 19 | ExtendedUser user; 20 | 21 | @BeforeEach 22 | void before(){ 23 | prepareUserService(); 24 | this.user = userService.registerOAuth2User(null, OAuth2UserSample.naverUser, 25 | ProvidedOAuth2User.Provider.naver); 26 | } 27 | 28 | @DisplayName("1. 사이트에 가입하지 않은 사용자가 구글 사용자로 로그인 하면 사용자가로 등록 된다.") 29 | @Test 30 | void test_1() { 31 | assertNotNull(user.getUserId()); 32 | assertEquals("최종원", user.getName()); 33 | assertEquals("https://phinf.pstatic.net/contact/20180308_276/1520490317846up6kA_PNG/avatar_profile.png", user.getPicUrl()); 34 | assertEquals("jongwons.choi@gmail.com", user.getEmail()); 35 | 36 | List list = providedOAuth2UserRepository.findAll(); 37 | assertEquals(1, list.size()); 38 | ProvidedOAuth2User naverUser = list.get(0); 39 | assertEquals("naver_18997705", naverUser.getOauth2UserId()); 40 | assertEquals("최종원", naverUser.getName()); 41 | assertEquals("https://phinf.pstatic.net/contact/20180308_276/1520490317846up6kA_PNG/avatar_profile.png", naverUser.getPicUrl()); 42 | assertEquals("jongwons.choi@gmail.com", naverUser.getEmail()); 43 | 44 | assertNotNull(naverUser.getRegistered()); 45 | assertNotNull(naverUser.getLastLoggedIn()); 46 | assertEquals(user.getUserId(), naverUser.getUserId()); 47 | assertEquals(ProvidedOAuth2User.Provider.naver, naverUser.getProvider()); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /user-oauth2-support/src/test/java/com/sp/sec/user/oauth2/OAuth2UserSample.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2; 2 | 3 | import com.sp.sec.user.domain.Authority; 4 | import org.springframework.security.oauth2.core.user.DefaultOAuth2User; 5 | import org.springframework.security.oauth2.core.user.OAuth2User; 6 | 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | public class OAuth2UserSample { 11 | 12 | public static OAuth2User googleUser = new DefaultOAuth2User(Set.of(Authority.USER), 13 | Map.of( 14 | "name", "옥탑방개발자", 15 | "sub", "113976141374150070219", 16 | "picture", "https://lh3.googleusercontent.com/a-/AOh14GgFLv4rMtdDUyBFDgsJggHdCK5IuKSLuOq9OwwLDyc=s96-c", 17 | "email", "jongwons.choi@gmail.com" 18 | ), "sub"); 19 | 20 | 21 | public static OAuth2User facebookUser = new DefaultOAuth2User(Set.of(Authority.USER), 22 | Map.of("id", "4000026893357972", 23 | "name", "Jongwon Choi", 24 | "email", "jongwons.choi@gmail.com"), "id"); 25 | 26 | public static OAuth2User naverUser = new DefaultOAuth2User(Set.of(Authority.USER), 27 | Map.of( 28 | "response", Map.of( 29 | "id", "18997705", 30 | "nickname", "슈타인", 31 | "profile_image", "https://phinf.pstatic.net/contact/20180308_276/1520490317846up6kA_PNG/avatar_profile.png", 32 | "email", "jongwons.choi@gmail.com", 33 | "name", "최종원" 34 | ) 35 | ), "response"); 36 | 37 | public static OAuth2User kakaoUser = new DefaultOAuth2User(Set.of(Authority.USER), 38 | Map.of( 39 | "id", 1534230750, 40 | "kakao_account", Map.of( 41 | "profile", Map.of( 42 | "nickname", "jongwon", 43 | "thumbnail_image_url", "http://k.kakaocdn.net/dn/XQHgC/btqyj3C5jCQ/KjiijMK462WPrRrnkoOtY0/img_110x110.jpg", 44 | "profile_image_url", "http://k.kakaocdn.net/dn/XQHgC/btqyj3C5jCQ/KjiijMK462WPrRrnkoOtY0/img_640x640.jpg" 45 | 46 | ), 47 | "email", "jongwons.choi@kakao.com" 48 | ) 49 | ), "id"); 50 | 51 | } 52 | -------------------------------------------------------------------------------- /user-oauth2-support/src/test/java/com/sp/sec/user/oauth2/UserOAuth2SupportApp.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 7 | 8 | @SpringBootApplication 9 | public class UserOAuth2SupportApp { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(UserOAuth2SupportApp.class, args); 13 | } 14 | 15 | @Configuration 16 | @EnableMongoRepositories(basePackages = { 17 | "com.sp.sec.user.repository", 18 | "com.sp.sec.user.oauth2.repository" 19 | }) 20 | class MongoConfig {} 21 | 22 | } 23 | -------------------------------------------------------------------------------- /user-oauth2-support/src/test/java/com/sp/sec/user/oauth2/WithExtendedUserTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user.oauth2; 2 | 3 | import com.sp.sec.user.oauth2.repository.ExtendedUserRepository; 4 | import com.sp.sec.user.oauth2.repository.ProvidedOAuth2UserRepository; 5 | import com.sp.sec.user.oauth2.service.ExtendedUserService; 6 | import com.sp.sec.user.oauth2.service.ProvidedOAuth2UserService; 7 | import com.sp.sec.user.repository.UserRepository; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.mongodb.core.MongoTemplate; 10 | import org.springframework.security.crypto.password.NoOpPasswordEncoder; 11 | 12 | public class WithExtendedUserTest { 13 | 14 | @Autowired 15 | protected MongoTemplate mongoTemplate; 16 | 17 | @Autowired 18 | protected UserRepository userRepository; 19 | 20 | @Autowired 21 | protected ExtendedUserRepository extendedUserRepository; 22 | 23 | @Autowired 24 | protected ProvidedOAuth2UserRepository providedOAuth2UserRepository; 25 | 26 | protected ProvidedOAuth2UserService providedOAuth2UserService; 27 | 28 | protected ExtendedUserService userService; 29 | 30 | protected ExtendedUserTestHelper userTestHelper; 31 | 32 | protected void prepareUserService(){ 33 | this.userRepository.deleteAll(); 34 | this.providedOAuth2UserRepository.deleteAll(); 35 | this.providedOAuth2UserService = new ProvidedOAuth2UserService(mongoTemplate, providedOAuth2UserRepository); 36 | this.userService = new ExtendedUserService(mongoTemplate, userRepository, 37 | extendedUserRepository, 38 | providedOAuth2UserService); 39 | this.userTestHelper = new ExtendedUserTestHelper(userService, NoOpPasswordEncoder.getInstance()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /web/auth-server-1/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jongwon/spring-security-junit5-test/8398bcfcc938cf1709496b099cd8e50270ec3dcf/web/auth-server-1/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /web/auth-server-1/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /web/auth-server-1/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.0 9 | 10 | 11 | com.sp.sec 12 | auth-server-1 13 | 1.0.0 14 | auth-server-1 15 | 테스트 인증서버 1 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-mongodb 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-security 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | true 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | org.junit.vintage 47 | junit-vintage-engine 48 | 49 | 50 | 51 | 52 | de.flapdoodle.embed 53 | de.flapdoodle.embed.mongo 54 | test 55 | 56 | 57 | org.springframework.security 58 | spring-security-test 59 | test 60 | 61 | 62 | 63 | com.sp.sec 64 | user-authority 65 | 1.0.0 66 | 67 | 68 | 69 | com.sp.sec 70 | user-authority 71 | 1.0.0 72 | test-jar 73 | test 74 | 75 | 76 | 77 | com.sp.sec 78 | sp-jwt-security 79 | 1.0.0 80 | 81 | 82 | 83 | com.sp.sec 84 | sp-jwt-security 85 | 1.0.0 86 | test-jar 87 | test 88 | 89 | 90 | 91 | 92 | com.sp.sec 93 | sp-web-util 94 | 1.0.0 95 | 96 | 97 | 98 | 99 | 100 | org.springframework.boot 101 | spring-boot-maven-plugin 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /web/auth-server-1/src/main/java/com/sp/sec/web/AuthServer1Application.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication(scanBasePackages = { 7 | "com.sp.sec.config", 8 | "com.sp.sec.web" 9 | }) 10 | public class AuthServer1Application { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(AuthServer1Application.class, args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /web/auth-server-1/src/main/java/com/sp/sec/web/config/DBInit.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.sp.sec.user.domain.Authority; 4 | import com.sp.sec.user.domain.User; 5 | import com.sp.sec.user.service.UserService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.CommandLineRunner; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.Set; 12 | 13 | @Component 14 | public class DBInit implements CommandLineRunner { 15 | 16 | @Autowired 17 | private UserService userService; 18 | 19 | @Autowired 20 | private PasswordEncoder passwordEncoder; 21 | 22 | @Override 23 | public void run(String... args) throws Exception { 24 | userService.clearUsers(); 25 | User user1 = User.builder().name("user1") 26 | .email("user1@test.com") 27 | .password(passwordEncoder.encode("1234")) 28 | .enabled(true) 29 | .authorities(Set.of(Authority.USER)) 30 | .build(); 31 | User admin = User.builder().name("admin") 32 | .email("admin@test.com") 33 | .password(passwordEncoder.encode("admin")) 34 | .enabled(true) 35 | .authorities(Set.of(Authority.ADMIN)) 36 | .build(); 37 | 38 | userService.save(user1); 39 | userService.save(admin); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /web/auth-server-1/src/main/java/com/sp/sec/web/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sp.sec.user.service.UserService; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.security.authentication.AuthenticationManager; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.config.http.SessionCreationPolicy; 13 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 14 | 15 | @EnableWebSecurity 16 | @EnableGlobalMethodSecurity(prePostEnabled = true) 17 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 18 | 19 | private final UserService userService; 20 | private final ObjectMapper objectMapper; 21 | private final JWTUtil jwtUtil; 22 | 23 | public SecurityConfig(UserService userService, ObjectMapper objectMapper, JWTUtil jwtUtil) { 24 | this.userService = userService; 25 | this.objectMapper = objectMapper; 26 | this.jwtUtil = jwtUtil; 27 | } 28 | 29 | 30 | @Override 31 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 32 | auth.userDetailsService(userService) 33 | .passwordEncoder(passwordEncoder()); 34 | } 35 | 36 | @Bean 37 | BCryptPasswordEncoder passwordEncoder(){ 38 | return new BCryptPasswordEncoder(); 39 | } 40 | 41 | @Override 42 | protected AuthenticationManager authenticationManager() throws Exception { 43 | return super.authenticationManager(); 44 | } 45 | 46 | @Override 47 | protected void configure(HttpSecurity http) throws Exception { 48 | final RefreshableJWTLoginFilter loginFilter = new RefreshableJWTLoginFilter( 49 | authenticationManager(), userService, jwtUtil, objectMapper); 50 | http 51 | .csrf().disable() 52 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 53 | .and() 54 | .addFilter(loginFilter) 55 | ; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /web/auth-server-1/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9001 3 | 4 | 5 | sp: 6 | jwt: 7 | secret: auth-server-1 8 | token-life-time: 100 9 | token-refresh-time: 10000 10 | 11 | 12 | spring: 13 | data: 14 | mongodb: 15 | database: auth-server-1 16 | host: localhost 17 | port: 27018 18 | -------------------------------------------------------------------------------- /web/auth-server-1/src/test/java/com/sp/sec/web/AuthServer1ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class AuthServer1ApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /web/google-client-4/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jongwon/spring-security-junit5-test/8398bcfcc938cf1709496b099cd8e50270ec3dcf/web/google-client-4/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /web/google-client-4/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /web/google-client-4/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.0 9 | 10 | 11 | com.sp.sec 12 | google-client-4 13 | 1.0.0 14 | google-client-4 15 | 구글 클라이언트 4 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-mongodb 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-oauth2-client 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-security 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-web 37 | 38 | 39 | 40 | org.projectlombok 41 | lombok 42 | true 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-test 47 | test 48 | 49 | 50 | de.flapdoodle.embed 51 | de.flapdoodle.embed.mongo 52 | test 53 | 54 | 55 | org.springframework.security 56 | spring-security-test 57 | test 58 | 59 | 60 | 61 | com.sp.sec 62 | user-authority 63 | 1.0.0 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-maven-plugin 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /web/google-client-4/src/main/java/com/sp/sec/web/GoogleClient4Application.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication(scanBasePackages = { 7 | "com.sp.sec.config", 8 | "com.sp.sec.web" 9 | }) 10 | public class GoogleClient4Application { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(GoogleClient4Application.class, args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /web/google-client-4/src/main/java/com/sp/sec/web/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 7 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 8 | import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; 9 | import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter; 10 | 11 | @EnableWebSecurity 12 | @EnableGlobalMethodSecurity(prePostEnabled = true) 13 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 14 | 15 | // CommonOAuth2Provider provider; 16 | 17 | @Autowired 18 | private SpGoogleUser spGoogleUser; 19 | 20 | @Autowired 21 | private SpGoogleUserToMyUserFilter googleUserToMyUserFilter; 22 | 23 | @Override 24 | protected void configure(HttpSecurity http) throws Exception { 25 | http 26 | .oauth2Login(oauth->{ 27 | oauth.userInfoEndpoint(userinfo->{ 28 | userinfo.oidcUserService(spGoogleUser); 29 | }); 30 | }) 31 | .addFilterAfter(googleUserToMyUserFilter, OAuth2LoginAuthenticationFilter.class) 32 | ; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web/google-client-4/src/main/java/com/sp/sec/web/config/SpGoogleUser.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; 4 | import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; 5 | import org.springframework.security.oauth2.core.OAuth2AuthenticationException; 6 | import org.springframework.security.oauth2.core.oidc.user.OidcUser; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class SpGoogleUser extends OidcUserService { 11 | 12 | @Override 13 | public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { 14 | OidcUser googleUser = super.loadUser(userRequest); 15 | return googleUser; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /web/google-client-4/src/main/java/com/sp/sec/web/config/SpGoogleUserToMyUserFilter.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.sp.sec.user.domain.Authority; 4 | import com.sp.sec.user.domain.User; 5 | import com.sp.sec.user.service.UserService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.core.context.SecurityContextHolder; 10 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; 11 | import org.springframework.security.oauth2.core.oidc.user.OidcUser; 12 | import org.springframework.stereotype.Component; 13 | 14 | import javax.servlet.*; 15 | import java.io.IOException; 16 | import java.util.Set; 17 | 18 | @Component 19 | public class SpGoogleUserToMyUserFilter implements Filter { 20 | 21 | @Autowired 22 | private UserService userService; 23 | 24 | @Override 25 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 26 | Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 27 | if(auth instanceof OAuth2AuthenticationToken){ 28 | OidcUser googleUser = (OidcUser)((OAuth2AuthenticationToken) auth).getPrincipal(); 29 | User user = userService.findUser("google_"+googleUser.getSubject()) 30 | .orElseGet(()-> 31 | userService.save(User.builder() 32 | .userId("google_"+googleUser.getSubject()) 33 | .email(googleUser.getEmail()) 34 | .authorities(Set.of(Authority.USER, new Authority("FROM_GOOGLE"))) 35 | .enabled(true) 36 | .build()) 37 | ); 38 | SecurityContextHolder.getContext().setAuthentication( 39 | new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()) 40 | ); 41 | } 42 | filterChain.doFilter(servletRequest, servletResponse); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /web/google-client-4/src/main/java/com/sp/sec/web/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.controller; 2 | 3 | 4 | import com.sp.sec.user.domain.User; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.access.prepost.PreAuthorize; 7 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 8 | import org.springframework.security.oauth2.core.user.OAuth2User; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RestController 13 | public class HomeController { 14 | 15 | @Autowired 16 | private SecuredService securedService; 17 | 18 | @PreAuthorize("isAuthenticated()") 19 | @GetMapping("/") 20 | public String home(@AuthenticationPrincipal User user){ 21 | return securedService.secured(); 22 | } 23 | 24 | 25 | @PreAuthorize("isAuthenticated()") 26 | @GetMapping("/user") 27 | public OAuth2User user(@AuthenticationPrincipal OAuth2User user){ 28 | 29 | return user; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /web/google-client-4/src/main/java/com/sp/sec/web/controller/SecuredService.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.controller; 2 | 3 | 4 | import org.springframework.security.access.prepost.PreAuthorize; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | public class SecuredService { 9 | 10 | @PreAuthorize("hasAnyAuthority('FROM_GOOGLE')") 11 | public String secured(){ 12 | return "secured info : 옥수수"; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /web/google-client-4/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9006 3 | spring: 4 | security: 5 | oauth2: 6 | client: 7 | registration: 8 | google: 9 | client-id: 377825014181-2tpha7niq0hjar86e4j8h6dl0h8j1s3i.apps.googleusercontent.com 10 | client-secret: yyHXhXWBRrx3jyDWrhiSwzu8 11 | data: 12 | mongodb: 13 | database: google-client-4 14 | host: localhost 15 | port: 27018 16 | -------------------------------------------------------------------------------- /web/google-client-4/src/test/java/com/sp/sec/web/GoogleClient4ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class GoogleClient4ApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /web/jwt-refresh-token-test/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jongwon/spring-security-junit5-test/8398bcfcc938cf1709496b099cd8e50270ec3dcf/web/jwt-refresh-token-test/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /web/jwt-refresh-token-test/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /web/jwt-refresh-token-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.0 9 | 10 | 11 | com.sp.sec 12 | jwt-refresh-token-test 13 | 1.0.0 14 | jwt-refresh-token-test 15 | jwt-refresh-token-test 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-mongodb 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-security 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | true 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | org.junit.vintage 47 | junit-vintage-engine 48 | 49 | 50 | 51 | 52 | de.flapdoodle.embed 53 | de.flapdoodle.embed.mongo 54 | test 55 | 56 | 57 | org.springframework.security 58 | spring-security-test 59 | test 60 | 61 | 62 | 63 | com.sp.sec 64 | user-authority 65 | 1.0.0 66 | 67 | 68 | 69 | com.sp.sec 70 | user-authority 71 | 1.0.0 72 | test-jar 73 | test 74 | 75 | 76 | 77 | com.sp.sec 78 | sp-board 79 | 1.0.0 80 | 81 | 82 | 83 | com.sp.sec 84 | sp-board 85 | 1.0.0 86 | test-jar 87 | test 88 | 89 | 90 | 91 | 92 | com.sp.sec 93 | sp-jwt-security 94 | 1.0.0 95 | 96 | 97 | 98 | com.sp.sec 99 | sp-jwt-security 100 | 1.0.0 101 | test-jar 102 | test 103 | 104 | 105 | 106 | 107 | com.sp.sec 108 | sp-web-util 109 | 1.0.0 110 | 111 | 112 | 113 | 114 | 115 | 116 | org.springframework.boot 117 | spring-boot-maven-plugin 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /web/jwt-refresh-token-test/src/main/java/com/sp/sec/web/JwtRefreshTokenTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication(scanBasePackages = { 7 | "com.sp.sec.config", 8 | "com.sp.sec.web" 9 | }) 10 | public class JwtRefreshTokenTestApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(JwtRefreshTokenTestApplication.class, args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /web/jwt-refresh-token-test/src/main/java/com/sp/sec/web/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sp.sec.user.service.UserService; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.security.authentication.AuthenticationManager; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.config.http.SessionCreationPolicy; 13 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 14 | 15 | @EnableWebSecurity 16 | @EnableGlobalMethodSecurity(prePostEnabled = true) 17 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 18 | 19 | private final UserService userService; 20 | private final ObjectMapper objectMapper; 21 | private final JWTUtil jwtUtil; 22 | 23 | public SecurityConfig(UserService userService, ObjectMapper objectMapper, JWTUtil jwtUtil) { 24 | this.userService = userService; 25 | this.objectMapper = objectMapper; 26 | this.jwtUtil = jwtUtil; 27 | } 28 | 29 | 30 | @Override 31 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 32 | auth.userDetailsService(userService) 33 | .passwordEncoder(passwordEncoder()); 34 | } 35 | 36 | @Bean 37 | BCryptPasswordEncoder passwordEncoder(){ 38 | return new BCryptPasswordEncoder(); 39 | } 40 | 41 | @Override 42 | protected AuthenticationManager authenticationManager() throws Exception { 43 | return super.authenticationManager(); 44 | } 45 | 46 | @Override 47 | protected void configure(HttpSecurity http) throws Exception { 48 | final RefreshableJWTLoginFilter loginFilter = new RefreshableJWTLoginFilter( 49 | authenticationManager(), userService, jwtUtil, objectMapper); 50 | final JWTCheckFilter checkFilter = new JWTCheckFilter(authenticationManager(), userService, jwtUtil); 51 | 52 | http 53 | .csrf().disable() 54 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 55 | .and() 56 | .addFilter(loginFilter) 57 | .addFilter(checkFilter) 58 | ; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /web/jwt-refresh-token-test/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /web/jwt-refresh-token-test/src/test/java/com/sp/sec/web/RefreshTokenTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.core.type.TypeReference; 5 | import com.sp.sec.board.domain.SpBoard; 6 | import com.sp.sec.board.domain.SpBoardSummary; 7 | import com.sp.sec.board.service.SpBoardService; 8 | import com.sp.sec.board.service.SpBoardTestHelper; 9 | import com.sp.sec.web.config.JWTUtil; 10 | import com.sp.sec.web.util.RestResponsePage; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.DisplayName; 13 | import org.junit.jupiter.api.Test; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.http.HttpMethod; 17 | import org.springframework.http.ResponseEntity; 18 | import org.springframework.web.client.HttpClientErrorException; 19 | 20 | import java.net.URISyntaxException; 21 | 22 | import static org.junit.jupiter.api.Assertions.assertEquals; 23 | import static org.junit.jupiter.api.Assertions.assertThrows; 24 | 25 | /** 26 | * user1 이 두개의 게시물을 올린다. 27 | * 28 | */ 29 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 30 | public class RefreshTokenTest extends SpJwtRefreshableTwoUserIntegrationTest{ 31 | 32 | @Autowired 33 | private SpBoardService boardService; 34 | 35 | @Autowired 36 | private JWTUtil jwtUtil; 37 | 38 | @BeforeEach 39 | void before() throws URISyntaxException { 40 | prepareTwoUsers(); 41 | boardService.clearBoards(); 42 | Tokens tokens = 유저로그인("user1"); 43 | 게시글을_작성한다(tokens, SpBoardTestHelper.makeBoard(USER1, "title1", "content1")); 44 | 게시글을_작성한다(tokens, SpBoardTestHelper.makeBoard(USER1, "title2", "content2")); 45 | } 46 | 47 | private void 게시글을_작성한다(Tokens tokens, SpBoard board1) throws URISyntaxException { 48 | ResponseEntity response = restTemplate.exchange(uri("/board/save"), 49 | HttpMethod.POST, getPostAuthHeaderEntity(tokens.getAccessToken(), board1), 50 | SpBoard.class); 51 | assertEquals(200, response.getStatusCodeValue()); 52 | } 53 | 54 | @DisplayName("1. user2 가 게시물을 조회하고, 일정 시간이 지나 토큰이 만료된 후 다시 조회한다.") 55 | @Test 56 | void test_1() throws URISyntaxException, JsonProcessingException, InterruptedException { 57 | 토큰타임을_1초로_맞춘다(); 58 | 59 | final Tokens 첫번째토큰 = 유저로그인("user2"); 60 | 게시판의_게시글이_2개인걸_확인한다(첫번째토큰); 61 | 62 | Thread.sleep(2000); 63 | 64 | assertThrows(HttpClientErrorException.class, ()->{ 65 | 게시판의_게시글이_2개인걸_확인한다(첫번째토큰); 66 | }); 67 | 68 | Tokens 다시얻은토큰 = getRefreshToken(첫번째토큰.getRefreshToken()); 69 | 게시판의_게시글이_2개인걸_확인한다(다시얻은토큰); 70 | } 71 | 72 | private void 게시판의_게시글이_2개인걸_확인한다(Tokens tokens) throws URISyntaxException, JsonProcessingException { 73 | ResponseEntity response = restTemplate.exchange(uri("/board/list"), 74 | HttpMethod.GET, getAuthHeaderEntity(tokens.getAccessToken()), String.class); 75 | assertEquals(200, response.getStatusCodeValue()); 76 | RestResponsePage page = objectMapper.readValue(response.getBody(), 77 | new TypeReference>() { 78 | }); 79 | assertEquals(2, page.getTotalElements()); 80 | } 81 | 82 | private Tokens 유저로그인(String name) throws URISyntaxException { 83 | return getToken(name+"@test.com", name+"123"); 84 | } 85 | 86 | private void 토큰타임을_1초로_맞춘다() { 87 | jwtUtil.getProperties().setTokenLifeTime(1); 88 | } 89 | 90 | 91 | } 92 | -------------------------------------------------------------------------------- /web/jwt-user-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.0 9 | 10 | 11 | com.sp.sec 12 | jwt-user-web 13 | 1.0.0 14 | jwt-user-web 15 | JWT 유저 웹 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-mongodb 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-security 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | true 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | org.junit.vintage 47 | junit-vintage-engine 48 | 49 | 50 | 51 | 52 | de.flapdoodle.embed 53 | de.flapdoodle.embed.mongo 54 | test 55 | 56 | 57 | org.springframework.security 58 | spring-security-test 59 | test 60 | 61 | 62 | 63 | com.auth0 64 | java-jwt 65 | 3.11.0 66 | 67 | 68 | 69 | com.sp.sec 70 | user-authority 71 | 1.0.0 72 | 73 | 74 | 75 | com.sp.sec 76 | user-authority 77 | 1.0.0 78 | test-jar 79 | test 80 | 81 | 82 | com.sp.sec 83 | sp-web-util 84 | 1.0.0 85 | compile 86 | 87 | 88 | com.sp.sec 89 | sp-jwt-security 90 | 1.0.0 91 | compile 92 | 93 | 94 | com.sp.sec 95 | sp-jwt-security 96 | 1.0.0 97 | test-jar 98 | test 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | org.springframework.boot 107 | spring-boot-maven-plugin 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /web/jwt-user-web/src/main/java/com/sp/sec/web/JwtUserWebApplication.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import com.sp.sec.web.config.SpJwtProperties; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 7 | 8 | @SpringBootApplication(scanBasePackages = { 9 | "com.sp.sec.config", 10 | "com.sp.sec.web" 11 | }) 12 | public class JwtUserWebApplication { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(JwtUserWebApplication.class, args); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /web/jwt-user-web/src/main/java/com/sp/sec/web/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sp.sec.user.service.UserService; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.security.authentication.AuthenticationManager; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | 15 | 16 | @EnableWebSecurity 17 | @EnableGlobalMethodSecurity(prePostEnabled = true) 18 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 19 | 20 | private final UserService userService; 21 | private final ObjectMapper objectMapper; 22 | private final JWTUtil jwtUtil ; 23 | 24 | public SecurityConfig(UserService userService, ObjectMapper objectMapper, JWTUtil jwtUtil) { 25 | this.userService = userService; 26 | this.objectMapper = objectMapper; 27 | this.jwtUtil = jwtUtil; 28 | } 29 | 30 | 31 | @Bean 32 | PasswordEncoder passwordEncoder(){ 33 | return new BCryptPasswordEncoder(); 34 | } 35 | 36 | @Override 37 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 38 | auth.userDetailsService(userService) 39 | .passwordEncoder(passwordEncoder()); 40 | } 41 | 42 | @Override 43 | protected AuthenticationManager authenticationManager() throws Exception { 44 | return super.authenticationManager(); 45 | } 46 | 47 | @Override 48 | protected void configure(HttpSecurity http) throws Exception { 49 | JWTLoginFilter jwtLoginFilter = new JWTLoginFilter(authenticationManager(), jwtUtil, objectMapper); 50 | JWTCheckFilter checkFilter = new JWTCheckFilter(authenticationManager(), 51 | userService, jwtUtil); 52 | http 53 | .csrf().disable() 54 | .addFilter(jwtLoginFilter) 55 | .addFilter(checkFilter) 56 | ; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /web/jwt-user-web/src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8098 3 | 4 | spring: 5 | data: 6 | mongodb: 7 | database: jwt-user-web 8 | host: localhost 9 | port: 0 10 | 11 | sp: 12 | jwt: 13 | secret: test-secret 14 | token-life-time: 3 -------------------------------------------------------------------------------- /web/jwt-user-web/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8098 3 | 4 | spring: 5 | data: 6 | mongodb: 7 | database: jwt-user-web 8 | host: localhost 9 | port: 27018 10 | 11 | sp: 12 | jwt: 13 | secret: product-secret 14 | token-life-time: 600 -------------------------------------------------------------------------------- /web/jwt-user-web/src/test/java/com/sp/sec/web/JWTLoginFilterTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.sp.sec.user.UserTestHelper; 6 | import com.sp.sec.user.domain.Authority; 7 | import com.sp.sec.user.domain.User; 8 | import com.sp.sec.user.service.UserService; 9 | import com.sp.sec.web.config.JWTUtil; 10 | import com.sp.sec.web.config.UserLogin; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.DisplayName; 13 | import org.junit.jupiter.api.Test; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.boot.web.server.LocalServerPort; 17 | import org.springframework.http.HttpEntity; 18 | import org.springframework.http.HttpMethod; 19 | import org.springframework.http.ResponseEntity; 20 | import org.springframework.security.core.parameters.P; 21 | import org.springframework.security.crypto.password.PasswordEncoder; 22 | import org.springframework.test.context.ActiveProfiles; 23 | import org.springframework.web.client.HttpClientErrorException; 24 | import org.springframework.web.client.RestTemplate; 25 | 26 | import java.net.URI; 27 | import java.net.URISyntaxException; 28 | import java.util.Set; 29 | 30 | import static java.lang.String.format; 31 | import static org.junit.jupiter.api.Assertions.assertEquals; 32 | import static org.junit.jupiter.api.Assertions.assertThrows; 33 | 34 | @ActiveProfiles("test") 35 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 36 | public class JWTLoginFilterTest { 37 | 38 | @LocalServerPort 39 | private int port; 40 | 41 | @Autowired 42 | private ObjectMapper objectMapper; 43 | 44 | @Autowired 45 | private UserService userService; 46 | @Autowired 47 | private PasswordEncoder passwordEncoder; 48 | 49 | private UserTestHelper userTestHelper; 50 | 51 | private RestTemplate restTemplate = new RestTemplate(); 52 | 53 | private URI uri(String path) throws URISyntaxException { 54 | return new URI(format("http://localhost:%d%s", port, path)); 55 | } 56 | 57 | @BeforeEach 58 | void before(){ 59 | userService.clearUsers(); 60 | this.userTestHelper = new UserTestHelper(userService, passwordEncoder); 61 | this.userTestHelper.createUser("user1", Authority.ROLE_USER); 62 | } 63 | 64 | @DisplayName("1. jwt 로 로그인을 시도한다.") 65 | @Test 66 | void test_1() throws URISyntaxException { 67 | UserLogin login = UserLogin.builder().username("user1@test.com") 68 | .password("user1123").build(); 69 | HttpEntity body = new HttpEntity<>(login); 70 | ResponseEntity response = restTemplate.exchange(uri("/login"), HttpMethod.POST, body, String.class); 71 | assertEquals(200, response.getStatusCodeValue()); 72 | } 73 | 74 | @DisplayName("2. 비번이 틀리면 로그인을 하지 못한다.") 75 | @Test 76 | void test_2() throws URISyntaxException { 77 | UserLogin login = UserLogin.builder().username("user1@test.com") 78 | .password("1234").build(); 79 | HttpEntity body = new HttpEntity<>(login); 80 | 81 | assertThrows(HttpClientErrorException.class, ()->{ 82 | restTemplate.exchange(uri("/login"), HttpMethod.POST, body, String.class); 83 | // expected 401 에러 84 | }); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /web/jwt-user-web/src/test/java/com/sp/sec/web/JWTTokenTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.algorithms.Algorithm; 5 | import com.auth0.jwt.exceptions.TokenExpiredException; 6 | import com.auth0.jwt.interfaces.Claim; 7 | import com.auth0.jwt.interfaces.DecodedJWT; 8 | import org.junit.jupiter.api.Disabled; 9 | import org.junit.jupiter.api.DisplayName; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import java.time.Instant; 13 | import java.util.Date; 14 | import java.util.Map; 15 | import java.util.stream.Collectors; 16 | import java.util.stream.Stream; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertThrows; 19 | 20 | public class JWTTokenTest { 21 | 22 | static void printClaim(String key, Claim value){ 23 | if(value.isNull()){ 24 | System.out.printf("%s:%s\n", key, "none"); 25 | return; 26 | } 27 | if(value.asString() != null){ 28 | System.out.printf("%s:{str}%s\n", key, value.asString()); 29 | return; 30 | } 31 | if(value.asLong() != null){ 32 | System.out.printf("%s:{lng}%d\n", key, value.asLong()); 33 | return; 34 | } 35 | if(value.asInt() != null ){ 36 | System.out.printf("%s:{int}%d\n", key, value.asInt()); 37 | return; 38 | } 39 | if(value.asBoolean() != null){ 40 | System.out.printf("%s:{bol}%b\n", key, value.asBoolean()); 41 | return; 42 | } 43 | if(value.asDate() != null){ 44 | System.out.printf("%s:{dte}%s\n", key, value.asDate().toString()); 45 | return; 46 | } 47 | if(value.asDouble() != null){ 48 | System.out.printf("%s:{dbl}%f\n", key, value.asDouble()); 49 | return; 50 | } 51 | String[] values = value.asArray(String.class); 52 | if(values != null){ 53 | System.out.printf("%s:{arr}%s\n", key, Stream.of(values).collect(Collectors.joining(","))); 54 | return; 55 | } 56 | Map valueMap = value.asMap(); 57 | if(valueMap != null) { 58 | System.out.printf("%s:{map}%s\n", key, valueMap); 59 | return; 60 | } 61 | System.out.println("====>> unknown type for :"+key); 62 | } 63 | 64 | @DisplayName("1. JWT 토큰이 잘 만들어 진다.") 65 | @Test 66 | @Disabled 67 | void test_() throws InterruptedException { 68 | 69 | Algorithm AL = Algorithm.HMAC256("hello"); 70 | String token = JWT.create() 71 | .withSubject("jongwon") 72 | .withClaim("exp", Instant.now().getEpochSecond()+2) 73 | .withArrayClaim("role", new String[]{"ROLE_ADMIN", "ROLE_USER"}) 74 | .sign(AL); 75 | System.out.println(token); 76 | // DecodedJWT decode = JWT.decode(token); 77 | 78 | Thread.sleep(1000); 79 | 80 | DecodedJWT decode = JWT.require(AL).build().verify(token); 81 | 82 | printClaim("typ", decode.getHeaderClaim("typ")); 83 | printClaim("alg", decode.getHeaderClaim("alg")); 84 | System.out.println("======="); 85 | decode.getClaims().forEach(JWTTokenTest::printClaim); 86 | 87 | Thread.sleep(2000); 88 | 89 | assertThrows(TokenExpiredException.class, ()->{ 90 | JWT.require(AL).build().verify(token); 91 | }); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /web/jwt-user-web/src/test/java/com/sp/sec/web/JwtUserWebApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | import org.springframework.test.context.ActiveProfiles; 6 | 7 | @ActiveProfiles("test") 8 | @SpringBootTest 9 | class JwtUserWebApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /web/oauth2-client-test/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jongwon/spring-security-junit5-test/8398bcfcc938cf1709496b099cd8e50270ec3dcf/web/oauth2-client-test/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /web/oauth2-client-test/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /web/oauth2-client-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.0 9 | 10 | 11 | com.sp.sec 12 | oauth2-client-test 13 | 1.0.0 14 | oauth2-client-test 15 | oauth2-client-test 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-oauth2-client 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-security 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | true 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | org.springframework.security 47 | spring-security-test 48 | test 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-maven-plugin 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /web/oauth2-client-test/src/main/java/com/sp/sec/web/Oauth2ClientTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Oauth2ClientTestApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Oauth2ClientTestApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /web/oauth2-client-test/src/main/java/com/sp/sec/web/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.sp.sec.web.service.SpOAuth2UserService; 4 | import com.sp.sec.web.service.SpOidcUserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 11 | import org.springframework.security.core.userdetails.User; 12 | import org.springframework.security.core.userdetails.UserDetails; 13 | import org.springframework.security.core.userdetails.UserDetailsService; 14 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 15 | import org.springframework.security.crypto.password.PasswordEncoder; 16 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 17 | 18 | @EnableWebSecurity 19 | @EnableGlobalMethodSecurity(prePostEnabled = true) 20 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 21 | 22 | @Autowired 23 | private SpOAuth2UserService oAuth2UserService; 24 | 25 | @Autowired 26 | private SpOidcUserService oidcUserService; 27 | 28 | 29 | @Bean 30 | UserDetailsService users() { 31 | UserDetails user1 = User.builder() 32 | .username("user1") 33 | .password(passwordEncoder().encode("1234")) 34 | .roles("USER") 35 | .build(); 36 | 37 | UserDetails admin = User.builder() 38 | .username("admin") 39 | .password(passwordEncoder().encode("1234")) 40 | .roles("ADMIN") 41 | .build(); 42 | 43 | return new InMemoryUserDetailsManager(user1, admin); 44 | } 45 | 46 | @Bean 47 | PasswordEncoder passwordEncoder() { 48 | return new BCryptPasswordEncoder(); 49 | } 50 | 51 | 52 | 53 | @Override 54 | protected void configure(HttpSecurity http) throws Exception { 55 | http 56 | .formLogin() 57 | .and() 58 | .oauth2Login(oauth2->{ 59 | oauth2.userInfoEndpoint(userinfo->{ 60 | userinfo.userService(oAuth2UserService) // OAuth2 방식 61 | .oidcUserService(oidcUserService) // OIDC 방식 62 | ; 63 | }); 64 | }) 65 | ; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /web/oauth2-client-test/src/main/java/com/sp/sec/web/config/SpOidcUserToSiteUserFilter.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | 4 | public class SpOidcUserToSiteUserFilter { 5 | } 6 | -------------------------------------------------------------------------------- /web/oauth2-client-test/src/main/java/com/sp/sec/web/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.controller; 2 | 3 | import org.springframework.security.access.prepost.PreAuthorize; 4 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | import org.springframework.security.oauth2.core.user.OAuth2User; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | public class HomeController { 12 | 13 | 14 | @RequestMapping("/") 15 | @PreAuthorize("isAuthenticated()") 16 | public OAuth2User home(@AuthenticationPrincipal OAuth2User user){ 17 | return user; 18 | } 19 | 20 | @RequestMapping("/site") 21 | @PreAuthorize("isAuthenticated()") 22 | public UserDetails siteUser(@AuthenticationPrincipal UserDetails user){ 23 | return user; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /web/oauth2-client-test/src/main/java/com/sp/sec/web/service/SpOAuth2UserService.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.service; 2 | 3 | import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; 4 | import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; 5 | import org.springframework.security.oauth2.core.OAuth2AuthenticationException; 6 | import org.springframework.security.oauth2.core.user.OAuth2User; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class SpOAuth2UserService extends DefaultOAuth2UserService { 11 | 12 | @Override 13 | public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { 14 | OAuth2User user = super.loadUser(userRequest); 15 | 16 | // TODO : ExtendedUser 로 바꾼뒤 리턴한다. 17 | 18 | return user; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /web/oauth2-client-test/src/main/java/com/sp/sec/web/service/SpOidcUserService.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.service; 2 | 3 | import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; 4 | import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; 5 | import org.springframework.security.oauth2.core.OAuth2AuthenticationException; 6 | import org.springframework.security.oauth2.core.oidc.user.OidcUser; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class SpOidcUserService extends OidcUserService { 11 | @Override 12 | public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { 13 | OidcUser user = super.loadUser(userRequest); 14 | 15 | // TODO : ExtendedUser 를 생성을 보장한다. 16 | 17 | return user; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web/oauth2-client-test/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | server: 3 | port: 9007 4 | 5 | spring: 6 | security: 7 | oauth2: 8 | client: 9 | registration: 10 | google: 11 | client-id: 377825014181-2tpha7niq0hjar86e4j8h6dl0h8j1s3i.apps.googleusercontent.com 12 | client-secret: yyHXhXWBRrx3jyDWrhiSwzu8 13 | facebook: 14 | client-id: 488695445381590 15 | client-secret: bcbbc8a3a1d98bedeb258b318878e745 16 | kakao: 17 | client-id: fa84d469d8038b6a422d1f9b2b6f9067 18 | client-secert: RuubX7J4HuhrekWplEeu2FnlSMiCftHH 19 | redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' 20 | authorization-grant-type: authorization_code 21 | client-name: kakao 22 | naver: 23 | client-id: bkdZjYp4EfmqIkZaEtqi 24 | client-secret: YLHoiLkdSy 25 | redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' 26 | authorization-grant-type: authorization_code 27 | client-name: naver 28 | provider: 29 | kakao: 30 | authorization-uri: https://kauth.kakao.com/oauth/authorize 31 | token-uri: https://kauth.kakao.com/oauth/token 32 | user-info-uri: https://kapi.kakao.com/v2/user/me 33 | user-name-attribute: id 34 | naver: 35 | authorization-uri: https://nid.naver.com/oauth2.0/authorize 36 | token-uri: https://nid.naver.com/oauth2.0/token 37 | user-info-uri: https://openapi.naver.com/v1/nid/me 38 | user-name-attribute: response -------------------------------------------------------------------------------- /web/oauth2-client-test/src/test/java/com/sp/sec/web/Oauth2ClientTestApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class Oauth2ClientTestApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /web/resource-server-1/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jongwon/spring-security-junit5-test/8398bcfcc938cf1709496b099cd8e50270ec3dcf/web/resource-server-1/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /web/resource-server-1/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /web/resource-server-1/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.0 9 | 10 | 11 | com.sp.sec 12 | resource-server-1 13 | 1.0.0 14 | resource-server-1 15 | 테스트 리소스 서버1 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-mongodb 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-security 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | true 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | org.junit.vintage 47 | junit-vintage-engine 48 | 49 | 50 | 51 | 52 | de.flapdoodle.embed 53 | de.flapdoodle.embed.mongo 54 | test 55 | 56 | 57 | org.springframework.security 58 | spring-security-test 59 | test 60 | 61 | 62 | 63 | com.sp.sec 64 | user-authority 65 | 1.0.0 66 | 67 | 68 | 69 | com.sp.sec 70 | user-authority 71 | 1.0.0 72 | test-jar 73 | test 74 | 75 | 76 | 77 | com.sp.sec 78 | sp-board 79 | 1.0.0 80 | 81 | 82 | 83 | com.sp.sec 84 | sp-board 85 | 1.0.0 86 | test-jar 87 | test 88 | 89 | 90 | 91 | 92 | com.sp.sec 93 | sp-jwt-security 94 | 1.0.0 95 | 96 | 97 | 98 | com.sp.sec 99 | sp-jwt-security 100 | 1.0.0 101 | test-jar 102 | test 103 | 104 | 105 | 106 | 107 | com.sp.sec 108 | sp-web-util 109 | 1.0.0 110 | 111 | 112 | 113 | 114 | 115 | 116 | org.springframework.boot 117 | spring-boot-maven-plugin 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /web/resource-server-1/src/main/java/com/sp/sec/web/ResourceServer1Application.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication(scanBasePackages = { 7 | "com.sp.sec.config", 8 | "com.sp.sec.web" 9 | }) 10 | public class ResourceServer1Application { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(ResourceServer1Application.class, args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /web/resource-server-1/src/main/java/com/sp/sec/web/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sp.sec.user.service.UserService; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.security.authentication.AuthenticationManager; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.config.http.SessionCreationPolicy; 13 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 14 | 15 | @EnableWebSecurity 16 | @EnableGlobalMethodSecurity(prePostEnabled = true) 17 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 18 | 19 | private final UserService userService; 20 | private final ObjectMapper objectMapper; 21 | private final JWTUtil jwtUtil; 22 | 23 | public SecurityConfig(UserService userService, ObjectMapper objectMapper, JWTUtil jwtUtil) { 24 | this.userService = userService; 25 | this.objectMapper = objectMapper; 26 | this.jwtUtil = jwtUtil; 27 | } 28 | 29 | 30 | @Override 31 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 32 | auth.userDetailsService(userService) 33 | .passwordEncoder(passwordEncoder()); 34 | } 35 | 36 | @Bean 37 | BCryptPasswordEncoder passwordEncoder(){ 38 | return new BCryptPasswordEncoder(); 39 | } 40 | 41 | @Override 42 | protected AuthenticationManager authenticationManager() throws Exception { 43 | return super.authenticationManager(); 44 | } 45 | 46 | @Override 47 | protected void configure(HttpSecurity http) throws Exception { 48 | final JWTCheckFilter checkFilter = new JWTCheckFilter(authenticationManager(), userService, jwtUtil); 49 | http 50 | .csrf().disable() 51 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 52 | .and() 53 | .addFilter(checkFilter) 54 | ; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /web/resource-server-1/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | sp: 4 | jwt: 5 | secret: auth-server-1 6 | token-life-time: 100 7 | token-refresh-time: 10000 8 | -------------------------------------------------------------------------------- /web/resource-server-1/src/test/java/com/sp/sec/web/AuthResourceTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.core.type.TypeReference; 5 | import com.sp.sec.board.domain.SpBoardSummary; 6 | import com.sp.sec.board.service.SpBoardService; 7 | import com.sp.sec.board.service.SpBoardTestHelper; 8 | import com.sp.sec.user.domain.Authority; 9 | import com.sp.sec.user.domain.User; 10 | import com.sp.sec.web.config.UserLogin; 11 | import com.sp.sec.web.util.RestResponsePage; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.DisplayName; 14 | import org.junit.jupiter.api.Test; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.http.HttpEntity; 18 | import org.springframework.http.HttpMethod; 19 | import org.springframework.http.ResponseEntity; 20 | 21 | import java.net.URISyntaxException; 22 | import java.util.Set; 23 | 24 | import static org.junit.jupiter.api.Assertions.assertEquals; 25 | 26 | /** 27 | * Auth 서버로 부터 28 | * 29 | */ 30 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 31 | public class AuthResourceTest extends SpJwtRefreshableTwoUserIntegrationTest { 32 | 33 | @Autowired 34 | private SpBoardService boardService; 35 | 36 | private SpBoardTestHelper boardTestHelper; 37 | 38 | @BeforeEach 39 | void before(){ 40 | prepareTwoUsers(); 41 | User 인증서버의사용자 = User.builder() 42 | .userId("5fab670555c23260d5f5fdbf") 43 | .email("user3@test.com") 44 | .name("user1") 45 | .authorities(Set.of(Authority.USER)) 46 | .enabled(true) 47 | .build(); 48 | userService.save(인증서버의사용자); 49 | 50 | boardTestHelper = new SpBoardTestHelper(boardService); 51 | boardTestHelper.createBoard(USER1, "title1", "content1"); 52 | boardTestHelper.createBoard(USER1, "title2", "content2"); 53 | } 54 | 55 | @DisplayName("Auth 서버에서 토큰을 받아와서 리소스 서버로 부터 게시판의 게시글을 조회한다.") 56 | @Test 57 | void test_1() throws URISyntaxException, JsonProcessingException { 58 | Tokens 인증서버토큰 = getAuthServerToken("user1@test.com", "1234"); 59 | ResponseEntity response = restTemplate.exchange(uri("/board/list"), 60 | HttpMethod.GET, getAuthHeaderEntity(인증서버토큰.getAccessToken()), String.class); 61 | assertEquals(200, response.getStatusCodeValue()); 62 | 63 | RestResponsePage page = objectMapper.readValue(response.getBody(), new TypeReference>() { 64 | }); 65 | assertEquals(2, page.getTotalElements()); 66 | } 67 | 68 | private Tokens getAuthServerToken(String username, String password) { 69 | UserLogin login = UserLogin.builder().type(UserLogin.Type.login) 70 | .username(username).password(password).build(); 71 | HttpEntity body = new HttpEntity<>(login); 72 | ResponseEntity response = restTemplate.exchange(("http://localhost:9001/login"), 73 | HttpMethod.POST, body, String.class); 74 | return Tokens.builder() 75 | .accessToken(getAccessToken(response)) 76 | .refreshToken(getRefreshToken(response)) 77 | .build(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /web/resource-server-1/src/test/java/com/sp/sec/web/ResourceServer1ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ResourceServer1ApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /web/security-basic/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.0 9 | 10 | com.sp.sec 11 | security-basic 12 | 1.0.0 13 | security-basic 14 | 사용자 모듈 15 | 16 | 17 | 11 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-data-mongodb 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-security 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-test 37 | test 38 | 39 | 40 | org.junit.vintage 41 | junit-vintage-engine 42 | 43 | 44 | 45 | 46 | de.flapdoodle.embed 47 | de.flapdoodle.embed.mongo 48 | test 49 | 50 | 51 | org.springframework.security 52 | spring-security-test 53 | test 54 | 55 | 56 | org.projectlombok 57 | lombok 58 | 59 | 60 | com.fasterxml.jackson.core 61 | jackson-annotations 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-maven-plugin 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /web/security-basic/src/main/java/com/sp/sec/basic/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.basic; 2 | 3 | 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @Controller 12 | public class HomeController { 13 | 14 | @RequestMapping("/") 15 | public String home() { 16 | return "index"; 17 | } 18 | 19 | @RequestMapping("/login") 20 | public String login( 21 | @RequestParam(defaultValue = "false") Boolean error, 22 | Model model 23 | ) { 24 | if (error) { 25 | model.addAttribute("errorMessage", "아이디나 패스워드가 올바르지 않습니다."); 26 | } 27 | return "loginForm"; 28 | } 29 | 30 | 31 | // @GetMapping(value="/hello") 32 | // public String hello(){ 33 | // return "hello jongwon"; 34 | // } 35 | } 36 | -------------------------------------------------------------------------------- /web/security-basic/src/main/java/com/sp/sec/basic/SecurityBasicApplication.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.basic; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 7 | import org.springframework.security.core.userdetails.User; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 13 | 14 | @SpringBootApplication 15 | @EnableGlobalMethodSecurity(securedEnabled = true) 16 | public class SecurityBasicApplication { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(SecurityBasicApplication.class, args); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /web/security-basic/src/main/java/com/sp/sec/basic/SecurityMessage.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.basic; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.springframework.security.core.Authentication; 10 | 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Builder 15 | public class SecurityMessage { 16 | 17 | private String message; 18 | 19 | @JsonIgnore 20 | private Authentication auth; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /web/security-basic/src/main/java/com/sp/sec/basic/TestController.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.basic; 2 | 3 | 4 | import org.springframework.security.access.annotation.Secured; 5 | import org.springframework.security.core.context.SecurityContextHolder; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | public class TestController { 11 | 12 | @Secured({"ROLE_USER", "ROLE_ADMIN"}) 13 | @GetMapping(value = "/user") 14 | public SecurityMessage user() { 15 | return SecurityMessage.builder() 16 | .message("user page") 17 | .auth(SecurityContextHolder.getContext().getAuthentication()).build(); 18 | } 19 | 20 | @Secured({"ROLE_ADMIN"}) 21 | @GetMapping(value = "/admin") 22 | public SecurityMessage admin() { 23 | return SecurityMessage.builder() 24 | .message("admin page") 25 | .auth(SecurityContextHolder.getContext().getAuthentication()).build(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /web/security-basic/src/main/java/com/sp/sec/basic/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.basic.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 6 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 7 | import org.springframework.security.core.userdetails.User; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 13 | 14 | 15 | @EnableWebSecurity 16 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 17 | 18 | @Bean 19 | UserDetailsService users() { 20 | UserDetails user1 = User.builder() 21 | .username("user1") 22 | .password(passwordEncoder().encode("1234")) 23 | .roles("USER") 24 | .build(); 25 | 26 | UserDetails admin = User.builder() 27 | .username("admin") 28 | .password(passwordEncoder().encode("1234")) 29 | .roles("ADMIN") 30 | .build(); 31 | 32 | return new InMemoryUserDetailsManager(user1, admin); 33 | } 34 | 35 | @Bean 36 | PasswordEncoder passwordEncoder() { 37 | return new BCryptPasswordEncoder(); 38 | } 39 | 40 | 41 | @Override 42 | protected void configure(HttpSecurity http) throws Exception { 43 | http 44 | .csrf().disable() 45 | .formLogin(config -> { 46 | config.loginPage("/login") 47 | // .successForwardUrl("/") // requestCache 48 | .failureForwardUrl("/login?error=true"); 49 | }) 50 | .authorizeRequests(config -> { 51 | config.antMatchers("/login") 52 | .permitAll() 53 | .antMatchers("/") 54 | .authenticated() 55 | ; 56 | }) 57 | ; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /web/security-basic/src/main/java/com/sp/sec/user/UserAuthorityApplication.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class UserAuthorityApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(UserAuthorityApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /web/security-basic/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /web/security-basic/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8095 3 | 4 | spring: 5 | security: 6 | user: 7 | name: user1 8 | password: 1234 9 | roles: USER -------------------------------------------------------------------------------- /web/security-basic/src/main/resources/static/login.css: -------------------------------------------------------------------------------- 1 | 2 | .error-message { 3 | color: red; 4 | } -------------------------------------------------------------------------------- /web/security-basic/src/main/resources/static/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | ul { 6 | list-style: none; 7 | margin: 0; 8 | padding: 0; 9 | display: flex; 10 | flex-direction: row; 11 | width: 100vw; 12 | } 13 | 14 | ul li { 15 | justify-content: space-between; 16 | align-items: center; 17 | background-color: #196ba2; 18 | color: white; 19 | border-radius: 0.5em; 20 | cursor: pointer; 21 | margin: 1em; 22 | } 23 | 24 | ul li:hover { 25 | background-color: #4991c3; 26 | } 27 | 28 | ul li a { 29 | display: inline-block; 30 | padding: 1em; 31 | text-decoration: none; 32 | color: white; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /web/security-basic/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 테스트 홈 화면 7 | 8 | 9 | 10 | 11 |

Security Test

12 | 13 |
19 | 20 | -------------------------------------------------------------------------------- /web/security-basic/src/main/resources/templates/loginForm.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 로그인 7 | 8 | 9 | 10 | 11 |
12 | 28 |
29 | 30 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /web/security-basic/src/test/java/com/sp/sec/basic/SecurityBasicApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.basic; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SecurityBasicApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /web/security-basic/src/test/java/com/sp/sec/basic/UserAccessTest.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.basic; 2 | 3 | 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 9 | import org.springframework.security.core.userdetails.User; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | import org.springframework.security.test.context.support.WithAnonymousUser; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 19 | 20 | @WebMvcTest 21 | public class UserAccessTest { 22 | 23 | @Autowired 24 | private MockMvc mockMvc; 25 | 26 | @Autowired 27 | private ObjectMapper mapper; 28 | 29 | @Autowired 30 | private PasswordEncoder passwordEncoder; 31 | 32 | UserDetails user1() { 33 | return User.builder() 34 | .username("user1") 35 | .password(passwordEncoder.encode("1234")) 36 | .roles("USER") 37 | .build(); 38 | } 39 | 40 | UserDetails admin() { 41 | return User.builder() 42 | .username("admin") 43 | .password(passwordEncoder.encode("1234")) 44 | .roles("ADMIN") 45 | .build(); 46 | } 47 | 48 | 49 | @DisplayName("1. user 로 user 페이지를 접근할 수 있다.") 50 | @Test 51 | // @WithMockUser(username = "user1", roles = {"USER"}) 52 | void test_user_access_userpage() throws Exception { 53 | String resp = mockMvc.perform(get("/user").with(user(user1()))) 54 | .andExpect(status().isOk()) 55 | .andReturn().getResponse().getContentAsString(); 56 | SecurityMessage message = mapper.readValue(resp, SecurityMessage.class); 57 | assertEquals("user page", message.getMessage()); 58 | } 59 | 60 | @DisplayName("2. user로 admin 페이지를 접근할 수 없다.") 61 | @Test 62 | // @WithMockUser(username = "user1", roles = {"USER"}) 63 | void test_user_cannot_access_adminpage() throws Exception { 64 | mockMvc.perform(get("/admin").with(user(user1()))) 65 | .andExpect(status().is4xxClientError()); 66 | } 67 | 68 | @DisplayName("3. admin 이 user 페이지와 admin 페이지를 접근할 수 있다.") 69 | @Test 70 | // @WithMockUser(username="admin", roles={"ADMIN"}) 71 | void test_admin_can_access_user_and_admin_page() throws Exception { 72 | SecurityMessage message = mapper.readValue(mockMvc.perform(get("/user").with(user(admin()))) 73 | .andExpect(status().isOk()) 74 | .andReturn().getResponse().getContentAsString(), SecurityMessage.class); 75 | assertEquals("user page", message.getMessage()); 76 | 77 | message = mapper.readValue(mockMvc.perform(get("/admin").with(user(admin()))) 78 | .andExpect(status().isOk()) 79 | .andReturn().getResponse().getContentAsString(), SecurityMessage.class); 80 | assertEquals("admin page", message.getMessage()); 81 | 82 | } 83 | 84 | @DisplayName("4. login 페이지는 아무나 접근할 수 있어야 한다.") 85 | @Test 86 | @WithAnonymousUser 87 | void test_login_page_can_accessed_anonymous() throws Exception { 88 | mockMvc.perform(get("/login")) 89 | .andExpect(status().isOk()); 90 | } 91 | 92 | @DisplayName("5. / 홈페이지는 로그인 하지 않은 사람은 접근할 수 없다.") 93 | @Test 94 | void test_need_login() throws Exception { 95 | mockMvc.perform(get("/")) 96 | .andExpect(status().is3xxRedirection()); // 302 redirect to /login 97 | mockMvc.perform(get("/user")).andExpect(status().is3xxRedirection()); 98 | mockMvc.perform(get("/admin")).andExpect(status().is3xxRedirection()); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /web/security-basic/src/test/java/com/sp/sec/user/UserAuthorityApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.user; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class UserAuthorityApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /web/sp-board-web/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jongwon/spring-security-junit5-test/8398bcfcc938cf1709496b099cd8e50270ec3dcf/web/sp-board-web/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /web/sp-board-web/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /web/sp-board-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.0 9 | 10 | 11 | com.sp.sec 12 | sp-board-web 13 | 1.0.0 14 | sp-board-web 15 | 게시판 테스트 웹 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-mongodb 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-security 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | true 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | org.junit.vintage 47 | junit-vintage-engine 48 | 49 | 50 | 51 | 52 | de.flapdoodle.embed 53 | de.flapdoodle.embed.mongo 54 | test 55 | 56 | 57 | org.springframework.security 58 | spring-security-test 59 | test 60 | 61 | 62 | 63 | com.sp.sec 64 | user-authority 65 | 1.0.0 66 | 67 | 68 | 69 | com.sp.sec 70 | user-authority 71 | 1.0.0 72 | test-jar 73 | test 74 | 75 | 76 | 77 | com.sp.sec 78 | sp-board 79 | 1.0.0 80 | 81 | 82 | 83 | com.sp.sec 84 | sp-board 85 | 1.0.0 86 | test-jar 87 | test 88 | 89 | 90 | 91 | 92 | com.sp.sec 93 | sp-jwt-security 94 | 1.0.0 95 | 96 | 97 | 98 | com.sp.sec 99 | sp-jwt-security 100 | 1.0.0 101 | test-jar 102 | test 103 | 104 | 105 | 106 | 107 | com.sp.sec 108 | sp-web-util 109 | 1.0.0 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | org.springframework.boot 118 | spring-boot-maven-plugin 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /web/sp-board-web/src/main/java/com/sp/sec/web/SpBoardWebApplication.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication(scanBasePackages = { 7 | "com.sp.sec.config", 8 | "com.sp.sec.web" 9 | }) 10 | public class SpBoardWebApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(SpBoardWebApplication.class, args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /web/sp-board-web/src/main/java/com/sp/sec/web/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sp.sec.user.service.UserService; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.security.authentication.AuthenticationManager; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.config.http.SessionCreationPolicy; 13 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 14 | 15 | @EnableWebSecurity 16 | @EnableGlobalMethodSecurity(prePostEnabled = true) 17 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 18 | 19 | private final UserService userService; 20 | private final ObjectMapper objectMapper; 21 | private final JWTUtil jwtUtil; 22 | 23 | public SecurityConfig(UserService userService, ObjectMapper objectMapper, JWTUtil jwtUtil) { 24 | this.userService = userService; 25 | this.objectMapper = objectMapper; 26 | this.jwtUtil = jwtUtil; 27 | } 28 | 29 | 30 | @Override 31 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 32 | auth.userDetailsService(userService) 33 | .passwordEncoder(passwordEncoder()); 34 | } 35 | 36 | @Bean 37 | BCryptPasswordEncoder passwordEncoder(){ 38 | return new BCryptPasswordEncoder(); 39 | } 40 | 41 | @Override 42 | protected AuthenticationManager authenticationManager() throws Exception { 43 | return super.authenticationManager(); 44 | } 45 | 46 | @Override 47 | protected void configure(HttpSecurity http) throws Exception { 48 | final JWTLoginFilter loginFilter = new JWTLoginFilter(authenticationManager(), jwtUtil, objectMapper); 49 | final JWTCheckFilter checkFilter = new JWTCheckFilter(authenticationManager(), userService, jwtUtil); 50 | 51 | http 52 | .csrf().disable() 53 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 54 | .and() 55 | .addFilter(loginFilter) 56 | .addFilter(checkFilter) 57 | ; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /web/sp-board-web/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /web/sp-board-web/src/test/java/com/sp/sec/web/SpBoardWebApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpBoardWebApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /web/user-authority-test-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.0 9 | 10 | 11 | com.sp.sec 12 | user-authority-test-web 13 | 0.0.1-SNAPSHOT 14 | user-authority-test-web 15 | user authority 모듈 테스트 웹 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-mongodb 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-security 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-thymeleaf 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-web 37 | 38 | 39 | org.thymeleaf.extras 40 | thymeleaf-extras-springsecurity5 41 | 42 | 43 | 44 | org.projectlombok 45 | lombok 46 | true 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-test 51 | test 52 | 53 | 54 | org.junit.vintage 55 | junit-vintage-engine 56 | 57 | 58 | 59 | 60 | de.flapdoodle.embed 61 | de.flapdoodle.embed.mongo 62 | test 63 | 64 | 65 | org.springframework.security 66 | spring-security-test 67 | test 68 | 69 | 70 | 71 | com.sp.sec 72 | user-authority 73 | 1.0.0 74 | 75 | 76 | 77 | com.sp.sec 78 | user-authority 79 | 1.0.0 80 | test-jar 81 | test 82 | 83 | 84 | com.sp.sec 85 | sp-web-util 86 | 1.0.0 87 | compile 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-maven-plugin 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /web/user-authority-test-web/src/main/java/com/sp/sec/web/UserAuthorityTestWebApplication.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication(scanBasePackages = { 7 | "com.sp.sec.user", 8 | "com.sp.sec.web" 9 | }) 10 | public class UserAuthorityTestWebApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(UserAuthorityTestWebApplication.class, args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /web/user-authority-test-web/src/main/java/com/sp/sec/web/config/DBInit.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.sp.sec.user.domain.Authority; 4 | import com.sp.sec.user.domain.User; 5 | import com.sp.sec.user.service.UserService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.CommandLineRunner; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.Set; 12 | 13 | @Component 14 | public class DBInit implements CommandLineRunner { 15 | 16 | @Autowired 17 | private UserService userService; 18 | 19 | @Autowired 20 | private PasswordEncoder passwordEncoder; 21 | 22 | @Override 23 | public void run(String... args) throws Exception { 24 | userService.clearUsers(); 25 | User user1 = User.builder().name("user2") 26 | .email("user2@test.com") 27 | .password(passwordEncoder.encode("1234")) 28 | .enabled(true) 29 | .authorities(Set.of(Authority.USER)) 30 | .build(); 31 | User admin = User.builder().name("admin") 32 | .email("admin@test.com") 33 | .password(passwordEncoder.encode("admin")) 34 | .enabled(true) 35 | .authorities(Set.of(Authority.ADMIN)) 36 | .build(); 37 | 38 | userService.save(user1); 39 | userService.save(admin); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /web/user-authority-test-web/src/main/java/com/sp/sec/web/config/MongoConfig.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 5 | 6 | @Configuration 7 | @EnableMongoRepositories(basePackages = { 8 | "com.sp.sec.user.repository" 9 | }) 10 | public class MongoConfig { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /web/user-authority-test-web/src/main/java/com/sp/sec/web/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.config; 2 | 3 | import com.sp.sec.user.service.UserService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 7 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 11 | import org.springframework.security.core.userdetails.User; 12 | import org.springframework.security.core.userdetails.UserDetails; 13 | import org.springframework.security.core.userdetails.UserDetailsService; 14 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 15 | import org.springframework.security.crypto.password.PasswordEncoder; 16 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 17 | 18 | 19 | @EnableWebSecurity 20 | @EnableGlobalMethodSecurity(prePostEnabled = true) 21 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 22 | 23 | @Autowired 24 | private UserService userService; 25 | 26 | @Override 27 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 28 | auth.userDetailsService(userService); 29 | } 30 | 31 | @Bean 32 | PasswordEncoder passwordEncoder() { 33 | return new BCryptPasswordEncoder(); 34 | } 35 | 36 | 37 | @Override 38 | protected void configure(HttpSecurity http) throws Exception { 39 | http 40 | .csrf().disable() 41 | .formLogin(config -> { 42 | config.loginPage("/login") 43 | // .successForwardUrl("/") // requestCache 44 | .failureForwardUrl("/login?error=true"); 45 | }) 46 | .authorizeRequests(config -> { 47 | config.antMatchers("/login") 48 | .permitAll() 49 | .antMatchers("/") 50 | .authenticated() 51 | ; 52 | }) 53 | ; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /web/user-authority-test-web/src/main/java/com/sp/sec/web/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.controller; 2 | 3 | 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestParam; 8 | 9 | @Controller 10 | public class HomeController { 11 | 12 | @RequestMapping("/") 13 | public String home() { 14 | return "index"; 15 | } 16 | 17 | @RequestMapping("/login") 18 | public String login( 19 | @RequestParam(defaultValue = "false") Boolean error, 20 | Model model 21 | ) { 22 | if (error) { 23 | model.addAttribute("errorMessage", "아이디나 패스워드가 올바르지 않습니다."); 24 | } 25 | return "loginForm"; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /web/user-authority-test-web/src/main/java/com/sp/sec/web/controller/SecurityMessage.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.controller; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.springframework.security.core.Authentication; 10 | 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Builder 15 | public class SecurityMessage { 16 | 17 | private String message; 18 | 19 | @JsonIgnore 20 | private Authentication auth; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /web/user-authority-test-web/src/main/java/com/sp/sec/web/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web.controller; 2 | 3 | 4 | import org.springframework.security.access.annotation.Secured; 5 | import org.springframework.security.access.prepost.PreAuthorize; 6 | import org.springframework.security.core.context.SecurityContextHolder; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | public class TestController { 12 | 13 | @PreAuthorize("hasAnyAuthority('ROLE_USER', 'ROLE_ADMIN')") 14 | @GetMapping(value = "/user") 15 | public SecurityMessage user() { 16 | return SecurityMessage.builder() 17 | .message("user page") 18 | .auth(SecurityContextHolder.getContext().getAuthentication()).build(); 19 | } 20 | 21 | @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')") 22 | @GetMapping(value = "/admin") 23 | public SecurityMessage admin() { 24 | return SecurityMessage.builder() 25 | .message("admin page") 26 | .auth(SecurityContextHolder.getContext().getAuthentication()).build(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /web/user-authority-test-web/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8097 3 | spring: 4 | data: 5 | mongodb: 6 | database: user-auth-test-web 7 | host: localhost 8 | port: 27018 9 | 10 | -------------------------------------------------------------------------------- /web/user-authority-test-web/src/main/resources/static/login.css: -------------------------------------------------------------------------------- 1 | 2 | .error-message { 3 | color: red; 4 | } -------------------------------------------------------------------------------- /web/user-authority-test-web/src/main/resources/static/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | ul { 6 | list-style: none; 7 | margin: 0; 8 | padding: 0; 9 | display: flex; 10 | flex-direction: row; 11 | width: 100vw; 12 | } 13 | 14 | ul li { 15 | justify-content: space-between; 16 | align-items: center; 17 | background-color: #196ba2; 18 | color: white; 19 | border-radius: 0.5em; 20 | cursor: pointer; 21 | margin: 1em; 22 | } 23 | 24 | ul li:hover { 25 | background-color: #4991c3; 26 | } 27 | 28 | ul li a { 29 | display: inline-block; 30 | padding: 1em; 31 | text-decoration: none; 32 | color: white; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /web/user-authority-test-web/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 테스트 홈 화면 7 | 8 | 9 | 10 | 11 |

Security Test

12 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /web/user-authority-test-web/src/main/resources/templates/loginForm.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 로그인 7 | 8 | 9 | 10 | 11 |
12 | 28 |
29 | 30 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /web/user-authority-test-web/src/test/java/com/sp/sec/web/UserAuthorityTestWebApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sp.sec.web; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class UserAuthorityTestWebApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------