지난 글에서 스프링 앱에 최소한의 인증과 접근 관리를 추가해봤습니다. 권한별로 접근할 수 있는 url을 정의해 놓고 인증 여부, 권한 여부에 따라 테스트를 작성했습니다. 이번에는 지난 번 앱의 인증 부분을 좀 더 고쳐보도록 하죠. 최종 소스는 github의 step-02 브랜치에 있습니다.
실제 운용할 때는 오라클 같은 상용 디비를 쓰겠지만, 테스트니까 간단하게 H2를 사용하겠습니다. H2는 메모리/파일 기반 데이터베이스입니다. MySQL처럼 설치하고, 사용자 설정하고, 서버 시작하고 등등 이런 번거로움 없이 간단하게 테스트가 가능합니다. H2를 사용하려면 H2에 대한 의존성과 Spring Data JPA에 대한 의존성을 추가해줘야 합니다.
implementation('org.springframework.boot:spring-boot-starter-data-jpa') implementation('com.h2database:h2')
우리가 사용할 테이블 정의는 간단합니다. 사용자명, 비밀번호, 이메일, 권한 이렇게 네 개의 컬럼이 존재하는 User 테이블입니다. src/main/resources에 schema.sql이라는 파일을 만들어 다음과 같이 저장합니다.
CREATE TABLE IF NOT EXISTS User ( username VARCHAR(16) NOT NULL, password VARCHAR(16) NOT NULL, email VARCHAR(64) NOT NULL, role VARCHAR(16) NOT NULL, PRIMARY KEY (username) );
매번 DB에 접속해서 사용자 레코드를 생성하기는 귀찮으니 src/main/resources/data.sql을 하나 생성해서 다음과 같이 저장해 두면 스프링 시작 때 자동으로 DB에 SQL을 실행해줍니다.
INSERT INTO User VALUES ('user', 'password', 'user@email', 'USER'); INSERT INTO User VALUES ('admin', 'password', 'admin@email', 'ADMIN');
schema.sql과 data.sql이 스프링 시작 때 실행되려면 설정을 좀 바꿔줘야 합니다. application.properties 파일을 열어 다음과 같이 바꿔줍니다.
spring.jpa.hibernate.ddl-auto=none spring.h2.console.enabled=true
User 테이블은 위의 schema.sql이 생성을 하고, 데이터는 미리 data.sql이 넣어줍니다. User 테이블에서 자료를 받아오면 저장할 User 클래스를 만들어 봅시다. username, password, email, role이라는 필드를 넣어주고, getter, setter, constructor 생성해 주면 됩니다.
@Entity public class User { @Id private String username; private String password; private String email; private String role; public User() { } public User(String username, String password, String email, String role) { this.username = username; this.password = password; this.email = email; this.role = role; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } }
User 테이블에 자료를 읽고 쓰는 클래스를 만들어 봅시다. JpaRepository는 테이블에 대응하는 클래스와 테이블의 키 타입을 알려주면 기본적인 CRUD 기능을 수행해줍니다. WHERE 절의 조건을 바꾸고 싶다면 메소드 이름만 바꿔주면 됩니다. username으로 사용자를 검색하고 싶으면 findByUsername이라고 메소드만 정의해 주면 JpaRepository가 나머지는 알아서 처리해줍니다.
@Repository public interface UserRepository extends JpaRepository{ User findByUsername(String username); }
이제 데이터베이스 접근에 관한 부분은 완료가 되었습니다.
Spring Security는 기본적으로 user라는 사용자명을 가진 사용자를 하나 생성합니다. 비밀번호는 앱 시작할 때 마다 무작위로 생성해줍니다. 그래서 지난 번 글에서는 application.properties을 고쳐서 사용자명 user, 비밀번호 password를 가지는 사용자를 만들도록 바꿨습니다.
Spring Security 문서에 의하면 스프링 시큐리티에서 기본적으로 제공하는 사용자는 한 명 밖에 없습니다. 따라서 DB에 사용자 정보를 저장해 두고 인증을 해서 등록된 사용자인지, 관리자인지 권한 관리를 해야합니다.
The default AuthenticationManager has a single user (‘user’ username and random password, printed at INFO level when the application starts up)
인증을 위해 사용자 정보를 불러오는 일은 UserDetailsService가 합니다. 이미 스프링은 테스트용으로 클래스를 하나 구현해 뒀습니다. InMemoryUserDetailsManager 라는 이름에서 유추할 수 있다시피, db가 아닌 메모리에 사용자 정보를 저장해 둡니다. 따라서 서버가 멈추면 사용자 정보도 사라집니다.
먼저 application.properties에 아래와 같이 설정해뒀던 사용자 정보를 지웁니다.
spring.security.user.name=user spring.security.user.password=password
그 다음은 UserDetailsService 를 구현하는 생성하는 코드를 작성합니다. loadUserByUsername 메소드만 구현하면 됩니다.
@Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); UserDetails userDetails = null; PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); org.springframework.security.core.userdetails.User.UserBuilder builder = org.springframework.security.core.userdetails.User.builder() .passwordEncoder(encoder::encode); if (user != null) { userDetails = builder.username(user.getUsername()) .password(user.getPassword()) .roles(user.getRole()) .build(); } return userDetails; } }
이번에는 실제 DB에 접속해서 사용자 정보를 가져와서 인증까지 하는 테스트를 해보도록 하겠습니다. 이전 글에서는 @WebMvcTest 주석을 이용해서 테스트를 했습니다. 실제 인증을 하지 않고 인증이 되었다는 가정하에 컨트롤러만 테스트 하는 것이었기에 @WebMvcTest로 충분했습니다. @WebMvcTest는 @Service, @Repository 빈을 불러들이지 않기 때문에 다른 주석을 달아야 합니다.
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. @Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not @Component, @Service or @Repository beans).
If you are looking to load your full application configuration and use MockMVC, you should consider @SpringBootTest combined with @AutoConfigureMockMvc rather than this annotation.
먼저 이전에 있던 SpringSecurityApplicationTests의 이름을 WebSecurityControllerTest라 바꿔놓고, WebSecurityIntegratinoTest를 하나 아래와 같이 생성합니다.
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class WebSecurityIntegrationTest { @Autowired private MockMvc mvc; @Test public void givenRequestOnPrivatePageWithRealCredential_shouldReturn200() throws Exception { mvc.perform( get("/private") .with(httpBasic("user", "wrong-password")) ).andExpect( status().isUnauthorized() ); mvc.perform( get("/private") .with(httpBasic("admin", "password")) ).andExpect( status().isOk() ); mvc.perform( get("/private") .with(httpBasic("user", "password")) ).andExpect( status().isOk() ); } }
Install Docker on Ubuntu (0) | 2019.04.26 |
---|---|
비밀번호 없이 ssh 로그인 (0) | 2019.04.26 |
Install SSH Server on Ubuntu (0) | 2019.04.26 |
Spring Boot Security - 접근 제어 (0) | 2018.11.10 |
자바스크립트 비동기 함수(async function)의 순차 실행 (0) | 2017.08.16 |