스프링 시큐리티 ver.
로그인/로그아웃, 회원 가입
📗 스프링 시큐리티⭐
⭐ 인증과 인가
- 인증 (Authentication) : 등록된 사용자의 신원을 입증하는 과정 (예 : 로그인 시 누구인지 확인하는 과정)
- 인가 (Authorization) : 특정 부분에 접근할 수 있는지 권한을 확인하는 작업 (예 : 관리자 페이지)
스프링 시큐리티 (P.201,202)
스프링 기반 애플리케이션 보안(인증, 인가,권한)을 담당하는 스프링 하위 프레임워크
- 역할 : 보안 관련 옵션 제공 (CSRF 공격, 세션 고정 공격 방어)
* CSRF 공격 : 사용자 권한을 갖고 특정 동작을 수행하도록 유도하는 공격
* 세션 고정 공격 : 사용자의 인증 정보를 탈취하거나 변조하는 공격
- 필터 기반 동작
UsernamePasswordAuthentication
: ID, PW 넘어오면 인증 요청을 위임하는 인증 관리자FilterSecurityInterceptor
: 권한 부여 처리를 위임해 접근 제어 결정을 쉽게하는 접근 결정 관리자
- 세션&쿠키 방식으로 인증 처리 (세션 기반 인증)
UserDetails
: 스프링 시큐리티에서 인증,인가 정보를 담은 인터페이스. 이 클래스를 상속받은 뒤 메서드를 오버라이드해 사용하면 된다.UserDetailService
: 스프링 시큐리티에서 사용자 정보 가져올 때 사용하는 클래스. 이 클래스를 상속받은 뒤loadUserByUsername()
을 오버라이드해 사용
📗 회원 도메인 만들기
1. 의존성 추가하기 (build.gradle)
dependencies에 스프링 시큐리티용 의존성 추가하기
implementation 'org.springframework.boot:spring-boot-starter-security' // 1) 스프링 시큐리티 사용하기 위한 스타터 추가
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' // 2) 타임리프에서 스프링 시큐리티 사용하기 위한 의존성 추가
testImplementation 'org.springframework.security:spring-security-test' // 3) 스프링 시큐리티 테스트 위한 의존성 추가
2. 엔티티 추가하기 (domain 패키지 - User.java)
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
@Table(name = "users")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class User implements UserDetails { // UserDetails 상속받아 인증 객체로 사용
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키
@Column(name = "id", updatable = false)
private Long id;
@Column(name = "email", nullable = false, unique = true)
private String email;
@Column(name = "password")
private String password;
@Builder
public User(String email, String password, String auth) {
this.email = email;
this.password = password;
}
@Override // 권한 반환
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("user"));
}
// 사용자 id 반환 (고유값)
@Override
public String getUsername() {
return email;
}
// 사용자 pwd 반환
@Override
public String getPassword() {
return password;
}
// 계정 만료 여부 반환
@Override
public boolean isAccountNonExpired() {
return true; // true -> 아직 만료 X
}
// 계정 잠금 여부 반환
@Override
public boolean isAccountNonLocked() {
return true; // true -> 아직 잠금 X
}
// 패스워드 만료 여부 반환
@Override
public boolean isCredentialsNonExpired() {
return true; // true -> 만료 X
}
// 계정 사용 가능 여부 반환
@Override
public boolean isEnabled() {
return true; // true -> 사용 가능
}
}
➜ UserDetails 클래스 : 스프링 시큐리티에서 사용자의 인증 정보 담아두는 인터페이스
3. 리포지토리 만들기 (UserRepository.java)
public interface UserRepository extends JpaRepository<User, Long>{
Optional<User> findByEmail(String email); // 1) email로 사용자 정보 가져옴
}
▶ 자주 사용하는 쿼리 메서드
- findByName()
: "name" 컬럼 값 中 파라미터로 들어오는 값과 같은 데이터 반환
- findByNameAndAge()
- findByNameOrAge()
- findByAgeLessThan()
- findByAgeGreaterThan()
- findByName(Is)Null()
4. 서비스 메서드 코드 만들기 (service - UserDetailService.java)
@RequiredArgsConstructor
@Service
// 스프링 시큐리티에서 사용자 정보 가져오는 인터페이스
public class UserDetailService implements UserDetailsService {
private final UserRepository userRepository;
// 사용자 이름(email)으로 사용자 정보 가져오는 메소드
@Override
public User loadUserByUsername(String email) {
return userRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException((email)));
}
}
📗 시큐리티 설정하기
config - WebSecurityConfig.java : 실제 인증 처리하는 시큐리티 설정 파일
@RequiredArgsConstructor
@Configuration
public class WebSecurityConfig {
private final UserDetailService userService;
// 1) 스프링 시큐리티의 모든 기능 비활성화
@Bean
public WebSecurityCustomizer configure() {
return (web) -> web.ignoring()
.requestMatchers(toH2Console())
.requestMatchers("/static/**");
}
// 2) 특정 HTTP 요청에 대한 웹 기반 보안 구성
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests() // 3) 인증, 인가 설정
.requestMatchers("/login", "/signup", "/user").permitAll()
.anyRequest().authenticated()
.and()
.formLogin() // 4) 폼 기반 로그인 설정
.loginPage("/login")
.defaultSuccessUrl("/articles")
.and()
.logout() // 5) 로그아웃 설정
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.and()
.csrf().disable() // 6) CSRF 비활성화
.build();
}
// 7) 인증 관리자 관련 설정
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http,
BCryptPasswordEncoder bCryptPasswordEncoder,
UserDetailService userDetailService) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userService) // 8) 사용자 정보 서비스 설정
.passwordEncoder(bCryptPasswordEncoder)
.and()
.build();
}
// 9) 패스워드 인코더로 사용할 Bean 등록
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
3) 특정 경로에 대한 액세스 설정
➜ requestMatchers() : 특정 요청과 일치하는 url에 대한 액세스 설정
➜ permitAll() : 인증/인가 없이 누구나 접근 가능하게 함
➜ anyRequest() : 위에서 설정한 url 이외의 요청에 대해 설정
➜ authenticated() : 인가는 필요 없지만 인증이 성공된 상태여야 접근 가능
4) 폼 기반 로그인 설정
➜ loginPage() : 로그인 페이지 경로 설정
➜ defaultSuccessUrl() : 로그인 완료 시 이동할 경로 설정
5) 로그아웃 설정
➜ logoutSuccessUrl() : 로그아웃 완료 시 이동할 경로 설정
➜ invalidateHttpSession() : 로그아웃 이후 세션을 전체 삭제할지 여부 설정
📗 회원 가입 구현하기
1. 서비스 메서드 코드 만들기
1) 사용자 정보 담는 객체 작성하기 (dto - AddUserRequest.java)
@Getter
@Setter
public class AddUserRequest {
private String email;
private String password;
}
2) AddUserRequest 객체를 인수로 받는 회원 정보 추가 메서드 작성 (service - UserService.java)
- 패스워드 저장 시 시큐리티 설정하며 패스워드 인코딩용으로 등록한 빈을 사용해 암호화한 후 저장
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public Long save(AddUserRequest dto) {
return userRepository.save(User.builder()
.email(dto.getEmail())
// 1) 패스워드 암호화
.password(bCryptPasswordEncoder.encode(dto.getPassword()))
.build()).getId();
}
}
2. 컨트롤러 작성하기 (UserApiController.java)
- signup() : 회원가입 폼에서 회원가입 요청 받으면 서비스 메서드 사용해 사용자 저장 후, 로그인 페이지로 이동
@RequiredArgsConstructor
@Controller
public class UserApiController {
private final UserService userService;
@PostMapping("/user")
public String signup(AddUserRequest request) {
userService.save(request); // 회원 가입 메서드 호출
return "redirect:/login"; // 회원 가입이 완료된 이후 로그인 페이지로 이동
}
}
📗 회원 가입, 로그인 뷰 작성하기
1. 뷰 컨트롤러 구현하기 (UserViewController.java)
: 로그인, 회원가입 경로로 접근하면 뷰 파일(화면)을 연결하는 컨트롤러
@Controller
public class UserViewController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/signup")
public String signup() {
return "signup";
}
}
2. 뷰 작성하기
📗 로그아웃 구현하기
1. 로그아웃 메서드 작성하기 (UserApiController.java)
- SecurityContextHolder
: 스프링 시큐리티에서 인증이 완료된 후 Authentication 객체 저장하는 곳
// 로그아웃
@GetMapping("/logout")
public String logout(HttpServletRequest request, HttpServletResponse response) {
new SecurityContextLogoutHandler().logout(request, response,
SecurityContextHolder.getContext().getAuthentication());
return "redirect:/login";
}
2. 로그아웃 뷰 추가하기 (articleList.html)
<a th:href="@{/articles/{id}(id=${item.id})}" class="btn btn-primary">보러 가기</a>
</div>
</div>
<br>
</div>
<button type="button" class="btn btn-secondary" onclick="location.href='/logout'">로그아웃</button>
</div>
<script src="/js/article.js"></script>
</body>
</html>
📗 실행 테스트하기
1. 테스트 위한 환경변수 추가하기 (application.yml)
spring:
jpa:
# 전송 쿼리 확인
show-sql: true
properties:
hibernate:
format_sql: true
# 테이블 생성 후, data.sql 실행
defer-datasource-initialization: true
datasource: # DB 정보 추가
url: jdbc:h2:mem:testdb
username: sa
h2: # H2 서버 활성화
console:
enabled: true
2. 로그인, 회원 가입 실행 테스트하기
1) http://localhost:8080/articles
: 인증된 사용자만 접근 가능하므로 로그인 페이지인 /login으로 리다이렉트 된다.
2) 회원가입하기 (localhost:8080/signup) → 로그인하기
: ID, PWD 입력하고 [Submit] 눌러 회원가입하고 로그인 페이지에서 만든 계정으로 로그인 성공 시 글 목록 페이지로 이동
3) 회원가입한 데이터가 DB에 있는지 확인하기 (http://localhost:8080/h2-console)
3. 로그아웃 실행 테스트하기
/logout으로 이동하면 이렇게 변한다.
(해당 글 내용은 📗 스프링 부트 3 백엔드 개발자 되기 - 자바 편을 읽고 정리한 내용입니다.)
'📚 관련 독서 > 스프링 부트 3 백엔드 개발자 되기 - 자바 편' 카테고리의 다른 글
[SpringBoot] 10장 OAuth2 ver. 로그인/로그아웃 구현 (0) | 2024.02.21 |
---|---|
[SpringBoot] 09장 JWT ver. 로그인/로그아웃 구현 (0) | 2024.02.13 |
[SpringBoot] 07장 블로그 화면 구성하기 (타임리프) (0) | 2024.02.08 |
[SpringBoot] 06장 블로그 기획하고 API 만들기 (0) | 2023.08.17 |
[SpringBoot] 05장 데이터베이스 조작이 편해지는 ORM (0) | 2023.08.16 |