각진 세상에 둥근 춤을 추자
[CRUD] 3. 로그인 기능 구현 - 스프링 시큐리티 본문
스프링 시큐리티와 <Form> 전송을 통한 로그인을 진행한다.
1. Spring Security dependency 추가
build.gradle에 security와 관련된 의존성을 추가한다.
implementation 'org.springframework.boot:spring-boot-starter-security'
프로그램을 구동시키면 스프링 시큐리티에서 제공하는 로그인 페이지가 뜬다.
아래와 같이 콘솔창을 통해 비밀번호를 입력한다.
- Username: user
- Password: 콘솔창 확인
또는 application.properties에 기본 스프링 시큐리티 Username과 Password를 지정한다.
#############################################
#스프링 시큐리티 임시 아이디/패스워드 설정
#############################################
spring.security.user.name=user
spring.security.user.password=1234
2. Security Config 작성하기
package kr.co.crud.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
/* SecurityConfig.java -> Security Filter Chain
* (1) HTTP 요청에 대한 보안 작업
* - UsernamePasswordAuthenticationFilter : 사용자가 입력한 인증 정보로 Authentication 객체 생성
* - http.formLogin() 등 메서드를 통해 설정
* (2) 사용자 인증(로그인 설정), 인가(접근 권한 설정), 로그아웃 설정
* */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
/* 인가(접근권한) 설정 */
http.authorizeHttpRequests().antMatchers("/", "/user/login", "/user/register").permitAll(); // 경로에 대해서 모든 사용자에게 접근을 허용한다는 의미
http.authorizeHttpRequests().antMatchers("/board/**").hasAnyRole("1", "2");
/* 사이트 위변조 요청 방지
* CRSF(Cross-Site Request Forgery) 공격 방지 기능을 비활성화
* disable -> CRSF 토큰을 사용하지 않도록 설정
* */
//http.csrf().disable();
/* 로그인 설정 */
http.formLogin()
.loginPage("/user/login") // 로그인 페이지 경로 설정 (해당 경로를 통해 로그인 진행)
.defaultSuccessUrl("/") // 로그인 성공 시 이동 경로
.failureUrl("/user/login?success=100") // 로그인 실패 시 이동 경로
.usernameParameter("uid") // 로그인 폼에서 사용자 아이디를 입력받는 Input 필드 이름 지정 (name="uid")
.passwordParameter("pass"); // 로그인 폼에서 사용자 비밀번호를 입력받는 Input 필드 이름 지정 (name="pass")
/* 로그아웃 설정 */
http.logout()
.invalidateHttpSession(true) // 로그아웃 시 세션 무효화
.logoutRequestMatcher(new AntPathRequestMatcher("/user/logout")) // 로그아웃 요청 URL (POST 요청 URL)
.logoutSuccessUrl("/user/login?success=200"); // 로그아웃 성공 시 이동 경로
/* 로그인 시 세션 유지 */
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
//return http.build(); // build: SecurityConfigurerAdapter의 메서드 중 하나로, HttpSecurity 객체를 반환
}
/* 비밀번호 암호화
* BCrypt 해시 함수를 사용하여 비밀번호를 암호화
* */
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3. MyUserDetails
package kr.co.crud.security;
import kr.co.crud.entity.UserEntity;
import lombok.Builder;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/*
* AuthenticationManager => UserDetails
* - 생성된 Authentication 객체를 검증 및 인증을 수행
* */
@Data
@Builder
public class MyUserDetails implements UserDetails {
/*
* 직렬화(Serializable)
* - 자바의 객체를 바이트의 배열로 변환하여 DB에 저장
* */
private static final long serialVersionUID = 1L;
@Autowired
private UserEntity user;
/*
* getAuthorities()
* - 사용자가 가지고 있는 권한을 반환하는 메서드
* - 반환타입: Collection<? extends GrantedAuthority>
* */
// <? extends GrantedAuthority>: 와일드카드('?')를 사용하여 GrantedAuthority가 확장한 타입이 될 수 있음을 명시
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// GrantedAuthority 객체들을 저장하기 위한 리스트 생성
List<GrantedAuthority> authorities = new ArrayList<>();
// 사용자의 권한을 나타내는 SimpleGrantedAuthority 객체를 생성하여 리스트에 추가
// 일반적으로 Spring Security의 규약에 따라 "ROLE_" 접두어를 붙여 권한 생성
authorities.add(new SimpleGrantedAuthority("ROLE_"+user.getGrade()));
return authorities;
}
/*
* getUsername()
* - 사용자의 아이디를 반환하는 메서드
* */
@Override
public String getUsername() {
return user.getUid();
}
/*
* getPassword()
* - 사용자의 비밀번호를 반환하는 메서드
* */
@Override
public String getPassword() {
return user.getPass();
}
public String getNickname() {
return user.getName();
}
/* 그외 */
// 계정 만료 여부 (true: 만료X, false: 만료)
@Override
public boolean isAccountNonExpired() {
return true;
}
// 계정 잠김 여부 (true: 잠김X, false: 잠김)
@Override
public boolean isAccountNonLocked() {
return true;
}
// 계정 비밀번호 만료 여부 (true: 만료X, false: 만료)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정 활성화 여부 (true: 활성화, false: 비활성화)
@Override
public boolean isEnabled() {
return true;
}
}
4. SecurityUserService
package kr.co.crud.security;
import kr.co.crud.entity.UserEntity;
import kr.co.crud.repository.UserRepo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/*
* UserDetailsService
* - UserDetails 객체를 사용해 인증 및 권한 부여
* */
@Service
@Slf4j
public class SecurityUserService implements UserDetailsService {
// 레포지토리를 통해 데이터베이스에서 사용자 정보를 조회
@Autowired
private UserRepo repo;
// loadUserByUsername: 사용자의 아이디(username)을 기반으로 사용자 정보를 가져온다
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.warn("SecurityUserService loadUserByUsername");
// 데이터베이스에서 사용자 정보를 조회
UserEntity user = repo.findById(username).get();
if(user == null) {
throw new UsernameNotFoundException(username);
}
// 조회한 사용자 정보로 UserDetails 객체 생성
UserDetails myUser = MyUserDetails.builder()
.user(user)
.build();
return myUser;
}
}
5. 로그인 HTML
앞서 SecurityConfig에서 작성했던 설정을 토대로 로그인 화면을 구현한다.
<form class="loginForm" th:action="@{/user/login}" method="post">
<div>
<input class="idForm" type="text" name="uid" placeholder="아이디">
</div>
<div>
<input class="passForm" type="password" name="pass" placeholder="비밀번호">
</div>
<button class="btn btnLogin" type="submit">로그인</button>
</form>
'프로젝트 > CRUD' 카테고리의 다른 글
[CRUD] 5. 게시글 작성 기능 구현 - 단일 파일 업로드 및 다운로드 (1) | 2024.01.10 |
---|---|
[CRUD] 4. 게시글 작성 기능 구현 - 네이버 스마트에디터 (0) | 2024.01.10 |
[CRUD] 2. 회원가입 기능 구현 - Daum 우편번호 API (0) | 2024.01.10 |
[CRUD] 1. 페이지 화면 구현 (0) | 2023.12.30 |