코드는 Github을 통해 확인할 수 있습니다.
과거에 사용했었지만 현재 Deprecate된 것은 다시 새롭게 구현했습니다.
일부 코드에 대해 좀 더 효율적인 방법으로 구현해놨습니다.
목차
- 페이지 별로 권한 주기 - 로그인을 구현하기 전 페이지 접근 시 로그인 권한이 필요한 페이지를 만듭니다.
- 회원가입과 비밀번호 암호화 설정 - Spring Security를 이용해 암호화 설정이 가능합니다.
- 로그인 기능 위임 및 Redirect 처리 - 로그인 처리를 Spring Security에게 맡기는 것을 배웁니다. 로그인이 되고나면 사용자가 접속 하려던 사이트로 이동하게 하는 Redirect 기능도 Spring Security가 대신 처리 해줍니다.
환경 구축
start.spring.io를 통해 위와 같이 설정하고 진행했습니다.
화면은 Thymeleaf, DB는 JPA를 이용합니다.
MariaDB가 없다면 아래에서 설치해주세요. 저는 10.9.3을 설치한 상태입니다.
create database security DEFAULT CHARSET UTF8; // security 데이터베이스 생성
//UTF8 설정 안하면 한글 Insert 시에 Data truncation: Incorrect string value 에러 발생.
Github에서 내려받은 프로젝트는 Propeties에 MariaDB의 데이터베이스와 계정 정보가 위와 같이 설정되어 있으니 DB 생성시 참고 바랍니다.
시작 전에 알아야 할 것
스프링 시큐리티는 인증(Authentication)과 인가(Authorization)라는 말을 알고가야 이해가 쉽습니다.
Authentication은 말 그대로 사용자가 인증하는 과정을 뜻하며
Authorization은 서버가 사용자에 대한 권한을 부여하는 것을 말합니다.
github에서 내려받은 프로젝트가 아니라 start.spring.io로 아무 것도 없는 상태에서 시작한다면 어떤 페이지로 이동하든 아래와 같이 Spring Security가 만든 기본 화면으로 강제 이동합니다.
Username에는 "user", Password에는 서버 Console 창에 찍힌 Password 를 입력하면 로그인이 되며 그제서야 원하는 페이지로 이동이 가능합니다.
그리고 로그아웃을 하려면 링크에 /logout을 전달해서 처리합니다.
위의 기능은 Spring Security가 제공하는 예시일뿐이며 이용하지 않는 기능이니 바로 기능 구현으로 넘어가겠습니다.
페이지 별로 권한 주기
Spring Security의 로그인 기능 구현 중 첫 목표는 페이지 별로 권한 주기입니다.
어떠한 페이지를 들어갔을 때 특정 권한을 가진 사람만 인가시켜줄 수 있는 기능을 만드는 것이 목표입니다.
아래는 Github에서 내려받았을 때 초기 메인화면 입니다.
페이지 별로 권한을 주기 위해 "user 페이지 이동 버튼"을 누르면 로그인을 하지 않은 상태이므로 해당 페이지에 권한이 필요 합니다. 권한이 없으면 /login 로그인 화면으로 이동하는 기능을 만들어 보겠습니다.
Spring Security 이전 버젼에서는 아래와 같이 WebSecurityConfigurerAdapter을 상속 받은 WebSecurityConfig 클래스를 만들었어야 했습니다.
@Configuration
@EnableWebSecurity // Spring Security 설정을 시작
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
하지만 WebSecurityConfigurerAdapter는 Deprecated 되었으므로 사용하지 않고 아래와 같이 SecurityFilterChain을 Bean으로 등록하여 사용합니다.
Bean으로 사용하기 위해 Class에 @Configuration 어노테이션을 추가해줍니다.
WebSecurityConfigurerAdapter 을 상속받는 것과 차이점은 return 타입을 통해 끝에 build()를 해서 반환해줘야 한다는 점입니다.
이제 페이지 권한 주기의 흐름을 이해하기 위해 빨간 테두리 영역을 봅시다.
http.csrf.disable()
CSRF 공격에 대한 방어를 해제 합니다.
Spring Security는 CSRF 공격에 대한 방어를 기본으로 탑재하고 있습니다.
여기서 CSRF에 대한 개념을 설명하기에는 주제가 벗어나기 때문에 해당 개념은 설명하지 않겠습니다.
어떨 때 CSRF 방어를 해제 해도 되는지 혹은 어떨때 CSRF 방어를 해야하는지에 대해 궁금하시다면 이 링크를 통해 확인해주세요.
.authorizeRequests()
URI에 따른 페이지에 대한 권한을 부여하기 위해 시작하는 메소드 입니다. 아래의 antMatchers 기능을 이용하기 위한 메소드라고 보면 됩니다.
.antMatchers()
특정 URL 접근 시 인가가 필요한 URI를 설정할 수 있습니다.
.authenticated()를 사용하면 해당 URI는 인증이 필요한 URI라고 명시할 수 있습니다.
.anyRequest().permitAll()
특정 URI을 제외한 나머지 URI은 전부 인가해줍니다.
.formLogin()
Form Login은 아이디와 비밀번호를 입력해서 들어오는 로그인 형태를 지원하는 Spring Security 기능입니다. 해당 로그인 방식을 이용할 때 Spring Security가 제공하는 유용한 기능들을 이용할 수 있습니다.
formLogin()을 통해 아래에 loginPage()와 같은 메소드를 사용할 수 있습니다.
.loginPage("/login")
로그인 페이지를 설정합니다. 무슨 말이냐면 로그인을 하지 않았을 경우 로그인이 필요한 페이지를 들어갔을 때 로그인 페이지로 다시 redirect 하기 위한 메소드입니다. 즉, 인가되지 않은 사용자에게 보여줄 페이지를 설정합니다.
더불어 logout 기능 역시 /logout으로 컨트롤러 작성 없이 바로 이용할 수 있으며 logout후에는 loginPage에 설정된 경로의 페이지를 보여줍니다.
.loginProcessingUrl("/loginProc")
formLogin의 자동 로그인 방식을 이용합니다. username과 password를 위의 경로에 던져주면 자동으로 로그인이 되도록 구현합니다.
.defaultSuccessUrl("/")
로그인을 성공했을 때의 기본 url을 설정합니다.
참고 - 위의 설정들을 했다면 Spring Security가 제공하는 초기 로그인 페이지는 사라집니다.
정확히는 permitAll()로 인해 권한이 필요 없는 페이지는 전부 인가되고 권한이 필요한 페이지는 /login으로 들어오게 됩니다.
이제 "user 페이지 이동" 버튼을 누르면 /login 페이지로 자동으로 이동하게 됩니다.
회원가입과 비밀번호 암호화 설정
/join으로 접속하면 회원가입 화면으로 넘어갑니다.
회원가입에 대한 특별한 기능이 있는 것은 아닙니다. 하지만 비밀번호에 대한 암호화 기능이 존재합니다.
BCryptPasswordEncoder라는 Security가 제공하는 암호화 클래스를 이용해야 합니다.
WebSecurityConfig 클래스에 BCryptPasswordEncoder 클래스를 Bean으로 등록해서 Controller에 의존성 주입을 받아서 위와 같이 사용하면 됩니다.
해당 암호화 기능을 이용하지 않는다면 아래 설명할 Spring Security의 로그인 기능을 이용할 수 없습니다.
로그인 기능 위임 및 로그인 후 Redirect 처리
여기서는 로그인을 Spring Security가 대신 위임해서 처리해주는 기능을 만듭니다.
그리고 로그인이 완료되면 적절한 페이지로 Redirect되는 기능을 만듭니다.
.loginProcessingUrl()
loginProcessingUrl안에 주소를 설정해주면 해당 URL로 진입 시 Spring Security가 로그인 기능을 위임받아서 처리합니다.
/loginProc이란 주소를 컨트롤러에 만들 필요는 없습니다.
/loginProc 주소로 들어오면 UserDetailService를 구현한 클래스(@Service가 붙은 클래스) 내에 loadUserByUsername 메소드가 자동으로 실행되게 됩니다.
loadUserByUsername의 매개변수 username은 화면에서 넘어온 input의 name 속성에 설정한 이름과 동일해야 합니다.
.defaultSuccessUrl("/")
- /login화면으로 들어가 로그인 성공 시 / 경로로 이동하게 됩니다.
만약에 로그인 전에 user 페이지를 들어가서 인가가 거부 당해 로그인 페이지로 이동 됐다면 로그인 후 다시 /user로 Redirect 합니다.
loadUserByUsername에서 핵심은 UserDetails 객체를 반환해줘야 로그인이 됩니다.
그리고 해당 객체는 받아온 user의 정보까지 넣어줘야 하기에 UserDetails를 상속받은 MemberDetails를 하나 만들고 아래와 같이 설정해줍니다.
참고 - JPA Entity는 스프링 Bean이 아니므로 @Autowired가 아닌 직접 위와 같이 주입해줘야 합니다.
그리고 user에 대한 Username과 Password를 반환하도록 설정합니다.
권한 부여 리팩토링 하기
로그인 시 각각의 사용자마다 권한을 부여해줘야 합니다. 권한에 따라 특정 API에 대한 인증이 승인 될 수도 있고 거절될 수도 있기 때문에 Security를 이용해 권한을 부여해줘야 합니다. 권한은 여러개가 될 수 있으므로 Collection 형태의 값으로 반환해줍니다.
아래의 방법을 다 알아야 하는 것은 아닙니다. 하지만 여러 방법을 써보면서 리팩토링 하길 원한다면 다 보길 추천 합니다. 개인적으로는 세번째 방법을 제일 추천합니다.
첫번째 방법 - GrantedAuthority() 객체 구현
new GrantedAuthority()의 익명 클래스를 이용하여 내부에 메소드를 Override 해서 Collect에 넣어주는 방법입니다.
두번째 방법 - 람다식 치환
위 방식이 코드가 복잡하니 람다식으로 아래와 같이 치환할 수 있습니다.
세번째 방법 - SimpleGrantedAuthority()
생성자 매개변수에 String 값을 반환하여 위와 같이 Override로 구현할 필요가 없습니다.
덕분에 람다식을 사용하지 않아도 되니 코드가 더 깔끔해졌습니다.
네번째 방법 - Collections.singleton
Collections.singleton을 이용하여 바로 Collection 객체를 바로 Return 하는 방법입니다.
다만 아래와 같은 방법은 Collection에 권한을 1개만 주는 방법이므로 여러개를 줄 경우 위와 같이 Collection 타입을 다시 선언해서 구현해야합니다.
결론
여기까지가 기초 사용법입니다.
세션 쿠키 로그인 방식은 구현하기 복잡하고 보안 문제 때문에 최근에는 많이 사용하지 않는 로그인 방식 입니다.
최근에는 더 좋은 선택지인 OAuth2와 JWT 토큰 로그인 방식이 존재합니다.
Spring Security로 구현하는 JWT 토큰 구현 방식은 위의 사용법 보다는 조금 더 까다롭습니다.
JWT 토큰 로그인 방식은 2편인 Spring Boot로 만드는 Spring Security 로그인 구현 - JWT(2)로 연결 됩니다.
JWT 로그인 방식은 위의 기초 내용을 이해하지 못하면 어려울 수 있습니다.
아래 블로그는 자바와 스프링에서 사용되는 기술의 정의, 효율에 대한 내용을 많이 담고있습니다
방문하셔서 읽어보시기를 추천드립니다
출처 : https://stir.tistory.com/266