스프링시큐리티의 아주 기본적인 구현부터 단계적으로 진행할것이다.
스프링 시큐리티 버전이 올라가면서 WebSecurityConfigurerAdapter 를 상속받아서 쓰던 방식이
없어지고 메서드체인을 직접 빈을 주입하는 방식으로 바뀌었다.
bulid.gradle
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
일단 필요한 두가지 기본적인 디펜던시를 추가한다.
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<title>Login</title>
<style>
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.login-box {
border: 1px solid #ccc;
padding: 20px;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<div class="login-box">
<h2>Login</h2>
<div sec:authorize="isAnonymous()">
<form th:action="@{/api/login}" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" required/>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required/>
</div>
<div>
<input type="submit" value="Login"/>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
타임리프로 구현한 html이다.
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
위 선언을 통해 sec변수를 붙이면 springsecurity의 기능들을 편리하게 쓸수 있다. 이 페이지에서는 딱히 사용하는것은 없지만 예를들어.
이 코드는 모두에게 보여주겠다는 의미이다. 그외에 많은 함수들이 있다.
스프링시큐리티는 기본적으로 username 과 password라는 변수네이밍을 사용하기때문에 이 네이밍을 그대로 쓰는게 좋다.
로그인을 누르게 되면 /api/login 으로 데이터가 전송된다.
HomeController
@Controller
public class HomeController {
@RequestMapping("/")
public String home() {
return "hello";
}
@RequestMapping("/main")
public String main() {
return "main";
}
@RequestMapping("/test")
public String test() {
return "test";
}
@RequestMapping("/login")
public String login() {
return "login";
}
}
별건없다.
스프링 시큐리티를 위해 전달하는 api들은 스프링콘피그에서 인터셉터할 예정이다.
SecurityConfig
@EnableWebSecurity
/*@AllArgsConstructor*/
@RequiredArgsConstructor
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authorize -> {
try{
authorize
.antMatchers("/main")
.permitAll() // "/main"은 권한 필요없이 접근가능
.and()
.formLogin()
.loginPage("/login") //로그인페이지
.loginProcessingUrl("/api/login") //이 url을 던지면 spring security에서 인터셉터해온다
.defaultSuccessUrl("/main",true) //로그인이 성공하면 이 주소로 이동하게 된다.
;
}catch (Exception e) {
throw new RuntimeException(e);
}
});
return http.build();
}
}
스프링시큐리티 설정들은 이 페이지에서 진행한다.
5.7 이후의 버전임으로 WebSecurityConfigurerAdapter 를 상속받지 않는다.
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
필터체인을 사용한다,
타임리프 html에서 보냈던 “/api/login” 은 .loginProcessingUrl("/api/login")
를 통해 스프링시큐리티가 처리할 예정
이 처리는 service에서 다시 진행한다.
MemberService
@Service
/*@AllArgsConstructor //모든필드의 생성자를 자동으로 추가해준다.*/
public class MemberService implements UserDetailsService {
@Autowired
private LoginRepository loginRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Member member = loginRepository.findByMemberId(username).orElseThrow(() -> new UsernameNotFoundException("User not found with id : " + username));
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ADMIN"));
return new User(member.getEmail(), member.getPw(), authorities);
}
}
UserDetailsService 를 상속받은 memberService . UserDetailService는 스프링시큐리티 내부 서비스이다. 이 서비스를 상속받으면 시큐리티에서 자동으로 추적이 들어간다.
loadUserByUsername
이 메서드를 오버라이드 해서 할 일은 클라이언트로부터 받은 username정보로 데이터베이스를 조회해 사용자 인증을 수행하는것이다.
말만들으면 복잡하지만 사용자 인증은 스프링시큐리티에서 자동적으로 처리해주니 username으로 디비를 가지고와 User (얘도 스프링시큐리티 내부의 서비스이다.) 에 id 그리고 password 그리고 권한을 담아서 넘기면 된다.
중요한점은 반드시 password를 인코딩해야한다는 점이다.