[common controller]
회원가입 이동을 허용하는 url을 커먼 컨트롤러에서 만들어 볼게요.
그전에 아무나 회원가입 페이지에 들어갈 수 있도록 퍼밋올에! 회원가입 페이지를 넣어주자고요!
.antMatchers("/","/signup").permitAll()
라잌 디스.
컨트롤러ㄱ
@Controller
public class Common {
@GetMapping("/signup")
public String signup() {
return "sign/signup";
}
}
이렇게 하고 sign 패키지에 signup 을 넣어주었습니다.
* 여기에서는 아직 기능구현을 안해주었기때문에 일단 이렇게만 설정했어요~^^ commoncontroller는 걍 경로만 지정해주었습니다!
적용 순서
필터에서 먼저 걸리고 컨트롤러로 가는겁니다. 시큐리티는 이름 자체가 시큐리티필터체인임.
[signup.html]
<a href="/">HOME</a>
<p>회원가입페이지</p>
<div class="container">
<form method="post" action="/signup">
<h2>Please input your info</h2>
<p>
<label for="email">Username</label>
<input type="text" id="email" name="email" placeholder="Email" required>
</p>
<p>
<label for="password">Password</label>
<input type="password" name="pass" placeholder="Password" required>
</p>
<p>
<label for="name">Name</label>
<input type="text" name="name" placeholder="Name" required>
</p>
*required - 필수로 입력해달라는 뜻!
*for - 아이디랑 매칭됩니다.
이렇게 하고 회원가입 누르면 로그인 페이지로 갑니다,,, 왜 그러시죠?
login페이지가 체인에 걸렸어요.. 왜죠?
바아로 csrf! 때문입니다.
CSRF
이아이가 보내는 겁니다.
http.csrf().disable();
post로 보낼때는 csrf 설정을 해주어야합니다.
disable 을 넣어야(안보어야) post로 가고요, 우리는 그냥 disable 안하고 걍 갈겁니다. 어케요? 토큰으로요!
html에 설정을 해주어야합니다. 토큰 번호는 그때그때 달라요!
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
출처 : https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#servlet-csrf-include
Cross Site Request Forgery (CSRF) for Servlet Environments :: Spring Security
There are a few special considerations to consider when implementing protection against CSRF attacks. This section discusses those considerations as it pertains to servlet environments. Refer to CSRF Considerations for a more general discussion. Logging In
docs.spring.io
여기에서 include the csrf token 으로 갑니다.
그런데 위에 토큰 매일 바뀌니까 저거 쓰지 않고요,
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
으로 해주면 됩니다.
그리고 더하여 헤드에다가 타임리프를 넣어주어야 합니다~^^ 타임리프없으면 적용이 안됩니다.
<html xmlns:th="http://thymeleaf.org">
//위에건 헤드에 아래건 바디에
<input type="hidden" th:name="${_csrf.parameterName}"
th: value="${_csrf.token}"/>
기억❗
폼에서 포스트로 전송되고 csrf가 적용 되어 있을때는 토큰을 전달해야합니다.
이렇게 까지 했는데 이제 오류가 났습니다,, 왜냐고요? 저 위에 value에 th: 다음 띄어쓰기 때문이죠,, ㅋ,,차암나!!!
다시 고쳐줍니다 젭r
th:value="${_csrf.token}"
이렇게 하고요 이제 post를 받을겁니다. membercontroller로 새로 받아서 할게요!
[MemberController]
@Controller
public class MemberController {
//회원가입 처리하기위한 주소
@PostMapping("/signup")
public String signup() {
return "redirect:/";
}
}
일단 인덱스로 다시 가는 방향으로 했습니다~!
그리고 데이터를 받아줘야하니까 그건 어디로요?
서비스~~
서비스는?
인터페이스~~
어케 만들어주냐고요? 맨 위에 선언!
@Autowired
private MemberService serivce;
//회원가입 처리하기위한 주소
@PostMapping("/signup")
public String signup(MemberInsertDTO dto) {
serivce.save(dto);
return "redirect:/";
이렇게요~!
하고 나면 service를 만들어줍니다.
그리고 service.save에 뜨는 빨간줄을 클릭하여 service에 save를 만들어 줄게요.
그러면 dto는?!? 도 만들어줍니다. 클릭해서요~!
앞으로 받을 정보들이 넘냐 많으니! .domain.dto.member 패키지 아래에 만들어주었습니다~!
@Setter
public class MemberInsertDTO {
//여기 선언하는 이름이랑 html의 name이 같아야함.
private String email;
private String pass;
private String name;
}
정보를 받아야 하니데이터들을 선언하고 setter를 어노테이션으로 넣어줍니다.
데이터를 선언할때는! 꼭 내가 html에 준 <name="ㅇㅇ"> 넣어준것과 동일하게 넣어주어야 합니다.
또한 기능 구현을 위해 service process를 만들어주어야 하는데요 이건 무조건? 클래스다~ (service impl이라고도 했었음.)
private MemberService serivce=new MemberServiceProcess();
이렇게 하면 프로세스 저기서 알아서 service를 상속받음.
만들면 뒤에 = 부터 끝까지 지워주기
이렇게 하고나면
@Service
public class MemberServiceProcess implements MemberService {
@Override
public void save(MemberInsertDTO dto) {
}
}
이렇게 뜹니다~!
[Entity]
데이터 베이스 때문에 만듭니다. dto로 받은거 저장해야지!
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Table(name = "sec_member")
@Entity
public class MemberEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long mno;
private String email;
private String pass;
private String name;
}
요렇게 만들어 주었어요. 테이블 이름은 sec_member!
그렇다면 당연히? repository도 만들어주어야 합니다.
@Repository
public interface MemberEntityRepository extends JpaRepository<MemberEntity, Long>{
}
jpa레포지토리를 받습니다! 기본적으로 저기에 기능들이 구현이 되어 있거든요. 더하여 상속 받을 entity와 id의 자료형을 꼭! 앞은 대문자로 설정해줍니당.
살짝 위를 보면 우리는 mno를 id로 설정을 해주었기 때문에 Long 형을 써주면 됩니당.
그리고 데이터 베이스에 추가해주려고 entity를 만들었다구 했죠? 그러면 거기에 담는 데이터는,,,?
DTO에 있는걸 받아오겠쥬.
public MemberEntity toEntity() {
return MemberEntity.builder()
.email(email).pass(pass).name(name)
.build();
}
DTO에 추가해줍니당~~,~~ ENTITY에 Builder 어노테이션을 추가해주었기 때문에 이렇게 빌더를 받아와줍니다.
MemberServiceProcess 에도 추가해줍니다! 넘 기본적인 거라 걍 다 추가해주자고요.
@Autowired
private MemberEntityRepository repository;
[PasswordEncode]
serviceprocess에 패스워드 인코딩을 해줍니다.
@Autowired
PasswordEncoder pe;
@Override
public void save(MemberInsertDTO dto) {
repository.save(dto.passEncode(pe).toEntity());
}
여기서 repository.save(dto.passEncode(pe).toEntity()); 는?
: 서비스에서 여기있는 메서드가 실행이 되면 인코딩 된 데이터로 바뀌겠죠? (InsertDTO의 private strng pass)
dto까지는 파라미터로 매핑한 원본!! 그다음에 엔티티로 넘긴다는 뜻입니다.
그러니 DTO에도 알려주어야하겠습니다~ 거기에 pass 있자나여.
public MemberInsertDTO passEncode(PasswordEncoder pe) {
pass=pe.encode(pass);
return this;
}
[Role 권한주기]
바로 MemberEntity에서 만들어 보겠습니다.
//role을 적용하는 편의메서드
public MemberEntity addRole(SecMemberRole role) {
roles.add(role);
return this;
}
권한은 이넘에서 만든다! 시큐리티로 슬거니까 시큐리티로 가져다놓을게요.
@RequiredArgsConstructor
public enum SecMemberRole {
GUEST("ROLE_GUEST","게스트"), //0
USER("ROLE_USER","회원"), //1
VIP("ROLE_VIP","vip회원"), //2
MANAGER("ROLE_MANAGER","부관리자"), //3
ADMIN("ROLE_ADMIN","관리자"); //4
final String role;
final String roleKr;
}
* 기본적으로 이넘의 타입은 클래스로 정의되어 있습니다. 이넘 클래스!
final String role; -> "ROLE_ADMIN"
final String roleKr; -> "관리자"
위에것을 넣어주어야 이렇게 각 역할에 대해 문자열로 입력 할 수 있습니다.
MemberEntity에 가서 권한 어노테이션 등등을 넣어줍니다.
//권한
//User, UserDetails : Set<GrantedAutority> authorities;
@Builder.Default
@Enumerated(EnumType.STRING)
@ElementCollection(fetch = FetchType.EAGER) //memberEntity 1:N 관계입니다.
Set<SecMemberRole> roles=new HashSet<>();
저 HashSet이 임포트가 안되가지고 진짜,,, 하,, 걍 결국 손으로 써서 넣어줫습니다. 그랬더니 오류가 사라지더군녀 차암나!
기가 찬 마음을 접고,
MemberServiceProcess 에도 넣어주자고요.
@Override
public void save(MemberInsertDTO dto) {
repository.save(dto.passEncode(pe).toEntity()
.addRole(SecMemberRole.GUEST)
.addRole(SecMemberRole.USER));
}
아까 보았던것에 addrole을 추가해주었습니다~! 후하후하후 드디어 끝이군요.
저기 보면 email과 name에 null값이 있는데 값을 넣었는데 값이 없다는거에요 ,,? 차암나!!!
알고보니, html의 input태그 안에 있는 name과 MemberInsertDTO에서 선언을 같게 안해줌,, ㅠㅡㅠ 실수조심 제발~!
암튼 잘 들어갔습니당 ^^,.,.
/*전체 코드*/
- CommonController
@Controller
public class Common {
//회원가입 페이지 이동
@GetMapping("/signup")
public String signup() {
return "sign/signup";
}
}
- MemberController
@Controller
public class MemberController {
@Autowired
private MemberService serivce;
//회원가입 처리하기위한 주소
@PostMapping("/signup")
public String signup(MemberInsertDTO dto) {
serivce.save(dto);
return "redirect:/";
}
}
- MemberEntity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Table(name = "sec_member")
@Entity
public class MemberEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long mno;
private String email;
private String pass;
private String name;
//권한
//User, UserDetails : Set<GrantedAutority> authorities;
@Builder.Default
@Enumerated(EnumType.STRING)
@ElementCollection(fetch = FetchType.EAGER) //memberEntity 1:N 관계입니다.
Set<SecMemberRole> roles=new HashSet<>();
//role을 적용하는 편의메서드
public MemberEntity addRole(SecMemberRole role) {
roles.add(role);
return this;
}
}
- MemberEntityRepository
@Repository
public interface MemberEntityRepository extends JpaRepository<MemberEntity, Long>{
}
- MemberInsertDTO
@Setter
public class MemberInsertDTO {
//여기 선언하는 이름이랑 html의 input안의 name값이 같아야함.
private String email;
private String pass;
private String name;
public MemberInsertDTO passEncode(PasswordEncoder pe) {
pass=pe.encode(pass);
return this;
}
public MemberEntity toEntity() {
return MemberEntity.builder()
.email(email).pass(pass).name(name)
.build();
}
}
- SecMemberRole
//ENUM
@RequiredArgsConstructor
public enum SecMemberRole {
GUEST("ROLE_GUEST","게스트"), //0
USER("ROLE_USER","회원"), //1
VIP("ROLE_VIP","vip회원"), //2
MANAGER("ROLE_MANAGER","부관리자"), //3
ADMIN("ROLE_ADMIN","관리자"); //4
final String role;
final String roleKr;
}
- MemberService
public interface MemberService {
void save(MemberInsertDTO dto);
}
- MemberServicProcess
@Service
public class MemberServiceProcess implements MemberService {
@Autowired
private MemberEntityRepository repository;
@Autowired
PasswordEncoder pe;
@Override
public void save(MemberInsertDTO dto) {
repository.save(dto.passEncode(pe).toEntity()
.addRole(SecMemberRole.GUEST)
.addRole(SecMemberRole.USER));
}
}
-signup.html
<!DOCTYPE html>
<html xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="/">HOME</a>
<p>회원가입페이지</p>
<div class="container">
<form method="post" action="/signup">
<h2>Please input your info</h2>
<p>
<label for="email">Username</label>
<input type="text" id="email" name="email" placeholder="Email" required>
</p>
<p>
<label for="password">Password</label>
<input type="password" name="pass" placeholder="Password" required>
</p>
<p>
<label for="name">Name</label>
<input type="text" name="name" placeholder="Name" required>
</p>
<!-- csrf post전송시에 토큰을 전달해야합니다. -->
<input type="hidden" th:name="${_csrf.parameterName}"
th:value="${_csrf.token}"/>
<button type="submit">Sign in</button>
</form>
</div>
</body>
</html>
'애플리케이션 설계 및 테스트' 카테고리의 다른 글
스프링 시큐리티 - 기본 준비 (0) | 2022.07.14 |
---|