[Spring Security] 스프링 시큐리티 로그인, 회원가입 예제
스프링 시큐리티로 로그인과 회원가입을 하는 방법에 대해 알아보겠습니다.
먼저 시큐리티 의존성을 추가해줘야 됩니다.
※ bulid.gradle
implementation 'org.springframework.boot:spring-boot-starter-security'
의존성을 추가해준 뒤 실행시켜보면
이런 화면이 나옵니다. 이거는 시큐리티에서 제공하는 로그인 페이지입니다.
※ 회원가입과 로그인을 하기 위한 User 엔티티
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.sql.Timestamp;
@Entity
@Data
public class User {
@Id
@GeneratedValue
private int id; // 기본키
private String username; // 사용자 아이디
private String password; // 사용자 비밀번호
private String email; // 사용자 이메일
private String role; // 사용자 권한
@CreationTimestamp
private Timestamp createDate; // 회원가입한 날짜
}
※ Controller, Repository
public interface UserRepository extends JpaRepository<User, Integer> {
}
@Controller
@RequiredArgsConstructor
public class UserController {
private final UserRepository userRepository;
@GetMapping({"", "/"})
public String index() {
return "index";
}
@GetMapping("/user")
public @ResponseBody String user() {
return "user";
}
@GetMapping("/admin")
public @ResponseBody String admin() {
return "admin";
}
@GetMapping("/manager")
public @ResponseBody String manager() {
return "manager";
}
@GetMapping("/login")
public String loginForm() {
return "login";
}
@GetMapping("/joinForm")
public String joinForm() {
return "joinForm";
}
@PostMapping("/join")
public String join(User user) {
return "redirect:/login";
}
}
▲ 참고 : UserRepository에 JpaRepository를 상속받아서 @Repository는 생략해도 스프링 빈으로 등록이 됩니다.
※ 시큐리티 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/user/**").authenticated()
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/loginForm")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/");
}
}
- @Configuration : 시큐리티에 대한 설정을 우선 스프링 빈으로 등록합니다.
- @EnableWebSecurity : 스프링 시큐리티 필터가 스프링 필터 체인에 등록됩니다.
- WebSecurityConfigurerAdapter을 상속받은 뒤 configure 메서드를 재정의 합니다.
- http.authorizeRequests() : 인가 요청을 받습니다.
- antMatchers("url주소"). authenticated() : url 주소를 입력했을 때 인증된 사용자만 url 주소를 이용할 수 있습니다.
- access("권한") : 사용자의 권한이 access에 정해놓은 권한이 있다면 해당 url주소를 이용할 수 있습니다.
- anyRequest.permitAll() : 위에 설정해놓은 url주소 말고 다른 url 주소들은 아무 조건 없이 url에 접속하는 것을 허용합니다.
- formLogin() : 폼 로그인을 이용할 수 있습니다.
- loginPage("로그인 페이지 경로") : 만든 로그인 페이지 경로
- loginProcessingUrl("시큐리티 로그인 경로") : 시큐리티 로그인 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행합니다.
- defaultSuccessUrl("로그인성공 시 디폴트 경로") : 로그인 성공시 디폴트 화면으로 이동을 하고 만약 user url로 접속을 시도했으면 로그인 성공 후 user url로 이동됩니다.
- BCryptPasswordEncoder : 비밀번호를 암호화합니다. controller에 빈으로 등록된 BCryptPasswordEncoder를 자동 주입을 해줍니다.
※ 사용자 인증
public class PrincipalDetails implements UserDetails {
private User user;
public PrincipalDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collection;
}
@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;
}
}
- 시큐리티가 /login 주소 요청이 오면 낚아채서 로그인을 진행하는데 로그인 진행이 완료되면 시큐리티 세션을 만들어줍니다. 세션의 키 값은 Security ContextHolder이고 값은 Authentication 타입의 객체입니다. Authentication 타입 객체에는 User 정보가 담겨 있어야 하는데 그 User정보는 UserDetails 타입 객체에 담겨 있습니다.
- UserDetails : 시큐리티에서 제공하는 인터페이스입니다.
- getAuthorities() : User의 권한을 리턴을 합니다.
- getPassword() : User의 비밀번호를 리턴을 합니다.
- getUsername() : User의 아이디를 리턴을 합니다.
- isAccountNonExpired() : 계정이 만료되었는지 여부를 리턴합니다.
- isAccountNonLocked() : 계정이 잠겼는지 여부를 리턴합니다.
- isCredentialsNonExpired() : 계정의 비밀번호가 오래 사용했는지에 대한 여부를 리턴합니다.
- isEnabled() : 계정의 활성화 여부를 리턴합니다.
@Service
public class PrincipallDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if(user != null) {
return new PrincipalDetails(user);
}
return null;
}
}
- 시큐리티 설정에서 loginProcessingUrl("/login")으로 요청이 오면 자동으로 UserDetailsService 타입으로 Ioc가 되어있는 loadUserByUsername함수가 실행이 됩니다.
- loadUserByUsername의 함수에서 매개 변수인 username은 html일 경우 name ="username"과 동일해야 됩니다.
비밀번호 암호화
// Controller 코드
@PostMapping("/join")
public String join(User user) {
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
user.setPassword(encPassword);
userRepository.save(user);
}
}
- 비밀번호를 1234로 하였을 경우 알 수 없는 문자들로 데이터베이스에 저장이 됩니다.
결과
사용자 등급이 user일 경우 /user/**에 대한 url에 접근이 가능하고 manager일 경우 /manager/**, /admin/**, /user/**에 접근이 가능하고 admin일 경우 /user/**, /admin/**에 접근이 가능합니다. 만약 인증이 안된 사용자는 방금 설명드린 url를 제외한 부분에만 접근이 가능합니다.