2022년 4월 13일(수)
[스파르타코딩클럽] Spring 심화반 - 2주차 - 1
기본적으로 HTTP는 상태를 저장하지 않는다.
-> 따라서 HTTP 한 클라이언트에서 서버에게 요청을 여러번 보낸다고 해도, 서버에서는 같은 클라이언트에서 보낸 요청인지 알 수 없다.
쿠키 : 클라이언트에 저장될 목적으로 생성한 작은 정보를 담은 파일
세션 : 서버에서 일정시간 동안 클라이언트 상태를 유지하기 위해 사용
'Spring Security': 스프링 서버에 필요한 인증 및 인가를 위해 많은 기능을 제공해 줌으로써 개발의 수고를 덜어준다.
'스프링 시큐리티' 프레임워크 추가하는 법
// build.gradle dependencies 에 추가하고 실행시켜 줄것
// 스프링 시큐리티
implementation 'org.springframework.boot:spring-boot-starter-security'
// com.sparta.security WebSecurityConfig.java
@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 어떤 요청이든 '인증'
.anyRequest().authenticated()
.and()
// 로그인 기능 허용
.formLogin()
.defaultSuccessUrl("/")
.permitAll()
.and()
// 로그아웃 기능 허용
.logout()
.permitAll();
}
}
// WebSecurityConfig.js
// image 폴더를 login 없이 허용
.antMatchers("/images/**").permitAll()
// css 폴더를 login 없이 허용
.antMatchers("/css/**").permitAll()
회원 테이블 설계 (id, username, password, email, role)
회원 ID는 username으로 설정할 것 (스프링 시큐어리티 기본값임)
role은 특정 값만 가지면 되므로 Enum을 선언해서 사용
// model > User 일부
...
@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
private UserRoleEnum role;
public User(String username, String password, String email, UserRoleEnum role) {
this.username = username;
this.password = password;
this.email = email;
this.role = role;
// model > UserRoleEnum
public enum UserRoleEnum {
USER, // 사용자 권한
ADMIN // 관리자 권한
}
회원 가입 UI 반영
// build.gradle dependencies 에 추가하고 실행시켜 줄것
// Thymeleaf (뷰 템플릿 엔진)
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
// WebSecurityConfig.js
.and()
// 로그인 기능 허용
.formLogin()
.loginPage("/user/login")
.defaultSuccessUrl("/")
.failureUrl("/user/login?error")
.permitAll()
현 상태의 문제점: 패스워드가 평문(=있는 그대로)으로 저장되어 있음
->'정보통신망법, 개인정보보호법' 에 의해 비밀번호는 암호화(Encryption)가 의무이다. // 해커나 개발자의 악용 소지가 있기 때문이다.
-> 암호화 후 패스워드 저장이 필요 / 평문 → (암호화 알고리즘) → 암호문
암호화 알고리즘 사용용도
패스워드 암호화 적용 (스프링 시큐어리티에서 'BCrypt 해시함수' 사용하기를 권고)
// security > WebSecurityConfig 에 추가
@Bean
public BCryptPasswordEncoder encodePassword() {
return new BCryptPasswordEncoder();
}
// service > UserService.js 일부
public class UserService {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
...
public void registerUser(SignupRequestDto requestDto) {
...
// 패스워드 암호화
String password = passwordEncoder.encode(requestDto.getPassword());
...
User user = new User(username, password, email, role);
userRepository.save(user);
}
}
스프링 시큐리티 사용시 구조
로그인 처리 과정
로그아웃 처리 과정
로그인, 로그아웃 구현
// security > WebSecurityConfig.js
// [로그인 기능]
.formLogin()
// 로그인 View 제공 (GET /user/login)
.loginPage("/user/login")
// 로그인 처리 (POST /user/login) *추가*
.loginProcessingUrl("/user/login")
// 로그인 처리 후 성공 시 URL *추가*
.defaultSuccessUrl("/")
// 로그인 처리 후 실패 시 URL *추가*
.failureUrl("/user/login?error")
.permitAll()
.and()
// [로그아웃 기능]
.logout()
// 로그아웃 처리 URL *추가*
.logoutUrl("/user/logout")
.permitAll();
DB 의 회원 정보 조회 → 스프링 시큐리티의 "인증 관리자" 에게 전달
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
@Autowired
public UserDetailsServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Can't find " + username));
return new UserDetailsImpl(user);
}
}
public class UserDetailsImpl implements UserDetails {
private final User user;
public UserDetailsImpl(User user) {
this.user = user;
}
public User getUser() {
return user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.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;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptyList();
}
}
Controller에서 로그인한 유저 정보 사용하는 법
// controller > HomeController.js
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model, @AuthenticationPrincipal UserDetailsImpl userDetails) {
model.addAttribute("username", userDetails.getUsername()); // 타임리프를 통해 정보 넘겨줌
return "index"; // html 건네줌
}
}