프로잭트에서 기존 MVC패턴으로 작성되어있던 회원 가입과 로그인에 SpringSecurity를 적용하였다.
기존 Model로 회원 정보를 받아 로그인 하는 로직으로 로그인을 처리하였다.
시큐리티를 적용하며 혼란스러웠던 점은 로그인 로직을 스프링 시큐리티가 실행해 준 다는 점을 알지 못하여서 Post로 받는 로그인 Request에 대해 어떻게 로그인을 실행하는가 이해가 안되어 한참 헤메었다.
아래 코드로 스프링 회원 가입 및 로그인을 정리해본다.
회원 Entity
@Data
@Entity
public class Member {
public Member(){
}
public Member(String loginId, String nickName, String password, String email, String profile) {
this.loginId = loginId;
this.nickName = nickName;
this.password = password;
this.email = email;
this.profile = profile;
}
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@NotEmpty(message = "아이디를 입력하세요")
@Pattern(regexp = "^[a-z0-9]{4,20}$", message = "아이디는 영어 소문자와 숫자만 사용하여 4~20자리여야 합니다.")
private String loginId;
@NotEmpty(message = "닉네임을 입력하세요")
@Length(min = 2, max = 16,message = "2자리 이상 16자리 이하입니다.")
private String nickName;
@NotEmpty(message = "비밀번호를 입력하세요")
private String password;
private String passwordConfirm;
private String Role;
@NotEmpty(message = "이메일 주소를 입력하세요")
@Email(message = "이메일 형식이 올바르지 않습니다.")
private String email;
private String profile;
@CreationTimestamp
private Timestamp created;
@UpdateTimestamp
private Timestamp updated;
}
MemberController
public class MemberController {
private final MemberRepository memberRepository;
private final MemberValidator memberValidator;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@GetMapping
public String addForm(@ModelAttribute Member member) {
return "users/addForm";
}
@PostMapping
public String save(@Valid @ModelAttribute Member member, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
memberValidator.validate(member, bindingResult);
if (bindingResult.hasErrors()) {
log.info("error={}", bindingResult);
return "users/addForm";
}
member.setRole("ROLE_USER");
String rawPassword = member.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
member.setPassword(encPassword);
memberRepository.save(member);
/**
* 이메일 보내는 프로세스 추가
*/
return "redirect:/";
}
회원 객체를 저장할 때 .bCrytPasswordEncoder.encode()로 유저가 입력한 비밀번호를 암호화 하여 저장한다.
SecurityConfig
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final LoginService loginService;
@Bean
public BCryptPasswordEncoder encodePWD() {
return new BCryptPasswordEncoder();
}
//시큐리티가 대신 로그인할때 받는 password 가 어떤 해쉬로 회원가입이 되었는지 해당 해쉬를 암호화하여 비교가능
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(loginService).passwordEncoder(encodePWD());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/users", "/login", "/posts", "/logout", "/css/**", "/*.ico", "/error", "/post/{postId}")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("loginId")
.loginProcessingUrl("/loginProc")
.defaultSuccessUrl("/");
}
}
Config에 접근 가능한 URL경로를 .antmatchers()로 등록한다.
.permitAll() : .antmatchers()에 등록한 경로를 인증 없이 접근 허용한다는 뜻
.anyRequest() : 모든 요청은
.authenticated() : 인증 되어야 한다
.loginPage() : 기본 로그인 페이지의 URL경로
.loginProcessingUrl() : 로그인 프로세스를 보낼 URL정보
.defaultSeccessUrl() : 로그인이 성공하면 보여줄 페이지
스프링 시큐리티 적용 시 로그인을 스프링 시큐리티가 대신해준다 .loginProcessingUrl() 에 이 작업을 대신 수행할 URL을 기입하고 프론트에서 받은 로그인 Request를 /logingProc URL로 전송하여 로그인 프로세스를 대신 할 수 있게 설정해야 한다.
아이디 생성 시 BcryptPasswordEncoder를 사용하여 비밀번호 암호화 한다.
configure 메서드를 오버라이딩 한다.
.userDetailService를 상속받은 loginService를 파라미터로 넣어주어 암호화된 비밀번호를 찾는 로직을 작성한다. 회원가입 당시 raw 비밀번호가 어떤 암호화된 비밀번호로 저장되었는지 확인한다
(추측)
Bean 으로 등록하여 싱글톤을 유지하면서 한 객체 안에 저장된 암호화된 비밀번호와 Raw 비밀번호를 가지고 있는게 아닌가 추측된다. 이 점은 계속 공부하면서 알아봐야 할 것 같다.
LoginController
public class LoginController {
private final LoginService loginService;
// @GetMapping("/login")
// public String loginForm(@ModelAttribute LoginForm loginForm) {
// return "/login/loginForm";
// }
@GetMapping("/login")
public String loginForm() {
return "/login/SecurityLoginForm";
}
//
// @PostMapping("/login")
// public String login(@Valid @ModelAttribute LoginForm loginForm, BindingResult bindingResult, @RequestParam(defaultValue = "/") String redirectURL, HttpServletRequest request) {
// if (bindingResult.hasErrors()) {
// log.info("errors={}", bindingResult);
// return "/login/loginForm";
// }
// Member loginId = loginService.login(loginForm.getLoginId(), loginForm.getPassword());
// if (loginId == null) {
// bindingResult.reject("loginFail", "아이디 또는 패스워드가 맞지 않습니다.");
// return "/login/loginForm";
// }
// log.info("memberId={}",loginId);
// HttpSession session = request.getSession();
// session.setAttribute(LONGIN_MEMBER, loginId);
// return "redirect:"+redirectURL;
// }
}
//로 주석처리 된 부분의 로직은 이전 MVC패턴을 사용하여 로그인 로직을 작성하였을 때의 코드이다.
스프링 시큐리티가 특정 URL로 로그인 정보를 받아 로그인 처리한다는 것을 깨닫지 못하고 헤메었는데
이 LoginController부분에서 가장 문제가 되었던거 같다.
@PostMapping에 대한 처리를 어떻게 해야하는지 확실하지 않아 많은 애러와 마주했지만 이해하고 나니 생각보다 어렵지 않은 작업이였다.
@GetMapping으로 사용자에게 제공할 페이지를 보여주고 이후 입력받은 값에 대한 Post처리는 SecurityConfig에서 특정 URL로 정보를 보내고 그곳에서 로그인 작업을 수행해준다.
LoginForm.html
<form action="/loginProc" method="post">
<div>
<label for="loginId">로그인 ID</label>
<input type="text" name="loginId" id="loginId" class="form-control">
</div>
<div>
<label for="password">비밀번호</label>
<input type="password" name="password" id="password" class="form-control">
</div>
<div class="from-group form-check">
<label class="form-check-label">
<input name="remember" class="form-check-input" type="checkbox"> Remember me
</label>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-primary btn-lg" type="submit">
로그인
</button>
</div>
<div class="col">
<button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='items.html'"
th:onclick="|location.href='@{/}'|"
type="button">취소
</button>
</div>
</div>
</form>
<from action> 에 어느 URL로 어떤 형식으로 보낼지 지정 해줘야 스프링 시큐리티가 로그인 프로세스를 수행해준다.
다음 블로그 포스팅은 프로잭트를 더 진행해 보고 게시물에 대한 개발을 진행 한 후 포스팅할 예정이다.
'커뮤니티 프로젝트' 카테고리의 다른 글
댓글 기능 개발 (댓글 저장 부분) (0) | 2022.12.02 |
---|---|
회원정보 수정 (0) | 2022.11.30 |
JpaRepository, Querydsl, 게시물 CRUD (1) | 2022.11.24 |
Spring Validator, 추가 Validator (0) | 2022.11.16 |
Thymeleaf (0) | 2022.11.15 |