당신은 주제를 찾고 있습니까 “스프링 부트 회원 가입 – 2020 04 05, 스프링부트, 문제, 회원가입 기능을 구현해주세요“? 다음 카테고리의 웹사이트 you.tfvp.org 에서 귀하의 모든 질문에 답변해 드립니다: https://you.tfvp.org/blog. 바로 아래에서 답을 찾을 수 있습니다. 작성자 몰입코딩 아카이브 이(가) 작성한 기사에는 조회수 1,896회 및 좋아요 20개 개의 좋아요가 있습니다.
스프링 부트 회원 가입 주제에 대한 동영상 보기
여기에서 이 주제에 대한 비디오를 시청하십시오. 주의 깊게 살펴보고 읽고 있는 내용에 대한 피드백을 제공하세요!
d여기에서 2020 04 05, 스프링부트, 문제, 회원가입 기능을 구현해주세요 – 스프링 부트 회원 가입 주제에 대한 세부정보를 참조하세요
스프링 부트 회원 가입 주제에 대한 자세한 내용은 여기를 참조하세요.
3-06 회원가입 – 점프 투 스프링부트
회원가입 기능을 만들어 보았다면 웹 프로그래밍은 거의 마스터했다고 할 수 있다. … 엔티티명을 User 대신 SiteUser로 한 이유는 스프링 시큐리티에 이미 User …
Source: wikidocs.net
Date Published: 11/27/2022
View: 6455
6. [springboot] Spring boot 기초 회원가입 예제 – 기록하기록
[springboot] Spring boot 기초 회원가입 예제 … 스프링 시큐리티를 이용하여 권한을 부여하고, 회원가입 처리를 하는 간단 예제를 만들어 볼 것 …Source: dkyou.tistory.com
Date Published: 10/2/2021
View: 9867
Spring Boot JPA 게시판 Security 회원가입,로그인 구현
이제 Security를 이용해 회원가입 및 로그인을 구현해보자. 1. build.gradle. implementation ‘org.springframework.boot:spring-boot-starter-security’ …
Source: dev-coco.tistory.com
Date Published: 1/18/2022
View: 6634
<스프링 부트 Spring boot> 회원가입 기능 구현
구현 순서는 다음과 같다. 1) 회원가입 페이지인 jsp에 태그로 사용자에게서 입력받은 정보를 서버(컨트롤러 서블릿)으로 보낸다.
Source: romanticdeveloper.tistory.com
Date Published: 7/18/2021
View: 8275
Spring Security 회원가입 / 로그인 구현
스프링 부트 환경에서 BCryptPasswordEncoder를 이용한 회원가입 암호화 / Spring Security를 … compile(“org.springframework.boot:spring-boot-starter-security”).
Source: blog.javabom.com
Date Published: 8/8/2022
View: 7582
[Spring Boot Tutorial] 10. 회원가입 화면 만들기
기존의 로그인 화면에서 Login버튼 아래에 Join버튼을 추가합니다. … 필수 정보인 이메일, 비밀번호, 이름, 성별과 옵션 정보인 생년월일, 전화번호를 …
Source: blog.jiniworld.me
Date Published: 2/18/2022
View: 1524
스프링부트로 게시판 만들기 5 : 회원가입 기능 추가
만약 중복된 아이디가 있는 경우, 이미 있는 회원이라 가입이 안된다는 메시지를 보낸다. 그렇지 않은 경우 전달 받은 데이터를 데이터베이스에 추가 한다 …
Source: semtax.tistory.com
Date Published: 10/22/2022
View: 7098
[spring boot] spring security를 이용한 회원가입 기능 구현
간단하게 사용자 이름(닉네임)과 비밀번호 정보를 가지고 회원가입 기능을 구현해보겠습니다. 저번에 포스팅하다가 끊긴 게시판 만들기와 같은 …
Source: coding-nyan.tistory.com
Date Published: 6/2/2021
View: 2186
주제와 관련된 이미지 스프링 부트 회원 가입
주제와 관련된 더 많은 사진을 참조하십시오 2020 04 05, 스프링부트, 문제, 회원가입 기능을 구현해주세요. 댓글에서 더 많은 관련 이미지를 보거나 필요한 경우 더 많은 관련 기사를 볼 수 있습니다.
주제에 대한 기사 평가 스프링 부트 회원 가입
- Author: 몰입코딩 아카이브
- Views: 조회수 1,896회
- Likes: 좋아요 20개
- Date Published: 2020. 4. 5.
- Video Url link: https://www.youtube.com/watch?v=RcONIQwk7RE
3-06 회원가입
이번에는 SBB에 회원가입 기능을 구현해 보자.
회원가입 기능을 만들어 보았다면 웹 프로그래밍은 거의 마스터했다고 할 수 있다. 그만큼 회원가입 기능은 웹 사이트에서 핵심 중의 핵심이라 할 수 있다.
회원 정보를 위한 엔티티
지금까지는 질문, 답변 엔티티만 사용했다면 이제 회원 정보를 위한 엔티티가 필요하다. 회원 정보 엔티티에는 최소한 다음과 같은 속성이 필요하다.
속성 설명 username 사용자 이름 (사용자 ID) password 비밀번호 email 이메일
User 도메인
그리고 회원은 질문, 답변 도메인이 아니므로 user라는 도메인을 사용할 것이다. 다음과 같이 com.mysite.sbb.user 패키지를 생성하자.
SiteUser 엔티티
그리고 사용자를 관리할 SiteUser 엔티티를 다음처럼 작성하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/user/SiteUser.java]package com.mysite.sbb.user; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import lombok.Getter; import lombok.Setter; @Getter @Setter @Entity public class SiteUser { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true) private String username; private String password; @Column(unique = true) private String email; }
Question, Answer 엔티티와 동일한 방법으로 SiteUser 엔티티를 만들었다. 엔티티명을 User 대신 SiteUser로 한 이유는 스프링 시큐리티에 이미 User 클래스가 있기 때문이다. 물론 패키지명이 달라 User라는 이름을 사용할수 있지만 패키지 오용으로 인한 오류가 발생할수 있으므로 이 책에서는 User 대신 SiteUser라는 이름으로 명명하였다.
그리고 username, email 속성에는 @Column(unique = true) 처럼 unique = true 를 지정했다. unique = true 는 유일한 값만 저장할 수 있음을 의미한다. 즉, 값을 중복되게 저장할 수 없음을 뜻한다. 이렇게 해야 username과 email에 동일한 값이 저장되지 않는다.
SiteUser 테이블
SiteUser 엔티티를 작성하고 H2 콘솔에 접속하여 테이블이 잘 만들어졌는지 확인해 보자.
SITE_USER 테이블과 컬럼들 그리고 unique로 설정한 속성들로 인해 생긴 UK_ 로 시작하는 인덱스들이 보일 것이다.
User 리포지터리와 서비스
사용자 엔티티가 준비되었으니 이제 User 리포지터리와 User 서비스를 만들어 보자.
User 리포지터리
다음과 같이 UserRepository를 작성하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/user/UserRepository.java]package com.mysite.sbb.user; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository
{ } SiteUser의 PK의 타입은 Long이다. 따라서 JpaRepository
처럼 사용했다. User 서비스
그리고 다음과 같이 UserService를 작성하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/user/UserService.java]package com.mysite.sbb.user; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository; public SiteUser create(String username, String email, String password) { SiteUser user = new SiteUser(); user.setUsername(username); user.setEmail(email); BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); user.setPassword(passwordEncoder.encode(password)); this.userRepository.save(user); return user; } }
User 서비스에는 User 리포지터리를 사용하여 User 데이터를 생성하는 create 메서드를 추가했다. 이 때 사용자의 비밀번호는 보안을 위해 반드시 암호화하여 저장해야 한다. 암호화를 위해 시큐리티의 BCryptPasswordEncoder 클래스를 사용하여 암호화하여 비밀번호를 저장했다.
BCryptPasswordEncoder는 BCrypt 해싱 함수(BCrypt hashing function)를 사용해서 비밀번호를 암호화한다.
하지만 이렇게 BCryptPasswordEncoder 객체를 직접 new로 생성하는 방식보다는 PasswordEncoder 빈(bean)으로 등록해서 사용하는 것이 좋다. 왜냐하면 암호화 방식을 변경하면 BCryptPasswordEncoder를 사용한 모든 프로그램을 일일이 찾아서 수정해야 하기 때문이다.
PasswordEncoder는 BCryptPasswordEncoder의 인터페이스이다.
PasswordEncoder 빈(bean)을 만드는 가장 쉬운 방법은 @Configuration이 적용된 SecurityConfig에 @Bean 메서드를 생성하는 것이다. 다음과 같이 SecurityConfig를 수정하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/SecurityConfig.java]package com.mysite.sbb; 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.web.SecurityFilterChain; [[MARK]]import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;[[/MARK]] import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers(“/**”).permitAll() .and() .csrf().ignoringAntMatchers(“/h2-console/**”) .and() .headers() .addHeaderWriter(new XFrameOptionsHeaderWriter( XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)) ; return http.build(); } [[MARK]]@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }[[/MARK]] }
이렇게 PasswordEncoder를 @Bean으로 등록하면 UserService도 다음과 같이 수정할수 있다.
[파일명:/sbb/src/main/java/com/mysite/sbb/user/UserService.java](… 생략 …) [[MARK]]import org.springframework.security.crypto.password.PasswordEncoder;[[/MARK]] (… 생략 …) public class UserService { private final UserRepository userRepository; [[MARK]]private final PasswordEncoder passwordEncoder;[[/MARK]] (… 생략 …) public SiteUser create(String username, String email, String password) { SiteUser user = new SiteUser(); user.setUsername(username); user.setEmail(email); [[MARK]]user.setPassword(passwordEncoder.encode(password));[[/MARK]] this.userRepository.save(user); return user; } }
BCryptPasswordEncoder 객체를 직접 생성하여 사용하지 않고 빈으로 등록한 PasswordEncoder 객체를 주입받아 사용하도록 수정했다.
회원가입 폼
그리고 회원 가입을 위한 폼 클래스를 작성하자. 다음처럼 UserCreateForm을 만들자.
[파일명:/sbb/src/main/java/com/mysite/sbb/user/UserCreateForm.java]package com.mysite.sbb.user; import javax.validation.constraints.Email; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Size; import lombok.Getter; import lombok.Setter; @Getter @Setter public class UserCreateForm { @Size(min = 3, max = 25) @NotEmpty(message = “사용자ID는 필수항목입니다.”) private String username; @NotEmpty(message = “비밀번호는 필수항목입니다.”) private String password1; @NotEmpty(message = “비밀번호 확인은 필수항목입니다.”) private String password2; @NotEmpty(message = “이메일은 필수항목입니다.”) @Email private String email; }
username은 필수항목이고 길이가 3-25 사이여야 한다는 검증조건을 설정했다. @Size는 폼 유효성 검증시 문자열의 길이가 최소길이(min)와 최대길이(max) 사이에 해당하는지를 검증한다. password1과 password2는 “비밀번호”와 “비밀번호확인”에 대한 속성이다. 로그인 할때는 비밀번호가 한번만 필요하지만 회원가입시에는 입력한 비밀번호가 정확한지 확인하기 위해 2개의 필드가 필요하다. 그리고 email 속성에는 @Email 애너테이션이 적용되었다. @Email은 해당 속성의 값이 이메일형식과 일치하는지를 검증한다.
회원가입 컨트롤러
이제 사용자 엔티티와 서비스 그리고 폼이 준비되었으니 회원가입을 위한 User 컨트롤러를 만들어보자.
[파일명:/sbb/src/main/java/com/mysite/sbb/user/UserController.java]package com.mysite.sbb.user; import javax.validation.Valid; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Controller @RequestMapping(“/user”) public class UserController { private final UserService userService; @GetMapping(“/signup”) public String signup(UserCreateForm userCreateForm) { return “signup_form”; } @PostMapping(“/signup”) public String signup(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return “signup_form”; } if (!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) { bindingResult.rejectValue(“password2”, “passwordInCorrect”, “2개의 패스워드가 일치하지 않습니다.”); return “signup_form”; } userService.create(userCreateForm.getUsername(), userCreateForm.getEmail(), userCreateForm.getPassword1()); return “redirect:/”; } }
/user/signup URL이 GET으로 요청되면 회원 가입을 위한 템플릿을 렌더링하고 POST로 요청되면 회원가입을 진행하도록 했다. 그리고 회원 가입시 비밀번호1 과 비밀번호2 가 동일한지를 검증하는 로직을 추가했다. 만약 2개의 값이 일치하지 않을 경우에는 bindingResult.rejectValue 를 사용하여 오류가 발생하게 했다. bindingResult.rejectValue 의 각 파라미터는 bindingResult.rejectValue(필드명, 오류코드, 에러메시지) 를 의미하며 여기서 오류코드는 일단 “passwordInCorrect”로 정의했다.
대형 프로젝트에서는 번역과 관리를 위해 오류코드를 잘 정의하여 사용해야 한다.
회원가입 템플릿
이어서 회원가입 템플릿을 작성하자. 다음처럼 signup_form.html 파일을 작성하자.
[파일명: /sbb/src/main/resources/templates/signup_form.html]
회원가입
회원가입을 위한 “사용자 ID”, “비밀번호”, “비밀번호 확인”, “이메일”에 해당되는 input 엘리먼트를 추가했다. <회원가입> 버튼을 누르면 폼 데이터가 POST 방식으로 /user/signup/ URL로 전송된다.
내비게이션 바에 회원가입 링크 추가하기
이제 회원가입 화면으로 이동할 수 있는 링크를 내비게이션 바에 추가하자.
[파일명: /sbb/src/main/resources/templates/navbar.html]회원가입 실행해 보기
이제 내비게이션 바의 “회원가입” 링크를 누르면 다음과 같은 회원가입 화면이 나온다.
입력값 중에서 비밀번호, 비밀번호 확인을 다르게 입력하고 <회원가입> 을 누르면 검증 오류가 발생하여 화면에 다음과 같은 오류 메시지를 표시해 줄 것이다.
이처럼 우리가 만든 회원가입 기능에는 필숫값 검증, 이메일 규칙 검증 등이 적용되어 있다. 올바른 입력값으로 회원가입을 완료하면 메인 페이지로 리다이렉트될 것이다.
회원가입 데이터 확인해 보기
H2 콘솔에서 다음의 SQL을 실행하여 바로 앞 단계를 거쳐 만든 회원 정보를 확인해 보자.
SELECT * FROM SITE_USER
축하한다. 이제 SBB 서비스에 회원가입 기능이 추가되었다.
중복 회원가입
이번에는 이미 가입한 동일한 사용자ID, 또는 동일한 이메일 주소로 회원가입을 진행해 보자. 아마도 다음과 같은 오류가 발생할 것이다.
동일한 사용자ID 또는 동일한 이메일 주소로 사용자 데이터를 저장하는 것은 unique=true 설정으로 인해 허용되지 않기 때문에 오류가 발생하는 것은 당연하다. 하지만 화면에 이렇게 500 오류 메시지를 그대로 보여주는 것은 좋지 않다. 따라서 회원가입시 발생하는 오류를 다음과 같이 처리해 주자.
[파일명:/sbb/src/main/java/com/mysite/sbb/user/UserController.java]package com.mysite.sbb.user; (… 생략 …) [[MARK]]import org.springframework.dao.DataIntegrityViolationException;[[/MARK]] (… 생략 …) public class UserController { (… 생략 …) @PostMapping(“/signup”) public String signup(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return “signup_form”; } if (!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) { bindingResult.rejectValue(“password2”, “passwordInCorrect”, “2개의 패스워드가 일치하지 않습니다.”); return “signup_form”; } [[MARK]]try {[[/MARK]] userService.create(userCreateForm.getUsername(), userCreateForm.getEmail(), userCreateForm.getPassword1()); [[MARK]]}catch(DataIntegrityViolationException e) { e.printStackTrace(); bindingResult.reject(“signupFailed”, “이미 등록된 사용자입니다.”); return “signup_form”; }catch(Exception e) { e.printStackTrace(); bindingResult.reject(“signupFailed”, e.getMessage()); return “signup_form”; }[[/MARK]] return “redirect:/”; } }
사용자ID 또는 이메일 주소가 동일할 경우에는 DataIntegrityViolationException이 발생하므로 DataIntegrityViolationException 예외가 발생할 경우 “이미 등록된 사용자입니다.”라는 오류를 화면에 표시하도록 했다. 그리고 다른 오류의 경우에는 해당 오류의 메시지( e.getMessage() )를 출력하도록 했다.
bindingResult.reject(오류코드, 오류메시지) 는 특정 필드의 오류가 아닌 일반적인 오류를 등록할때 사용한다.
이렇게 수정하고 다시 동일한 사용자로 로그인을 하면 다음과 같이 정상적인 오류를 표시하는 화면을 볼수 있다.
6. [springboot] Spring boot 기초 회원가입 예제
728×90
반응형
1. 서론
스프링 시큐리티를 이용하여 권한을 부여하고, 회원가입 처리를 하는 간단 예제를 만들어 볼 것입니다.
이 글을 이해하면 간단한 회원가입을 스프링 시큐리티로 구현할 수 있습니다.
간단한 MVC 구조를 알고 계신다면 더 편하게 따라오실 수 있습니다.
2. 본론
전체구조입니다.
엔티티 모델링
먼저, 원활한 회원가입을 위한 엔티티 모델링부터 진행합니다.
여기서 주의할 점은 username 부분인데, 시큐리티에서 템플릿과 연동 시 기본으로 제공하는 name 값이 username 이므로 이에 맞추어 줍니다. 커스터마이징 또한 따로 할 수 있으나 추후 포스팅하겠습니다.
import lombok.AccessLevel; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.*; @Entity @Data // 다른 패키지에서 생성자 함부로 생성하지 마세요! @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Account { @Id @Column(name = “user_id”) // SQL 에서 자동생성되도록 돕는 어노테이션 @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; private String email; private String age; private String role; @Builder public Account(Long id, String username, String password, String email, String age, String role) { this.id = id; this.username = username; this.password = password; this.email = email; this.age = age; this.role = role; } }
레파지토리 모델링
엔티티를 바탕으로 repository 클래스를 만들어줍니다.
package com.example.springsecurity.repository; import com.example.springsecurity.domain.Account; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface AccountRepository extends JpaRepository
{ } – 이를 사용하면 JpaRepository에서 제공하는 여러 함수들을 이용할 수 있게 됩니다.
– 자세한 것은 추후 포스팅하겠습니다.
서비스 모델링
– 서비스 클래스를 만들어 Transaction 처리할 수 있는 환경을 만들어줍니다. @Transactional 어노테이션은 함수가 불의의 사고로 구동 실패 시 Rollback 할 수 있도록 안전장치하는 어노테이션이므로 로직 처리는 여기에서 진행해줍니다.
package com.example.springsecurity.service; import com.example.springsecurity.domain.Account; import com.example.springsecurity.dto.AccountForm; import com.example.springsecurity.repository.AccountRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @Transactional(readOnly = true) @RequiredArgsConstructor public class AccountService { private final AccountRepository accountRepository; @Transactional public Long createUser(AccountForm form) { Account account = form.toEntity(); accountRepository.save(account); return account.getId(); } }
– 여기서 주의할 점은 AccountForm 이라는 DTO 객체가 나와있다는 점입니다. Entity 가 있는데 왜 굳이 DTO를 만들어 toEntity() 처리를 해주어야 하실지 궁금하실 것입니다. 이에 관해서 추후 포스팅하겠습니다.
– save(객체) 구문은 JpaRepository에서 제공하는 함수입니다. entityManager.persist()로 DB에 실제적으로 저장시켜줍니다.
DTO 모델링
package com.example.springsecurity.dto; import com.example.springsecurity.domain.Account; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Data @NoArgsConstructor public class AccountForm { private Long id; private String username; private String password; private String email; private String age; private String role; @Builder public AccountForm(Long id, String username, String password, String email, String age, String role) { this.id = id; this.username = username; this.password = password; this.email = email; this.age = age; this.role = role; } public Account toEntity(){ return Account.builder() .id(id) .username(username) .password(new BCryptPasswordEncoder().encode(password)) .email(email) .age(age) .role(role) .build(); } }
– 여기서 주의해서 볼 점은 toEntity() 부분입니다. 스프링 시큐리티에서 제공하는 BCryptPasswordEncoder를 이용하여 사용자 비밀번호를 암호화합니다.
컨트롤러 모델링
package com.example.springsecurity.controller; import com.example.springsecurity.dto.AccountForm; import com.example.springsecurity.service.AccountService; import lombok.RequiredArgsConstructor; 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.PostMapping; import javax.validation.Valid; @Controller @RequiredArgsConstructor public class UserController { private final AccountService accountService; @GetMapping(“/loginUser”) public String createUserForm(Model model){ model.addAttribute(“userForm”,new AccountForm()); return “user/login/register”; } @PostMapping(“/loginUser”) public String createUser(@Valid AccountForm form, BindingResult result){ if(result.hasErrors()){ return “user/login/register”; } accountService.createUser(form); return “redirect:/”; } }
– 주의해서 볼 점은 둘 다 “/loginUser” 임에도 불구하고 @GetMapping 인지, @PostMapping 인지에 따라 다른 함수가 실행된다는 것입니다. 자세한 내용은 get, post 방식을 선행 학습하시면 됩니다.
– get 방식으로 접속 시, userForm이라는 객체를 HTML에 전달해주게 됩니다.
– post 방식으로 접속 시, 회원가입 처리를 하게 됩니다.
– BindingResult는 @Valid 어노테이션에서 문제가 생겼을 경우, 처리를 돕는 기본 제공 로직입니다.
– 회원가입이 완료되지 않아 문제가 생겼을 경우 다시 회원가입 페이지로 가도록 하였고, 성공 시 redirect를 사용하여 루트 페이지로 오게 만들었습니다.
프런트엔드 로직
회원가입
– 저 같은 경우 fragments 내부에 각각의 템플릿 파일들을 삽입하여 꾸며보았습니다.
– 지금 포스팅에서는 설명하지 않겠지만, 관심 있으시다면 thymeleaf th:replace를 검색하여 공부하시면 좋을 것 같습니다.
– 타임리프 문법 중에 th:object = ${userForm}이라는 객체로 controller에서 매핑한 이름을 가지고 받아왔습니다. 옵젝으로 받은 하위 변수에는 ${} 이 아닌 *{}으로 변수를 할당한다는 것에 유의하세요.
– th:field는 name + id입니다.
3. 결론
회원가입 기능 추가된 front
– 회원가입 기능을 추가하였습니다.
회원가입 예제
클릭 시 해당 회원가입을 진행하게 됩니다.
h2
회원가입 완료 후 데이터가 잘 저장된 것을 볼 수 있습니다.
password는 아까 설명드린 곳에서 보았듯, 암호화되어 저장되어있습니다.
4. 마무리
– 이상으로 간단한 회원가입 예제를 구현해보았습니다. 이어서 custom 로그인을 구현해보는 예제를 포스팅해볼까 합니다.
+) version 2.0을 만들었습니다.
728×90
반응형
Spring Boot JPA 게시판 Security 회원가입,로그인 구현
반응형
예전에는 회원가입 및 로그인을 전통적인 방식으로 구현했지만 요즘은 사용하지 않는 추세이고,
Spring Security와 OAuth 2.0을 사용한다.
Spring Security ? 스프링 기반의 어플리케이션에서 보안을 위해 인증과 권한 부여를 사용해 접근을 제어하는 프레임워크이다.
OAuth 2.0에 대해 궁금하다면 여기서 참고하면 될 것 같다. 이해하기 쉽게 설명을 잘해주셨다.
이제 Security를 이용해 회원가입 및 로그인을 구현해보자.
1. build.gradle
implementation ‘org.springframework.boot:spring-boot-starter-security’
2. User , Role , UserRepository
@AllArgsConstructor @NoArgsConstructor @Builder @Getter @Entity public class User extends TimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 30, unique = true) private String username; // 아이디 @Column(nullable = false) private String nickname; @Column(nullable = false, length = 100) private String password; @Column(nullable = false, length = 50) private String email; @Enumerated(EnumType.STRING) @Column(nullable = false) private Role role; }
User의 username은 id이기 때문에 unique 속성을 추가로 넣어줬다.
@Getter @RequiredArgsConstructor public enum Role { USER(“ROLE_USER”), ADMIN(“ROLE_ADMIN”); private final String value; }
public interface UserRepository extends JpaRepository
{ Optional findByUsername(String username); } Username을 where 조건절에 넣어 데이터를 가져올 수 있도록 findByUsername 정의했다.
3. UserDto
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class UserDto { private String username; private String password; private String nickname; private String email; private Role role; /* DTO -> Entity */ public User toEntity() { User user = User.builder() .username(username) .password(password) .nickname(nickname) .email(email) .role(role.USER) .build(); return user; } }
4. SecurityConfig 생성
Spring Security에서 WebSecurityConfigurerAdapter를 상속받은 클래스에서 메소드를 오버라이딩하여 조정할 수 있다.
@RequiredArgsConstructor @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { private final CustomUserDetailsService customUserDetailsService; @Bean public BCryptPasswordEncoder encoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserDetailsService).passwordEncoder(encoder()); } @Override public void configure(WebSecurity web) throws Exception { web .ignoring().antMatchers( “/css/**”, “/js/**”, “/img/**”); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers(“/”, “/auth/**”, “/posts/read/**”, “/posts/search/**”) .permitAll() .anyRequest() .authenticated() .and() .formLogin() .loginPage(“/auth/login”) .loginProcessingUrl(“loginProc”) .defaultSuccessUrl(“/”) .and() .logout() .logoutSuccessUrl(“/”) .invalidateHttpSession(true); } }
@RequiredArgsConstructor CustomUserDetailsService 생성자 주입을 위한 lombok
@EnableWebSecurity @Configuration에 @EnableWebSecurity를 추가해 Spring Security 설정 클래스 임을 알려준다.
@EnableGlobalMethodSecurity(prePostEnabled = true) 특정 주소로 접근하면 권한 및 인증을 미리 체크하기 위해 사용
encoder() BCryptPasswordEncoder는 Spring Security에서 제공하는 비밀번호 암호화 객체 이다. 비밀번호를 암호화해서 사용할 수 있도록 Bean으로 등록한다.
configure(AuthenticationManagerBuilder auth) Spring Security에서 모든 인증처리는 AuthenticationManager를 통해 이루어지는데, AuthenticationManager를 생성하기 위해서 AuthenticationManagerBuilder를 사용해 생성한다. 로그인 인증을 위해 MyUserDetailsService에서 UserDetailsService를 implements하여 loadUserByUsername() 메소드를 구현 했다. 그리고 AuthenticationManager에게 어떤 해쉬로 암호화했는지 알려주기 위해 passwordEncoder를 사용했다.
configure(WebSecurity web) 인증을 무시할 경로를 설정해준다. static 하위 폴더 (css, js, img)는 무조건 접근이 가능해야하기 때문에 인증을 무시하게 해 주었다.
configure(HttpSecurity http) HttpSecurity 를 통해 HTTP 요청에 대한 보안을 구성할 수 있다. csrf().disable() Spring Security에서는 csrf 토큰 없이 요청하면 해당 요청을 막기 때문에 잠깐 비활성화해주었다. authorizeRequests() HttpServletRequest에 따라 접근을 제한한다. antMatchers() 메소드로 특정 경로를 지정하며, permitAll(),hasRole() 메소드로 권한에 따른 접근 설정을 한다. 예를 들면, .antMatchers( “/admin/**” ).hasRole( “ADMIN” ) /admin 으로 시작하는 경로는 ADMIN 권한을 가진 사용자만 접근이 가능하다. .antMatchers( “/user/**” ).hasRole( “USER” ) /user 로 시작하는 경로는 USER 권한을 가진 사용자만 접근이 가능하다. .antMatchers(“/”, “/auth/**”, “/posts/read/**”, “/posts/search/**”) .permitAll() 위 경로에 대해 권한 없이 접근이 가능하다. .anyRequest() .authenticated() 그 외의 경로는 인증된 사용자만이 접근이 가능하다. formLogin()
form 기반으로 인증한다. /login 경로로 접근하면, Spring Security에서 제공하는 로그인 Form을 사용할 수 있다. .loginPage(“/auth/login”) 기본으로 제공되는 form 말고, 커스텀 로그인 폼을 사용하기 위해 loginPage() 메소드를 사용했다. .loginProcessingUrl(“/loginProc”) Security에서 해당 주소로 오는 요청을 낚아채서 수행한다. .defaultSuccessUrl(“/”) 로그인 성공 시 이동되는 페이지이다. logout() 로그아웃을 지원하는 메소드이며, WebSecurityConfigureAdapter를 사용하면 자동으로 적용된다. 기본적으로 “/logout”에 접근하면 HTTP 세션을 제거 해준다. 필자는 그냥 명시적으로 작성했다. .logoutSuccessUrl(“/”) 로그아웃 성공시 이동되는 페이지이다. .invalidateHttpSession(true) HTTP 세션을 초기화하는 작업이다.
5. 세션정보 저장 dto 클래스
@Getter public class UserSessionDto implements Serializable { private String username; private String password; private String nickname; private String email; private Role role; /* Entity -> Dto */ public UserSessionDto(User user) { this.username = user.getUsername(); this.password = user.getPassword(); this.nickname = user.getNickname(); this.email = user.getEmail(); this.role = user.getRole(); } }
인증된 사용자 정보를 세션에 저장하기 위한 클래스이다.
User 엔티티 클래스에 직접 세션을 저장하려면 직렬화를 해야 하는데,
엔티티 클래스에 직렬화를 해준다면 추후에 다른 엔티티와 연관관계를 맺을 시 직렬화 대상에 다른 엔티티까지 포함될 수 있어
성능 이슈, 부수 효과 우려가 있다. -스프링 부트와 AWS로 혼자 구현하는 웹 서비스 中-
6. UserDetailsService와 UserDetails 구현 클래스
@RequiredArgsConstructor @Component public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; private final HttpSession session; /* username이 DB에 있는지 확인 */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException(“해당 사용자가 존재하지 않습니다. : ” + username)); session.setAttribute(“user”, new UserSessionDto(user)); /* 시큐리티 세션에 유저 정보 저장 */ return new CustomUserDetails(user); } }
UserDetailsService의 loadUserByUsername 메소드 오버라이딩 후 구현
/* * 스프링 시큐리티가 로그인 요청을 가로채 로그인을 진행하고 완료 되면 UserDetails 타입의 오브젝트를 * 스프링 시큐리티의 고유한 세션저장소에 저장 해준다. * */ @AllArgsConstructor public class CustomUserDetails implements UserDetails { private final User user; @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } /* 계정 만료 여부 * true : 만료 안됨 * false : 만료 */ @Override public boolean isAccountNonExpired() { return true; } /* 계정 잠김 여부 * true : 잠기지 않음 * false : 잠김 */ @Override public boolean isAccountNonLocked() { return true; } /* 비밀번호 만료 여부 * true : 만료 안됨 * false : 만료 */ @Override public boolean isCredentialsNonExpired() { return true; } /* 사용자 활성화 여부 * true : 만료 안됨 * false : 만료 */ @Override public boolean isEnabled() { return true; } /* 유저의 권한 목록 */ @Override public Collection extends GrantedAuthority> getAuthorities() { Collection
collectors = new ArrayList<>(); collectors.add(() -> “ROLE_”+user.getRole()); return collectors; } } /* 주석 참고 */
7. Service
@RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository; private final BCryptPasswordEncoder encoder; @Transactional public Long join(UserDto dto) { dto.setPassword(encoder.encode(dto.getPassword())); return userRepository.save(dto.toEntity()).getId(); } }
사용자 비밀번호를 해쉬 암호화 후 레파지토리에 저장한다.
8. Controller
/** * 화면 연결 Controller */ @Controller public class UserIndexController { @GetMapping(“/auth/join”) public String join() { return “/user/user-join”; } @PostMapping(“/auth/joinProc”) public String joinProc(UserDto userDto) { userService.userJoin(userDto); return “redirect:/auth/login”; } @GetMapping(“/auth/login”) public String login() { return “/user/user-login”; } }
/auth/** 경로에 대해 권한 없이 접근 가능하도록 설정해뒀기에 각 url에 /auth/를 붙여주었다.
… UserSession user = (UserSession) session.getAttribute(“user”); if (user != null) { model.addAttribute(“user”, user.getNickname()); } …
CustomUserDetailsService에서 세션정보를 저장하고 PostsIndexController 클래스에 가져와 각각 model에 담았다.
9. Mustache
header.mustache
세션 유무에 따라 로그인 or 로그아웃을 할 수 있도록 했다.
join.mustache
{{>layout/header}}
{{>layout/footer}}
login.mustache
{{>layout/header}}
{{>layout/footer}}
Spring Security에서 데이터 전달은 Form을 사용 (name 값으로 데이터 전달)
JSON으로 데이터 전달 시 label for와 id를 사용 (헷갈리지 않기!)
10. 결과 확인
10-1. 미 로그인 사용자 화면
메인 검색 게시글 상세보기
인증되지 않은 사용자는 메인화면, 검색, 상세보기만 가능하며, 글의 수정, 삭제 또한 불가능하다.
인증되지 않은 사용자가 글쓰기를 누르면 다음과 같이 로그인 화면으로 이동시킨다.
로그인 화면
10-2. 회원가입 및 로그인 사용자 화면
회원가입
회원가입이 완료되면 로그인 화면으로 이동시키고 로그인이 완료되면 메인화면으로 이동한다.
인증된 사용자는 글쓰기가 가능하다.
글쓰기 후 메인화면 인증된 사용자 상세보기
수정, 삭제 또한 가능하다.
마무리하며
여기까지 일단 Spring Security로 회원가입과 로그인을 구현했다.
하지만 아직 보완해야 될 부분들이 몇 가지 있다.
1. csrf 문제
– Spring Security에서 csrf 토큰 없이 요청하면 그 요청을 막아버린다. 임시로 csrf().disable()을 사용해 비활성화했지만, 이는 완전히 해결된 것은 아니다. 필자는 mustache를 템플릿 엔진으로 사용하고 있는데, mustache는 csrf 토큰을 기본적으로 제공해주지 않기 때문에 참 난감하다.
>>해결 ( 포스트 보러 가기 )
2. 세션 중복 코드 개선
– 다음은 로그인 완료 시 세션정보를 가져와 보여주는 코드이다.
UserSession user = (UserSession) session.getAttribute(“user”);
Controller의 각 메소드마다 위 코드가 반복되고 있다. 이는 추후에 수정이 필요할 경우 모든 부분을 하나씩 수정해야 할 것이다.
이렇게 될 경우 유지보수성이 떨어지고, 다른 문제가 생길 수도 있을 것이다.
>>해결 ( 포스트 보러 가기 )
3. 유효성 검사 및 중복 검사
– 사용자가 회원가입 페이지에서 입력한 데이터 값이 서버로 전송되기 전에 특정 규칙에 맞게 입력되었는지,
가입하려는 아이디가 이미 존재하는지 등 확인하는 검증 단계가 반드시 필요할 것이다.
>>해결 ( 유효성 검사 , 중복 검사 )
4. 에러 메시지 출력
– 만약 로그인 페이지 상태에서 잘못된 로그인 정보를 입력한다고 치면, 로그인에 실패했지만 아무런 에러 메시지를 보지 못하고 로그인 페이지만 보일 것이다. 로그인이 실패했으면 어떤 이유로 실패했는지 에러 메시지를 띄워주어야 한다고 생각한다.
그래서, AuthenticationFailureHandler를 구현했지만, SPRING_SECURITY_LAST_EXCEPTION의 키값을 머스테치에 어떻게 넘겨줘야 될지 모르겠다..
>>해결 ( 포스트 보러 가기 )
+++2022/05/21 추가+++
4-1. UserDetailsService 세션 관련 문제
– 한동안 바빠서 확인해보지 못했는데, 오늘 나에게도 위와 같은 문제가 있었다는 것을 알았다.
필자는 로그인 실패 처리를 담당하는 CustomAuthFailureHandler 클래스의 메소드에 session.invalidate를 넣어주는 방법으로 우선 해결하였다.
(이 방법은 절대 best practice가 아님)
CustomAuthFailureHandler 관련 포스팅은 여기 에서 볼 수 있다.
좋은 해결 방법으로는 AuthenticationProvider를 커스터마이징하여 로그인 시 입력했던 id, pw와 UserDetails에서 가져온 User 객체의 id, pw를 비교 후 로그인에 성공했을 때 세션을 설정하는 방법인 것 같다.
생각지 못했던 문제를 짚어주신 user님 감사합니다. (__)
반응형
<스프링 부트 Spring boot> 회원가입 기능 구현
낭만인
– 회원가입 기능
스프링 부트에서 회원가입 기능을 구현할 때, 보통 Security를 이용하여 구현하는데 이번 경우에는 Security 없이 구현하였다. 따라서 로그인 기능 구현 코드와 크게 다르지 않다. 구현 순서는 다음과 같다.
1) 회원가입 페이지인 jsp에 태그로 사용자에게서 입력받은 정보를 서버(컨트롤러 서블릿)으로 보낸다.
– method : post 방식으로 서버에 전송한다.
– action : ‘join’으로 매핑된 컨트롤러의 메서드로 보낸다.
2) 회원가입 클릭 시 Controller는 웹 브라우저의 요청을 GetMapping으로 받고 Post에서 작업을 처리한다. 회원가입 폼에서 입력한 사용자의 정보는 VO 객체로 받고, 이를 Service의 메서드 호출과 동시에 매개변수로 보낸다.
@GetMapping(“join”) public void setInsert() throws Exception{}; @PostMapping(“join”) public String setInsert(MemberVO memberVO) throws Exception{ memberService.setInsert(memberVO); return “redirect:../member/login”; }
3) Controller를 통해 메서드를 호출받은 Service는 다시 Repository(구 DAO)의 메서드를 호출한다. 그리고 Service로부터 전달받은 사용자의 입력 정보인 VO 객체도 매개변수로 함께 보낸다. 이후 Repository에서 처리 후 리턴되는 값은 변수 result에 저장한다.
public int setInsert(MemberVO memberVO) throws Exception{ int result = memberRepository.setInsert(memberVO); return result; }
4) Repository는 Service에서 전달받은 사용자의 입력 정보인 VO 객체를 통해 작업을 실행한다. 실 작업은 Mapper에서 이루어진다.
public int setInsert(MemberVO memberVO) throws Exception;
5) Mapper에서는 DB와의 연동을 통해 sql문을 보내어 사용자가 입력한 정보를 회원정보로 추가한다 (추후 중복체크 기능도 추가 구현이 필요하다).
insert into destudymember (id, pw) values (#{id}, #{pw})
[Spring Boot Tutorial] 10. 회원가입 화면 만들기
이전 포스팅에서 JDBC기반의 Spring Security 인증&인가를 이용하여 웹 로그인을 했습니다.
실질적인 테스트를 위해서는 ROLE_VIEW 권한을 가지고 있는 사용자를 만들어야할 텐데, 사용자의 비밀번호를 BCryptPasswordEncoder를 이용해 변환해야하는 번거로움이 있습니다.
물론 tip에서 만들었던 문자열 인코더 api를 이용하여 설정할 비밀번호의 변환값을 직접 sql로 insert 해도 되지만, 회원을 추가할 때마다 db를 통해 직접 추가해야하기 때문에 매우 성가십니다.
이번 시간에는 간단한 회원가입 화면을 만들어보도록 하겠습니다.
1. 만들고자하는 화면
기존의 로그인 화면에서 Login버튼 아래에 Join버튼을 추가합니다.
필수 정보인 이메일, 비밀번호, 이름, 성별 과 옵션 정보인 생년월일, 전화번호 를 입력받습니다.
Back버튼을 누를시 이전화면(로그인 화면)으로 이동됩니다.
필수 정보인 이메일, 비밀번호, 이름을 입력하지 않을 시, submit되지 않습니다.
회원가입 중, 중복된 이메일일 경우에는 중복알림을 띄운 후 이메일 input을 지우고 focus 합니다.
회원가입 성공할 경우 회원가입 완료 알림 후 로그인 화면으로 이동됩니다.
회원가입 로직에는 회원 정보 추가와 view 페이지 접속 권한을 위한 ROLE_VIEW 권한 추가가 들어있습니다.
2. 회원가입 화면 이동 버튼 추가하기
< section layout: fragment = " f-content " > … < div > < button type = " button " class = " btn btn-info btn-large form-control " id = " btn_login " > Login button > div > < div > < button type = " button " class = " btn btn-secondary btn-large form-control " id = " btn_joinForm " > Join button > div > … section >
로그인 버튼 아래에 회원가입 화면 이동 버튼을 추가합니다.
< th: block layout: fragment = " f-script " > < script > $ ( function ( ) { … $ ( “#btn_joinForm” ) . on ( “click” , function ( ) { location . href = “[[@{/join}]]” ; } ) ; } ) ; script > th: block >
/join 페이지로 이동합니다.
타임리프 템플릿상에서 context-relative url을 가져오기 위해 @{} 를 씁니다.
javascript태그 내에서 context-relative url을 가져오기 위해서는 [[]] 괄호를 붙여야 합니다.
3. 회원가입 페이지로 이동 처리
컨트롤러에 /join url에 관한 Get 메서드를 생성합니다.
@RequiredArgsConstructor @Controller public class LoginController { private final UserService userService ; . . . @GetMapping ( value = “/join” ) public String joinForm ( @AuthenticationPrincipal SecurityUser securityUser ) { if ( securityUser != null && securityUser . getRoleTypes ( ) . contains ( RoleType . ROLE_VIEW ) ) { return “redirect:/v” ; } return “login/join” ; } }
/login url로 접속했을 시, ROLE_VIEW 권한을 가진 사용자가 로그인 중일 경우 view 페이지의 메인화면으로 이동되게 했던 것과 같이 joinForm 메서드에도 사전 체크를 추가합니다.
로그인되지 않거나 ROLE_VIEW 권한을 보유하고 있지 않을 경우, join페이지를 엽니다.
4. 회원가입 화면 만들기
4-1. 기본 뼈대 구성
< html lang = " ko " xmlns: th = " http://www.thymeleaf.org " xmlns: layout = " http://www.ultraq.net.nz/thymeleaf/layout " layout: decorator = " cmm/layout_login " > < th: block layout: fragment = " f-title " > DEMO 회원가입 th: block > < section layout: fragment = " f-content " > section > < th: block layout: fragment = " f-script " > th: block >
login 화면에서 이용했었던 cmm/layout_login 레이아웃을 이용합니다.
회원가입에 필요한 정보는 f-content 영역에 넣고,
회원가입에 필요한 기타 javascript 코드는 f-script 내부에 작성합니다.
4-2. 회원가입 템플릿 만들기
위의 기본 뼈대 중 section영역을 채웁니다.
< section layout: fragment = " f-content " > < div class = " login_wrapper " style = " margin : calc ( 50 vh - 320 px ) auto 0 px ; " > < h1 > < img th: src = " @{/static/img/like.png} " width = " 50 " height = " auto " alt = " demo " id = " btn_loginHome " > h1 > < form method = " post " th: action = " @{/join} " > < div > < input type = " text " name = " email " class = " form-control " placeholder = " 이메일 " autocomplete = " off " required /> div > < div > < input type = " password " name = " password " class = " form-control " placeholder = " 비밀번호 " autocomplete = " off " required /> div > < div > < input type = " text " name = " name " class = " form-control " placeholder = " 이름 " autocomplete = " off " required /> div > < div style = " margin : 0 ; " > < div class = " custom-control custom-radio custom-control-inline " > < input type = " radio " class = " custom-control-input " id = " sex-1 " name = " sex " value = " 1 " checked > < label class = " custom-control-label " for = " sex-1 " > 남 label > div > < div class = " custom-control custom-radio custom-control-inline " > < input type = " radio " class = " custom-control-input " id = " sex-2 " name = " sex " value = " 2 " > < label class = " custom-control-label " for = " sex-2 " > 여 label > div > div > < div > < input type = " text " name = " birthDate " class = " form-control " placeholder = " 생년월일(yyMMdd) " autocomplete = " off " /> div > < div style = " margin-bottom : 50 px ; " > < input type = " text " name = " phoneNumber " class = " form-control " placeholder = " 전화번호(-생략) " autocomplete = " off " /> div > < div > < button type = " submit " class = " btn btn-dark btn-large form-control " > Join button > div > < div > < button type = " button " class = " btn btn-secondary btn-large form-control " id = " btn_loginForm " > Back button > div > form > < hr class = " separator " /> < div > < h1 > DEMO h1 > < p > ©2019 All Rights Reserved. p > div > div > section >
필수 입력요소인 email, password, name input 태그에 required 속성을 추가합니다.(ln 6,9,12)
form 태그 안의 required 속성을 이용한 html validation 체크는 form이 submit될 때 이뤄집니다.
validation 체크를 해야할 Join버튼의 type을 submit으로 설정합니다.(ln 31)
4-3. 비동기로 중복체크 및 회원가입
< th: block layout: fragment = " f-script " > < script > $ ( function ( ) { $ ( “form” ) . on ( “submit” , function ( e ) { e . preventDefault ( ) ; var $form = $ ( this ) . closest ( “form” ) ; var formData = $form . serializeObject ( ) ; $ ( “section div:eq(0)” ) . append ( ‘
‘ ) ; $ . ajax ( { type : $form . attr ( “method” ) , dataType : ‘json’ , contentType : “application/json” , data : JSON . stringify ( formData ) , url : $form . attr ( “action” ) , beforeSend : function ( xhr ) { xhr . setRequestHeader ( $ ( “meta[name=’_csrf_header’]” ) . attr ( “content” ) , $ ( “meta[name=’_csrf’]” ) . attr ( “content” ) ) ; } , success : function ( res ) { $ ( “#d-spin” ) . remove ( ) ; if ( res . duplicate ) { $ . notify ( “중복된 이메일 입니다.” ) ; $ ( “input[name=’email’]” ) . val ( “” ) ; $ ( “input[name=’email’]” ) . focus ( ) ; } else if ( res . success ) { $ . notify ( “회원 가입 완료되었습니다.” ) ; setTimeout ( function ( ) { window . location = document . referrer } , 800 ) ; } else { $ ( “#d-spin” ) . hide ( ) ; $ . notify ( “crud fail” ) ; } } , error : function ( error ) { alert ( error . errorMsg ) ; } } ) ; } ) ; $ ( “#btn_loginForm” ) . on ( “click” , function ( ) { location . href = document . referrer ; } ) ; } ) ; script > th: block >request를 json 형태로 보내기 위해 contentType을 application/json 으로 설정하고,
form 내의 name 설정된 input태그를 serializeObject하여 객체화 하였고
이 값을 JSON.stringify로 문자열형태로 변환하여 보냈습니다.
이 데이터는 @RequestBody 를 이용하여 받을 수 있습니다.
이메일이 중복될 경우 응답값에서 duplicate: true를 반환하며
정상적으로 회원가입을 완료할 경우 success: true를 반환합니다.
serializeObject와 JSON.stringify를 거친 데이터 값
참고로 serializeObject 메서드는 jquery3 에서는 포함되어있지 않아, 레이아웃의 jquery3 import문 아래에 추가했습니다.
$ . fn . serializeObject = function ( ) { var obj = null ; try { if ( this [ 0 ] . tagName && this [ 0 ] . tagName . toUpperCase ( ) == “FORM” ) { var arr = this . serializeArray ( ) ; if ( arr ) { obj = { } ; jQuery . each ( arr , function ( ) { obj [ this . name ] = this . value ; } ) ; } } } catch ( e ) { alert ( e . message ) ; } finally { } return obj ; } ;
5. 중복체크 및 회원가입 처리
실질적인 회원가입처리를 하는 컨트롤러 메서드를 만듭니다.
5-1. controller – post /join
@RequiredArgsConstructor @Controller public class LoginController { private final UserService userService ; . . . @ResponseBody @PostMapping ( value = “/join” ) public Map < String , Object > join ( @RequestBody UserValue value ) { Map < String , Object > response = new HashMap < > ( ) ; if ( userService . findByEmail ( value . getEmail ( ) ) . isPresent ( ) ) { response . put ( “duplicate” , true ) ; return response ; } response . put ( “success” , userService . join ( value ) != null ? true : false ) ; return response ; } }
이메일이 이미 등록되어있을 경우 duplicate: true를 반환합니다. (ln 13-16)
회원 가입 성공할 경우 success: true를 반환합니다. (ln 18)
5-2. service
@RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository ; private final UserRoleRepository userRoleRepository ; private final PasswordEncoder passwordEncoder ; . . . @Transactional public User save ( UserValue value ) { User user = User . builder ( ) . type ( value . getType ( ) ) . email ( value . getEmail ( ) ) . birthDate ( value . getBirthDate ( ) ) . name ( value . getName ( ) ) . password ( passwordEncoder . encode ( value . getPassword ( ) ) ) . phoneNumber ( value . getPhoneNumber ( ) ) . sex ( value . getSex ( ) ) . build ( ) ; return userRepository . save ( user ) ; } @Transactional private UserRole saveUserRole ( User user ) { return userRoleRepository . save ( UserRole . builder ( ) . user ( user ) . roleName ( RoleType . ROLE_VIEW ) . build ( ) ) ; } public User join ( UserValue value ) { User user = save ( value ) ; saveUserRole ( user ) ; return user ; } public Optional < User > findByEmail ( String email ) { return userRepository . findByEmail ( email ) ; } }
join 메서드는 user를 저장 한 후, 그 사용자의 ROLE_VIEW user_role을 저장합니다. (ln 30-31)
비밀번호는 passwordEncoder를 이용하여 BCryptPasswordEncoder를 이용하여 인코딩한 값을 넣습니다.(ln 17)
5-3. repository
@Repository public interface UserRepository extends JpaRepository < User , Long > { . . . Optional < User > findByEmail ( String email ) ; }
email을 이용하여 user엔티티를 조회하는 메서드를 추가합니다.
email은 unique 컬럼이기 때문에 findTop1 하지 않아도 됩니다.
save 메서드는 JpaRepository에 정의되어 있으므로 메서드를 추가하지 않아도 됩니다.
※ GitHub에서 demo 프로젝트를 다운받아 볼 수 있습니다.
스프링부트로 게시판 만들기 5 : 회원가입 기능 추가
반응형
개요
이번 포스팅에서는 회원가입 기능을 추가해보도록 하겠다.
설계
먼저 회원 정보는 간단하게 아래의 데이터만을 가지고 수행하도록 한다.
아이디 패스워드 이메일
회원 가입 기능은 대략적으로 아래와 같은 흐름을 따라서 만들어 진다.
사용자가 서버에 회원 정보를 전송한다. 서버는 회원 정보를 받고 아래와 같은 작업을 수행한다. 먼저 데이터베이스에 중복된 아이디가 있는지 확인 한다. 만약 중복된 아이디가 있는 경우, 이미 있는 회원이라 가입이 안된다는 메시지를 보낸다. 그렇지 않은 경우 전달 받은 데이터를 데이터베이스에 추가 한다. 이때, 전달받은 패스워드를 해싱해서 저장한다. 회원가입을 성공했다는 메시지를 전달한다.
구현
먼저 아래와 같이 실제 회원 정보에 해당하는 Account Entity를 추가 해 보도록 하자.
package com.semtax.application.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import java.util.Objects; @Entity public class Account { @Id @GeneratedValue private Long id; private String username; private String email; private String password; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Account account = (Account) o; return id.equals(account.id) && username.equals(account.username) && email.equals(account.email) && password.equals(account.password); } @Override public int hashCode() { return Objects.hash(id, username, email, password); } }
그리고 나서, 아래와 같이 실제 데이터베이스에 회원 정보를 저장/조회하는 로직을 구현 한다.
package com.semtax.application.repository; import com.semtax.application.entity.Account; import org.springframework.data.jpa.repository.JpaRepository; public interface AccountRepository extends JpaRepository
{ public Account findByUsername(String username); public Account findByUsernameAndPassword(String username, String password); } 스프링 데이터 JPA는 위와 같이, 메소드 이름을 스프링 데이터 JPA 규칙에 맞게 지어주면 해당 Entity에 맞게 데이터를 조회/조작 하는 코드를 자동으로 생성해주게 된다.
(물론 비즈니스 로직이 복잡해지면 레포지토리 코드를 직접 작성해야 한다)
추가적으로, 회원가입 정보를 받기위한 DTO 데이터도 작성해주도록 하자.
package com.semtax.application.dto; public class AccountDTO { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
그런 뒤, 회원가입 컨트롤러 코드를 아래와 같이 작성해준다.
package com.semtax.application.controller; import com.semtax.application.entity.Account; import com.semtax.application.repository.AccountRepository; import com.semtax.application.util.Hashing; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @Controller public class UserRegisterController { @Autowired AccountRepository accountRepository; @CrossOrigin(origins = “*”, allowedHeaders = “*”) @PostMapping(“/register”) @ResponseBody public String registerUser(@RequestBody Account newAccount) { String username = newAccount.getUsername(); String password = Hashing.hashingPassword(newAccount.getPassword()); String email = newAccount.getEmail(); if(username.equals(“”) || password.equals(“”) || email.equals(“”)) return “failed”; Account account = new Account(); account.setUsername(username); account.setPassword(password); account.setEmail(email); if(accountRepository.findByUsername(username) != null) return “failed”; accountRepository.save(account); return “success”; } }
위의 코드에서 전달받은 패스워드를 해싱해주는것을 알 수 있다.
이렇게 하는 이유는 이를 통해 만약 데이터베이스가 도난 당하더라도(그러면 안되기는 하지만),
패스워드 자체를 가지고 있는것은 아니기 때문에, 상대적으로 피해가 덜하게 된다.
(결국 탈취한 사람이 패스워드를 알아내기 까지에 시간이 소요되므로)
그리고, 실제로 해싱해주는 래퍼 클래스를 추가해주자.
public class Hashing { public static final String SALT = “!@salt$%^&”; public static String hashingPassword(String input) { try { MessageDigest md = MessageDigest.getInstance(“SHA-256”); byte[] hashData = md.digest(input.getBytes(StandardCharsets.UTF_8)); BigInteger number = new BigInteger(1, hashData); StringBuilder hexString = new StringBuilder(number.toString(16)); while (hexString.length() < 32) { hexString.insert(0, '0'); } return hexString.toString(); }catch(NoSuchAlgorithmException e) { return input; } } } 추가 과제(?) 사실 위의 코드는 그렇게 까지 좋은 코드는 아니다. 왜냐하면 컨트롤러에 실제 회원가입을 처리하는 로직과 다른 로직들이 섞여있기 때문이다. 나중에 시간이 나면 저 위에 있는 회원 가입 코드를 서비스 영역으로 빼보는걸 해보는것도 좋을것 같다. 테스트 Postman을 켜서 테스트를 해보자. 먼저 Postman을 켜서 아래와 같이 회원가입 요청을 보내 주자. 다음으로, DB를 확인해보자. 정상적으로 들어오는것을 확인 할 수 있다. 결론 일단, 이번 포스팅에서는 회원 가입 만 하는 기능을 구현하였다. 사실 회원 가입 기능 만 있는 경우는, 접근 권한 제어나 접근 권한 부여를 하지 않으므로 그다지 의미가 없다. 다음 포스팅에서는 로그인 기능을 구현 하면서, 저 접근 권한을 어떠한 방식으로 구현하는지 알아보도록 하겠다. 반응형
[spring boot] spring security를 이용한 회원가입 기능 구현
간단하게 사용자 이름(닉네임)과 비밀번호 정보를 가지고 회원가입 기능을 구현해보겠습니다. 저번에 포스팅하다가 끊긴 게시판 만들기와 같은 프로젝트입니다. 프론트엔드는 이미 따로 구성해놓으셨다는 가정하에 진행하겠습니다. 저는 디자인을 잘 몰라서.. 프론트엔드 부분을 대충 구성해놓았기에 공유해드리기도 민망해서 그렇습니다.
제일 먼저 spring security를 사용하려면 dependency를 추가해줘야겠죠. build.gradle에 다음과 같은 의존성을 추가해줍니다. implementation ‘org.springframework.boot:spring-boot-starter-security’
자 기본 설정(?)은 완료가 되었으니 실질적인 코드 작성을 해보겠습니다.
일단 이번에도 JPA를 가지고 만들게요.
User 클래스
UserDto
일단 UserDto랑 그냥 User 클래스는 조금 다릅니다. User 클래스는 실제 DB 테이블과 매칭되도록 작성한 클래스인 반면 데이터를 주고받기 위해 생성한 자바빈 객체입니다.
toEntity()는 전달받은 객체를 Entity로 간편하게 바꿔주는 메서드입니다. JPA를 이용해서 객체를 저장할때 Entity의 형태로 저장해줘야 되므로 변환용 메서드를 만들어줬습니다. 또한 제가 spring security를 사용한 부분이기도 합니다. toEntity의 password를 보시면 Bcrypt방식으로 인코딩을 하라고 작성해줬습니다. 이 기능을 spring security에서 제공해준답니다.
UserRepository 인터페이스
JpaRepository를 상속받는 interface를 만들어줍니다. 제너릭 안에 Entity와 ID의 타입을 적어줍니다.
UserService
@Transactional 위의 코드들은 생성자 주입을 해주는 부분입니다. @Transactional이 붙은 메서드는 프록시(Proxy) 객체를 return합니다. 또 그 프록시 객체는 정상 여부에 따라서 롤백과 커밋 기능을 수행합니다. 즉 예외가 발생되면 알아서 rollback을 해주기에 편리하니까 사용해봤습니다.
UserController
저 같은 경우에는 임시로 /register url로 접근을 하면 회원가입 서비스가 돌아가도록 구성해놨습니다. 또한 단순 db 저장까지가 이번 포스팅의 목표이므로 루트 페이지로 강제 이동시키고 끝낼게요.
자 여기까지 작성해주시면 기본적인 회원가입 서비스 기능 구현은 끝났습니다. 제대로 돌아가는지 직접 브라우저에서 접속해보시면 아마도 본인이 만든 적이 없는 페이지가 막 뜰겁니다.
형태는 사람마다 다르던데 저는 localhost:8080/login 이라면서 다른 url로 이동해도 계속 이 페이지가 뜨더라구요. spring security가 자체적으로 인증되지 않은 사용자의 접근을 막아두었기 때문에 이러한 현상이 발생합니다. 이것을 해결하는 방법에 대해서는 다음 포스팅에서 다루겠습니다.
참고
goddaehee.tistory.com/167
mommoo.tistory.com/92
키워드에 대한 정보 스프링 부트 회원 가입
다음은 Bing에서 스프링 부트 회원 가입 주제에 대한 검색 결과입니다. 필요한 경우 더 읽을 수 있습니다.
이 기사는 인터넷의 다양한 출처에서 편집되었습니다. 이 기사가 유용했기를 바랍니다. 이 기사가 유용하다고 생각되면 공유하십시오. 매우 감사합니다!
사람들이 주제에 대해 자주 검색하는 키워드 2020 04 05, 스프링부트, 문제, 회원가입 기능을 구현해주세요
- 동영상
- 공유
- 카메라폰
- 동영상폰
- 무료
- 올리기
2020 #04 #05, #스프링부트, #문제, #회원가입 #기능을 #구현해주세요
YouTube에서 스프링 부트 회원 가입 주제의 다른 동영상 보기
주제에 대한 기사를 시청해 주셔서 감사합니다 2020 04 05, 스프링부트, 문제, 회원가입 기능을 구현해주세요 | 스프링 부트 회원 가입, 이 기사가 유용하다고 생각되면 공유하십시오, 매우 감사합니다.