Diary/Today I Learned
21.10.02 TIL
이정인
2021. 10. 2. 22:23
나의 항해 블로그에 계정 회원가입/로그인/로그아웃 기능을 추가하려고 한다.
JWT를 사용해서 인증하는 것을 해보고싶긴하지만, 일단 세션 방식의 인증과 시간이 남는다면 소셜 로그인 기능까지만 추가할 예정이다.
회원 가입
계정 클래스 만들기
@Entity
@Getter
@NoArgsConstructor
public class Account extends Timestamped{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String role = "ROLE_USER";
private Account(AccountRequestDto requestDto) {
this.username = requestDto.getUsername();
this.password = requestDto.getPassword();
}
public static Account from(AccountRequestDto requestDto) {
return new Account(requestDto);
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", username='" + username + '\'' +
'}';
}
}
SecurityConfig를 수정했다.
/account/** 는 CSRF 방어를 하지 않는다.
인증하지 않고도 로그인/회원가입 페이지는 확인 할 수 있도록 했다.
인증하지 않으면 /articles/** 는 GET 만 허용한다.
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/images/**", "/css/**", "/account/**").permitAll() .antMatchers(HttpMethod.GET, "/articles/**").permitAll() .anyRequest().authenticated() .and() .csrf().ignoringAntMatchers("/account/**") .and() .formLogin() .loginPage("/account/login") .loginProcessingUrl("/account/login") .defaultSuccessUrl("/", true) .failureUrl("/account/login?error") .permitAll() .and() .logout().logoutUrl("/account/logout") .and() .exceptionHandling() .accessDeniedPage("/forbidden.html"); }
}
회원가입 컨트롤러
@Controller
@RequestMapping("/account")
@RequiredArgsConstructor
public class AccountController {
private final AccountService accountService;
@GetMapping("/register")
public String registerPage() {
return "register";
}
@PostMapping("/register")
public String register(@RequestBody AccountRequestDto requestDto) {
accountService.registerAccount(requestDto);
return "redirect:/login";
}
@GetMapping("/login")
public String loginPage() {
return "login";
}
}
암호화한 패스워드로 저장하기.
@Service
@RequiredArgsConstructor
public class AccountService {
private final AccountRepository accountRepository;
private final PasswordEncoder passwordEncoder;
public void registerAccount(AccountRequestDto requestDto) {
if ( accountRepository.findByUsername(requestDto.getUsername()).isPresent() ) {
throw new UsernameExistException("중복된 닉네임입니다.");
}
String password = requestDto.getPassword();
String encodedPassword = passwordEncoder.encode(password);
requestDto.setPassword(encodedPassword);
accountRepository.save(Account.from(requestDto));
}
}
메인 페이지에 유저 정보를 모델로 담아서 보내준다.
@GetMapping("/")
public String home(@AuthenticationPrincipal UserDetails userDetails, Model model) {
if ( userDetails != null ) {
String username = userDetails.getUsername();
model.addAttribute("username", username);
}
return "index";
}
로그인
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final AccountRepository accountRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountRepository.findByUsername(username).orElseThrow(
() -> new UsernameNotFoundException("유저가 존재하지 않습니다.")
);
return UserDetailsImpl.of(account);
}
}
public class UserDetailsImpl implements UserDetails {
private final Account account;
private UserDetailsImpl(Account account) {
this.account = account;
}
public static UserDetailsImpl of(Account account) {
return new UserDetailsImpl(account);
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return account.getPassword();
}
@Override
public String getUsername() {
return account.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
앞으로의 과제
- 소셜 로그인 기능
- 3주차 강의 듣기
- 테스트 코드 작성 및 리팩터링
- 4,5 주차 강의 듣기
- 나머지 과제 진행하기