코딩하는 털보

21.10.02 TIL 본문

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;
    }
}

앞으로의 과제

  1. 소셜 로그인 기능
  2. 3주차 강의 듣기
  3. 테스트 코드 작성 및 리팩터링
  4. 4,5 주차 강의 듣기
  5. 나머지 과제 진행하기
Comments