diff --git a/build.gradle b/build.gradle index 8d2b470..6886d8e 100644 --- a/build.gradle +++ b/build.gradle @@ -43,6 +43,7 @@ dependencies { compileOnly 'org.projectlombok:lombok:1.18.22' annotationProcessor 'org.projectlombok:lombok:1.18.22' + testImplementation 'org.springframework.boot:spring-boot-starter-test' // 에러 핸들러를 만들기 위해 validation과 관련된 기능을 사용하기 위한 의존성 추가 @@ -50,6 +51,18 @@ dependencies { // Swagger 세팅 implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + + + //Spring Security + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' + + //Thymeleaf 추가 + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE' + + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + } sourceSets { @@ -88,4 +101,4 @@ configurations { extendsFrom annotationProcessor } querydsl.extendsFrom compileClasspath -} \ No newline at end of file +} diff --git a/src/main/generated/umc/spring/domain/QMember.java b/src/main/generated/umc/spring/domain/QMember.java index 14ca837..2e49824 100644 --- a/src/main/generated/umc/spring/domain/QMember.java +++ b/src/main/generated/umc/spring/domain/QMember.java @@ -43,10 +43,14 @@ public class QMember extends EntityPathBase { public final StringPath name = createString("name"); + public final StringPath password = createString("password"); + public final NumberPath point = createNumber("point", Integer.class); public final ListPath reviewList = this.createList("reviewList", Review.class, QReview.class, PathInits.DIRECT2); + public final EnumPath role = createEnum("role", umc.spring.domain.enums.Role.class); + public final EnumPath socialType = createEnum("socialType", umc.spring.domain.enums.SocialType.class); public final StringPath specAddress = createString("specAddress"); diff --git a/src/main/java/umc/spring/Application.java b/src/main/java/umc/spring/Application.java index c8b838c..3d5ff80 100644 --- a/src/main/java/umc/spring/Application.java +++ b/src/main/java/umc/spring/Application.java @@ -3,11 +3,13 @@ import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; + import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import umc.spring.service.StoreService.StoreQueryService; + @SpringBootApplication @EnableJpaAuditing public class Application { @@ -16,22 +18,22 @@ public static void main(String[] args) { SpringApplication.run(Application.class, args); } - @Bean - public CommandLineRunner run(ApplicationContext context) { - return args -> { - StoreQueryService storeService = context.getBean(StoreQueryService.class); - - // 파라미터 값 설정 - String name = "요아정"; - Float score = 4.0f; - - // 쿼리 메서드 호출 및 쿼리 문자열과 파라미터 출력 - System.out.println("Executing findStoresByNameAndScore with parameters:"); - System.out.println("Name: " + name); - System.out.println("Score: " + score); - - storeService.findStoresByNameAndScore(name, score) - .forEach(System.out::println); - }; - } +// @Bean +// public CommandLineRunner run(ApplicationContext context) { +// return args -> { +// StoreQueryService storeService = context.getBean(StoreQueryService.class); +// +// // 파라미터 값 설정 +// String name = "요아정"; +// Float score = 4.0f; +// +// // 쿼리 메서드 호출 및 쿼리 문자열과 파라미터 출력 +// System.out.println("Executing findStoresByNameAndScore with parameters:"); +// System.out.println("Name: " + name); +// System.out.println("Score: " + score); +// +// storeService.findStoresByNameAndScore(name, score) +// .forEach(System.out::println); +// }; +// } } diff --git a/src/main/java/umc/spring/config/security/CustomOAuth2UserService.java b/src/main/java/umc/spring/config/security/CustomOAuth2UserService.java new file mode 100644 index 0000000..fa0fd22 --- /dev/null +++ b/src/main/java/umc/spring/config/security/CustomOAuth2UserService.java @@ -0,0 +1,65 @@ +package umc.spring.config.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +import umc.spring.domain.Member; +import umc.spring.domain.enums.Gender; +import umc.spring.domain.enums.Role; +import umc.spring.repository.MemberRepository.MemberRepository; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); + + Map attributes = oAuth2User.getAttributes(); + Map properties = (Map) attributes.get("properties"); + + String nickname = (String) properties.get("nickname"); + String email = nickname + "@kakao.com"; // 임시 이메일 생성 + + // 사용자 정보 저장 또는 업데이트 + Member member = saveOrUpdateUser(email, nickname); + + // 이메일을 Principal로 사용하기 위해 attributes 수정 + Map modifiedAttributes = new HashMap<>(attributes); + modifiedAttributes.put("email", email); + + return new DefaultOAuth2User( + oAuth2User.getAuthorities(), + modifiedAttributes, + "email" // email Principal로 설정 + ); + } + + private Member saveOrUpdateUser(String email, String nickname) { + Member member = memberRepository.findByEmail(email) + .orElse(Member.builder() + .email(email) + .name(nickname) + .password(passwordEncoder.encode("OAUTH_USER_" + UUID.randomUUID())) + .gender(Gender.NONE) // 기본값 설정 + .address("소셜로그인") // 기본값 설정 + .specAddress("소셜로그인") // 기본값 설정 + .role(Role.USER) + .build()); + + return memberRepository.save(member); + } +} diff --git a/src/main/java/umc/spring/config/security/CustomUserDetailsService.java b/src/main/java/umc/spring/config/security/CustomUserDetailsService.java new file mode 100644 index 0000000..b4fe12a --- /dev/null +++ b/src/main/java/umc/spring/config/security/CustomUserDetailsService.java @@ -0,0 +1,28 @@ +package umc.spring.config.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import umc.spring.domain.Member; +import umc.spring.repository.MemberRepository.MemberRepository; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final MemberRepository memberRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Member member = memberRepository.findByEmail(username) + .orElseThrow(() -> new UsernameNotFoundException("해당 이메일을 가진 유저가 존재하지 않습니다: " + username)); + + return org.springframework.security.core.userdetails.User + .withUsername(member.getEmail()) + .password(member.getPassword()) + .roles(member.getRole().name()) + .build(); + } +} diff --git a/src/main/java/umc/spring/config/security/SecurityConfig.java b/src/main/java/umc/spring/config/security/SecurityConfig.java new file mode 100644 index 0000000..00e383a --- /dev/null +++ b/src/main/java/umc/spring/config/security/SecurityConfig.java @@ -0,0 +1,44 @@ +package umc.spring.config.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@EnableWebSecurity +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http. + authorizeHttpRequests((requests) -> requests + .requestMatchers("/", "/home", "/signup", "/members/signup", "/css/**").permitAll() + .requestMatchers("/admin/**").hasRole("ADMIN") + .anyRequest().authenticated() + ) + .formLogin((form) -> form + .loginPage("/login") + .defaultSuccessUrl("/home", true) + .permitAll() + ) + .logout((logout) -> logout + .logoutUrl("/logout") + .logoutSuccessUrl("/login?logout") + .permitAll() + ) + .oauth2Login(oauth2 -> oauth2 + .loginPage("/login") + .defaultSuccessUrl("/home", true) + .permitAll() + ); + return http.build(); + } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/umc/spring/converter/MemberConverter.java b/src/main/java/umc/spring/converter/MemberConverter.java index 2e573dd..5cad3d4 100644 --- a/src/main/java/umc/spring/converter/MemberConverter.java +++ b/src/main/java/umc/spring/converter/MemberConverter.java @@ -40,10 +40,13 @@ public static Member toMember(MemberRequestDTO.JoinDto request){ } return Member.builder() + .name(request.getName()) + .email(request.getEmail()) // 추가된 코드 + .password(request.getPassword()) // 추가된 코드 + .gender(gender) .address(request.getAddress()) .specAddress(request.getSpecAddress()) - .gender(gender) - .name(request.getName()) + .role(request.getRole()) // 추가된 코드 .memberPreferList(new ArrayList<>()) .build(); } diff --git a/src/main/java/umc/spring/domain/FoodCategory.java b/src/main/java/umc/spring/domain/FoodCategory.java index efe9c4b..4328edc 100644 --- a/src/main/java/umc/spring/domain/FoodCategory.java +++ b/src/main/java/umc/spring/domain/FoodCategory.java @@ -1,6 +1,11 @@ package umc.spring.domain; + import jakarta.persistence.*; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; import lombok.*; import umc.spring.domain.common.BaseEntity; @@ -15,6 +20,7 @@ public class FoodCategory extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(nullable = false, length = 15) private String name; diff --git a/src/main/java/umc/spring/domain/Member.java b/src/main/java/umc/spring/domain/Member.java index 68bf235..19aae7c 100644 --- a/src/main/java/umc/spring/domain/Member.java +++ b/src/main/java/umc/spring/domain/Member.java @@ -4,11 +4,13 @@ import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.ColumnDefault; + import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; import umc.spring.domain.common.BaseEntity; import umc.spring.domain.enums.Gender; import umc.spring.domain.enums.MemberStatus; +import umc.spring.domain.enums.Role; import umc.spring.domain.enums.SocialType; import umc.spring.domain.mapping.MemberAgree; import umc.spring.domain.mapping.MemberMission; @@ -20,9 +22,9 @@ @Entity @Getter +@Builder @DynamicUpdate @DynamicInsert -@Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class Member extends BaseEntity { @@ -53,11 +55,21 @@ public class Member extends BaseEntity { private LocalDate inactiveDate; + @ColumnDefault("0") + private Integer point; + +// @Column(nullable = false, unique = true) +// private String email; + @Column(nullable = false, length = 50) private String email; - @ColumnDefault("0") - private Integer point; + @Column(nullable = false) + private String password; + + @Enumerated(EnumType.STRING) + private Role role; + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) private List memberAgreeList = new ArrayList<>(); @@ -70,4 +82,7 @@ public class Member extends BaseEntity { @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) private List memberMissionList = new ArrayList<>(); -} \ No newline at end of file + public void encodePassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/umc/spring/domain/Mission.java b/src/main/java/umc/spring/domain/Mission.java index 08db353..e67701a 100644 --- a/src/main/java/umc/spring/domain/Mission.java +++ b/src/main/java/umc/spring/domain/Mission.java @@ -32,4 +32,5 @@ public class Mission extends BaseEntity { @OneToMany(mappedBy = "mission", cascade = CascadeType.ALL) private List memberMissionList = new ArrayList<>(); + } diff --git a/src/main/java/umc/spring/domain/Region.java b/src/main/java/umc/spring/domain/Region.java index cc8f353..b986160 100644 --- a/src/main/java/umc/spring/domain/Region.java +++ b/src/main/java/umc/spring/domain/Region.java @@ -24,4 +24,6 @@ public class Region extends BaseEntity { @OneToMany(mappedBy = "region", cascade = CascadeType.ALL) private List memberMissionList = new ArrayList<>(); + + } diff --git a/src/main/java/umc/spring/domain/Review.java b/src/main/java/umc/spring/domain/Review.java index baa5bb1..52aa539 100644 --- a/src/main/java/umc/spring/domain/Review.java +++ b/src/main/java/umc/spring/domain/Review.java @@ -2,7 +2,6 @@ import jakarta.persistence.*; import lombok.*; - import org.hibernate.annotations.CascadeType; import umc.spring.domain.common.BaseEntity; diff --git a/src/main/java/umc/spring/domain/Terms.java b/src/main/java/umc/spring/domain/Terms.java index a20f7ec..e65a995 100644 --- a/src/main/java/umc/spring/domain/Terms.java +++ b/src/main/java/umc/spring/domain/Terms.java @@ -20,6 +20,7 @@ public class Terms extends BaseEntity { private Long id; @Column(nullable = false, length = 20) + private String title; private String body; diff --git a/src/main/java/umc/spring/domain/enums/Role.java b/src/main/java/umc/spring/domain/enums/Role.java new file mode 100644 index 0000000..61d3b50 --- /dev/null +++ b/src/main/java/umc/spring/domain/enums/Role.java @@ -0,0 +1,5 @@ +package umc.spring.domain.enums; + +public enum Role { + ADMIN, USER +} diff --git a/src/main/java/umc/spring/domain/mapping/MemberMission.java b/src/main/java/umc/spring/domain/mapping/MemberMission.java index e90a25e..8656146 100644 --- a/src/main/java/umc/spring/domain/mapping/MemberMission.java +++ b/src/main/java/umc/spring/domain/mapping/MemberMission.java @@ -2,10 +2,12 @@ import jakarta.persistence.*; import lombok.*; + import umc.spring.domain.Member; import umc.spring.domain.Mission; import umc.spring.domain.Store; import umc.spring.domain.Terms; + import umc.spring.domain.common.BaseEntity; import umc.spring.domain.enums.MissionStatus; @@ -32,4 +34,5 @@ public class MemberMission extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "mission_id") private Mission mission; + } diff --git a/src/main/java/umc/spring/domain/mapping/MemberPrefer.java b/src/main/java/umc/spring/domain/mapping/MemberPrefer.java index 9e5cd5f..0ad46fb 100644 --- a/src/main/java/umc/spring/domain/mapping/MemberPrefer.java +++ b/src/main/java/umc/spring/domain/mapping/MemberPrefer.java @@ -23,7 +23,6 @@ public class MemberPrefer extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "category_id") private FoodCategory foodCategory; - public void setMember(Member member){ if(this.member != null) member.getMemberPreferList().remove(this); @@ -33,4 +32,5 @@ public void setMember(Member member){ public void setFoodCategory(FoodCategory foodCategory){ this.foodCategory = foodCategory; } + } diff --git a/src/main/java/umc/spring/repository/MemberRepository/MemberRepository.java b/src/main/java/umc/spring/repository/MemberRepository/MemberRepository.java index ef18f87..7318c7a 100644 --- a/src/main/java/umc/spring/repository/MemberRepository/MemberRepository.java +++ b/src/main/java/umc/spring/repository/MemberRepository/MemberRepository.java @@ -7,7 +7,9 @@ import umc.spring.domain.Mission; import umc.spring.domain.Review; +import java.util.Optional; + public interface MemberRepository extends JpaRepository { Page findAllById(Member member, PageRequest pageRequest); - + Optional findByEmail(String email); } diff --git a/src/main/java/umc/spring/service/MemberService/MemberCommandServiceImpl.java b/src/main/java/umc/spring/service/MemberService/MemberCommandServiceImpl.java index 6d135ab..0d55efa 100644 --- a/src/main/java/umc/spring/service/MemberService/MemberCommandServiceImpl.java +++ b/src/main/java/umc/spring/service/MemberService/MemberCommandServiceImpl.java @@ -1,5 +1,6 @@ package umc.spring.service.MemberService; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.transaction.annotation.Transactional; import umc.spring.apiPayload.code.status.ErrorStatus; import umc.spring.apiPayload.exception.handler.FoodCategoryHandler; @@ -26,11 +27,17 @@ public class MemberCommandServiceImpl implements MemberCommandService { private final FoodCategoryRepository foodCategoryRepository; + private final PasswordEncoder passwordEncoder; + @Override @Transactional public Member joinMember(MemberRequestDTO.JoinDto request) { + System.out.println("joinMember method called with request: " + request); Member newMember = MemberConverter.toMember(request); + newMember.encodePassword(passwordEncoder.encode(request.getPassword())); + System.out.println("New member created: " + newMember); + List foodCategoryList = request.getPreferCategory().stream() .map(category -> { System.out.println("Checking category ID: " + category); @@ -41,6 +48,7 @@ public Member joinMember(MemberRequestDTO.JoinDto request) { memberPreferList.forEach(memberPrefer -> {memberPrefer.setMember(newMember);}); + System.out.println("Saving new member to repository."); return memberRepository.save(newMember); } } diff --git a/src/main/java/umc/spring/web/controller/MemberViewController.java b/src/main/java/umc/spring/web/controller/MemberViewController.java new file mode 100644 index 0000000..fd493c5 --- /dev/null +++ b/src/main/java/umc/spring/web/controller/MemberViewController.java @@ -0,0 +1,62 @@ +package umc.spring.web.controller; + +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import umc.spring.service.MemberService.MemberCommandService; +import umc.spring.web.dto.MemberRequestDTO; + +@Controller +@RequiredArgsConstructor +public class MemberViewController { + + private final MemberCommandService memberCommandService; + + private static final Logger logger = LoggerFactory.getLogger(MemberViewController.class); + + + + @PostMapping("/members/signup") + public String joinMember(@ModelAttribute("memberJoinDto") MemberRequestDTO.JoinDto request, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return "signup"; + } + + try { + memberCommandService.joinMember(request); + logger.info("회원가입 요청: {}", request); + return "redirect:/login"; + } catch (Exception e) { + model.addAttribute("error", e.getMessage()); + return "signup"; + } + } + @GetMapping("/login") + public String loginPage() { + return "login"; + } + + @GetMapping("/signup") + public String signupPage(Model model) { + model.addAttribute("memberJoinDto", new MemberRequestDTO.JoinDto()); + return "signup"; + } + + @GetMapping("/home") + public String home() { + return "home"; + } + + @GetMapping("/admin") + public String admin() { + return "admin"; + } +} \ No newline at end of file diff --git a/src/main/java/umc/spring/web/dto/MemberRequestDTO.java b/src/main/java/umc/spring/web/dto/MemberRequestDTO.java index d9b2013..b744cd5 100644 --- a/src/main/java/umc/spring/web/dto/MemberRequestDTO.java +++ b/src/main/java/umc/spring/web/dto/MemberRequestDTO.java @@ -1,5 +1,6 @@ package umc.spring.web.dto; +import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; @@ -7,6 +8,7 @@ import lombok.Setter; +import umc.spring.domain.enums.Role; import umc.spring.validation.annotation.ExistCategories; import java.util.List; @@ -14,6 +16,7 @@ public class MemberRequestDTO { @Getter + @Setter public static class JoinDto{ @NotBlank String name; @@ -31,5 +34,11 @@ public static class JoinDto{ String specAddress; @ExistCategories List preferCategory; + @Email + String email; // 이메일 필드 추가 + @NotBlank + String password; // 비밀번호 필드 추가 + @NotNull + Role role; // 역할 필드 추가 } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f876f62..b293534 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,9 +1,9 @@ spring: datasource: - url: jdbc:mysql://localhost:3306/study - username: 'root' - password: 'mysqlqlqjs098@' driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/study + username: root + password: mysqlqlqjs098@ sql: init: mode: never @@ -15,5 +15,24 @@ spring: format_sql: true use_sql_comments: true hbm2ddl: - auto: create - default_batch_fetch_size: 1000 \ No newline at end of file + auto: update + default_batch_fetch_size: 1000 + + security: + oauth2: + client: + registration: + kakao: + client-authentication-method: client_secret_post + client-id: 5a7ead2571a6fbcbf7deda41a70bcbd2 + client-secret: lZIkBOnRh0TeeXY3TuGDGoA2nDt6epnU + redirect-uri: http://localhost:8080/login/oauth2/code/kakao + authorization-grant-type: authorization_code + scope: profile_nickname + client-name: Kakao + provider: + kakao: + authorization-uri: https://kauth.kakao.com/oauth/authorize + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v2/user/me + user-name-attribute: id \ No newline at end of file diff --git a/src/main/resources/templates/admin.html b/src/main/resources/templates/admin.html new file mode 100644 index 0000000..55dbff1 --- /dev/null +++ b/src/main/resources/templates/admin.html @@ -0,0 +1,10 @@ + + + + Admin Page + + +

Admin Page

+

관리자만 접근할 수 있는 페이지입니다.

+ + \ No newline at end of file diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html new file mode 100644 index 0000000..8c10cb1 --- /dev/null +++ b/src/main/resources/templates/home.html @@ -0,0 +1,20 @@ + + + + Home + + +

Welcome to Home Page!

+ +

+ + + + + +
+ +
+ \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..26a3bcd --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,30 @@ + + + + Login + + +

Login

+
+
+ + +
+
+ + +
+ +
+ +

사용자 이름 또는 비밀번호가 잘못되었습니다.

+

로그아웃되었습니다.

+ + +

계정이 없나요? Sign up

+ + +카카오로 로그인 + + + \ No newline at end of file diff --git a/src/main/resources/templates/signup.html b/src/main/resources/templates/signup.html new file mode 100644 index 0000000..3629386 --- /dev/null +++ b/src/main/resources/templates/signup.html @@ -0,0 +1,72 @@ + + + + 회원가입 + + + +

회원가입

+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + + +
+
+
+ + +
+ +
+ + \ No newline at end of file