์ด๋ฒ์๋ ์ ๋ฒ ํฌ์คํ ์ ์ด์ด์ ์ํ๋ฆฌํฐ ํํฐ๋ฅผ ์ปค์คํ ํ์ฌ ๋ก๊ทธ์ธ์ ์งํํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
- ์ํ๋ฆฌํฐ๋ฅผ ์ด์ฉํ JSON ๋ฐ์ดํฐ๋ก ๋ก๊ทธ์ธ (์งํ ์ค)
- JWT๋ฅผ ์ด์ฉํ ์ธ์ฆ
- ๋๋ฉ์ธ, ํ ์ด๋ธ ์ค๊ณ, ์ํฐํฐ ์์ฑ
- ๋๊ธ ์ญ์ ๋ก์ง ๊ตฌํ
- ํ์๊ฐ์ + ์ ๋ณด์์ ๋ฑ ํ์ ์๋น์ค ๊ตฌํ
- ๊ฒ์ํ ์๋น์ค ๊ตฌํ
- ๋๊ธ ์๋น์ค ๊ตฌํ (1๋๊ธ -> *(๋ฌดํ) ๋๋๊ธ ๊ตฌ์กฐ)
- ์์ธ ์ฒ๋ฆฌ
- ์์ธ ๋ฉ์ธ์ง ๊ตญ์ ํ
- ์นดํ ๊ณ ๋ฆฌ๋ณ ๊ฒ์ํ ๋ถ๋ฅ
- ๊ฒ์๊ธ ํ์ด์ง
- ๋์ ์ธ ๊ฒ์ ์กฐ๊ฑด์ ์ฌ์ฉํ ๊ฒ์
- ์ฌ์ฉ์ ๊ฐ ์ชฝ์ง ๊ธฐ๋ฅ
- ๋ฌดํ ์ชฝ์ง ์คํฌ๋กค
- ๊ฒ์๋ฌผ & ๋๊ธ์ ๋ํ ์๋
- ์ชฝ์ง์ ๋ํ ์๋
- ์ ์ํ ์ฌ์ฉ์ ๊ฐ ์ค์๊ฐ ์ฑํ
- ํ์๊ฐ์ ์ ๊ฒ์ฆ(์: XX๋ํ๊ต XX๊ณผ๊ฐ ์๋๋ฉด ๊ฐ์ ํ ์ ์๊ฒ)
- Swagger๋ฅผ ์ฌ์ฉํ API ๋ฌธ์ ๋ง๋ค๊ธฐ
- ์ ๊ณ & ๋ธ๋๋ฆฌ์คํธ ๊ธฐ๋ฅ
- AOP๋ฅผ ํตํ ๋ก๊ทธ
- ์ด๋๋ฏผ ํ์ด์ง
- ์บ์
- ๋ฐฐํฌ (+ ๋ฌด์ค๋จ ๋ฐฐํฌ)
- ๋ฐฐํฌ ์๋ํ
- ํฌํธ์ ์ด๋ํฐ ์ค๊ณ๋ฅผ ๋ฐ๋ฅด๋ ํจํค์ง ๊ตฌ์กฐ ์ค๊ณํ๊ธฐ
- ...
์ํ๋ฆฌํฐ ํํฐ ์ปค์คํ ํ๊ธฐ
AbstractAuthenticationProcessingFilter ๊ตฌํ
formLogin์๋ AbstractAuthenticationProcessingFilter์๋ ์ธ์ฆ ์ฑ๊ณต๊ณผ ์คํจ์ ๋ํ ๋๋ถ๋ถ์ ๋ก์ง์ด ๋ค์ด์์ต๋๋ค.
๋ฐ๋ผ์ ์ฐ๋ฆฌ๋ formLogin ๋ฐฉ์๊ณผ ๋น์ทํ๊ฒ, ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์๋ง json์ผ๋ก ๋ฐ๊พธ์ด์ ์ฒ๋ฆฌํ๋๋ก ๋ง๋ค๊ฒ ์ต๋๋ค.
์ด๋ฆ์ JsonUsernamePasswordAuthenticationFilter๋ก ์ ํ๊ฒ ์ต๋๋ค.
public class JsonUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String DEFAULT_LOGIN_REQUEST_URL = "/login"; // /login/oauth2/ + ????? ๋ก ์ค๋ ์์ฒญ์ ์ฒ๋ฆฌํ ๊ฒ์ด๋ค
private static final String HTTP_METHOD = "POST"; //HTTP ๋ฉ์๋์ ๋ฐฉ์์ POST ์ด๋ค.
private static final String CONTENT_TYPE = "application/json";//json ํ์
์ ๋ฐ์ดํฐ๋ก๋ง ๋ก๊ทธ์ธ์ ์งํํ๋ค.
private final ObjectMapper objectMapper;
private static final String USERNAME_KEY="username";
private static final String PASSWORD_KEY="password";
private static final AntPathRequestMatcher DEFAULT_LOGIN_PATH_REQUEST_MATCHER =
new AntPathRequestMatcher(DEFAULT_LOGIN_REQUEST_URL, HTTP_METHOD); //=> /login ์ ์์ฒญ์, POST๋ก ์จ ์์ฒญ์ ๋งค์นญ๋๋ค.
public JsonUsernamePasswordAuthenticationFilter(ObjectMapper objectMapper) {
super(DEFAULT_LOGIN_PATH_REQUEST_MATCHER); // ์์์ ์ค์ ํ /oauth2/login/* ์ ์์ฒญ์, GET์ผ๋ก ์จ ์์ฒญ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ค์ ํ๋ค.
this.objectMapper = objectMapper;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
if(request.getContentType() == null || !request.getContentType().equals(CONTENT_TYPE) ) {
throw new AuthenticationServiceException("Authentication Content-Type not supported: " + request.getContentType());
}
String messageBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
Map<String, String> usernamePasswordMap = objectMapper.readValue(messageBody, Map.class);
String username = usernamePasswordMap.get(USERNAME_KEY);
String password = usernamePasswordMap.get(PASSWORD_KEY);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);//principal ๊ณผ credentials ์ ๋ฌ
return this.getAuthenticationManager().authenticate(authRequest);
}
}
์ค๋ช
- DEFAULT_LOGIN_REQUEST_URL ๋ฅผ ํตํด "/login"์ผ๋ก ๋ค์ด์ค๋ ์์ฒญ์ ๋ํด์๋ง ์๋ํ๋๋ก ์ค์ ํ์ต๋๋ค.
- HTTP_METHOD, CONTENT_TYPE : POST์, json ํ์ ์ผ๋ก ์ค๋ ๋ฐ์ดํฐ๋ง ์ฒ๋ฆฌํ๋๋ก ์ค์ ํ์ต๋๋ค.
- attemptAuthentication ๋ฉ์๋๋ฅผ ๊ตฌํํ์์ต๋๋ค.
- username๊ณผ password๋ฅผ ๋ฐ์์ FormLogin๊ณผ ๋์ผํ๊ฒ UsernamePasswordAuthenticationToken์ ์ฌ์ฉํ์ต๋๋ค. JSON์ผ๋ก ๋ก๊ทธ์ธํ๋ ๋ฐฉ์๋ง ๋ฌ๋ผ์ก์ ๋ฟ์ด์ง username๊ณผ password๋ฅผ ์ฌ์ฉํ์ฌ ๋ก๊ทธ์ธํ๋ ์ ๋ต์ ๋๊ฐ๊ธฐ ๋๋ฌธ์ ๊ตณ์ด ๋ฐ๋ก ๊ตฌํํ์ง ์๊ณ ์๋๊ฑธ ๊ฐ์ ธ๋ค ์ฌ์ฉํ์์ต๋๋ค.
- return์ authenticationManager์ authenticate ๋ฉ์๋๋ฅผ ์คํํ์ต๋๋ค. ์ฌ๊ธฐ์ ์ฌ์ฉ๋๋ AuthenticationManager๋ ProviderManager์ด๊ณ , ์ด ๋ํ ์ดํ SecurityConfig ํ์ผ์์ ์ค์ ํด ์ฃผ๊ฒ ์ต๋๋ค.
SecurityConfig ์ค์
(์ถ๊ฐ - WebSecurityConfigurerAdapter๊ฐ Deprecate ๋์์ต๋๋ค. ์๋ ์ฌ์ดํธ์ ์ด๋ป๊ฒ ์์ ํด์ผ ํ๋์ง ๋์์์ต๋๋ค.
https://www.codejava.net/frameworks/spring-boot/fix-websecurityconfigureradapter-deprecated )
๊ธฐ์กด์ ์์ฑํ ์ฝ๋์ ์ถ๊ฐํ์์ต๋๋ค.
@Configuration
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//private final LoginService loginService;
private final ObjectMapper objectMapper;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().disable()//formLogin ์ธ์ฆ๋ฐฉ๋ฒ ๋นํ์ฑํ
.httpBasic().disable()//httpBasic ์ธ์ฆ๋ฐฉ๋ฒ ๋นํ์ฑํ(ํน์ ๋ฆฌ์์ค์ ์ ๊ทผํ ๋ username๊ณผ password ๋ฌผ์ด๋ด)
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login", "/signUp","/").permitAll()
.anyRequest().authenticated();
http.addFilterAfter(jsonUsernamePasswordLoginFilter(), LogoutFilter.class);
}
@Bean
public PasswordEncoder passwordEncoder(){//1 - PasswordEncoder ๋ฑ๋ก
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager() {//2 - AuthenticationManager ๋ฑ๋ก
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();//DaoAuthenticationProvider ์ฌ์ฉ
provider.setPasswordEncoder(passwordEncoder());//PasswordEncoder๋ก๋ PasswordEncoderFactories.createDelegatingPasswordEncoder() ์ฌ์ฉ
//provider.setUserDetailsService(loginService); //์ดํ ์์ฑํ ์ฝ๋์
๋๋ค.
return new ProviderManager(provider);
}
/*@Bean
public LoginSuccessJWTProvideHandler loginSuccessJWTProvideHandler(){
return new LoginSuccessJWTProvideHandler();
}
@Bean
public LoginFailureHandler loginFailureHandler(){
return new LoginFailureHandler();
}*/
@Bean
public JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordLoginFilter(){
JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordLoginFilter = new JsonUsernamePasswordAuthenticationFilter(objectMapper);
jsonUsernamePasswordLoginFilter.setAuthenticationManager(authenticationManager());
//jsonUsernamePasswordLoginFilter.setAuthenticationSuccessHandler(loginSuccessJWTProvideHandler());
//jsonUsernamePasswordLoginFilter.setAuthenticationFailureHandler(loginFailureHandler());
return jsonUsernamePasswordLoginFilter;
}
}
(์ฃผ์ ์ฒ๋ฆฌํ ์ฝ๋๋ ์ดํ ์์ฑํ ์ฝ๋๋ค์ ๋๋ค.)
์ค๋ช
- PasswordEncoder๋ก PasswordEncoderFactories.createDelegatingPasswordEncoder()๋ฅผ ์ฌ์ฉํฉ๋๋ค.
- ํด๋น PasswordEncoder๋ฅผ ์ฌ์ฉํ๋ AuthenticationProvider๋ฅผ ์ง์ ํด์ค๋ค. FormLogin์์์ ๋์ผํ๊ฒ DaoAuthenticationProvider๋ฅผ ์ฌ์ฉํฉ๋๋ค.
- AuthemticationManager๋ก๋ FormLogin์์์ ๋์ผํ๊ฒ ProviderManager๋ฅผ ์ฌ์ฉํฉ๋๋ค.
- ๋ฐ๋ก ์์์ ์์ฑํ JsonUsernamePasswordAuthenticationFilter๋ฅผ ๋น์ผ๋ก ๋ฑ๋กํฉ๋๋ค. ์ด๋ AuthenticationManager๋ฅผ ๋ฑ๋กํด ์ฃผ์ด์ผ ํ๊ณ , ๋ฑ๋กํ์ง ์์ผ๋ฉด ๋ค์์ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
authenticationManager must be specified => authenticationManager๋ฅผ ์ง์ ํด ์ฃผ์ด์ผ ํ๋ค๋ ์ค๋ฅ์ ๋๋ค.
์ด์ UserDetailsService๋ฅผ ์์๋ฐ์ UserDetails๋ฅผ ๋ฐํํ๋ ํด๋์ค๋ฅผ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
LoginService ์์ฑ
@Service
@RequiredArgsConstructor
public class LoginService implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Member member = memberRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("์์ด๋๊ฐ ์์ต๋๋ค"));
return User.builder().username(member.getUsername())
.password(member.getPassword())
.roles(member.getRole().name())
.build();
}
}
DB์์ username์ ํด๋นํ๋ ๊ฐ์ ์ฐพ์ ๋ฐํํด์ฃผ๋ฉด ๋ฉ๋๋ค.
๋น๋ฐ๋ฒํธ ๊ฒ์ฆ์ DaoAuthenticationProvider ์์ ํด์ค๋๋ค.
๋ ์ ํํ๋ AbstractUserDetailsAuthenticationProvider์์ ํด์ฃผ๋๋ฐ, ์ฝ๋๋ฅผ ํตํด ์ดํด๋ณด๊ฒ ์ต๋๋ค.
retriveUser์ ๋ง์ฐฌ๊ฐ์ง๋ก additionalAuthenticationChecks๋ ์ถ์ ๋ฉ์๋๋ก ๊ตฌํ ํด๋์ค์๊ฒ ์ฒ๋ฆฌ๋ฅผ ๋งก๊น๋๋ค.
DaoAuthenticationProvider๋ ์ด๋ฅผ ๊ตฌํํด์ผ ํ๊ณ ๊ทธ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๋น๋ฐ๋ฒํธ(์ฌ๊ธฐ์๋ Credentials)์ ์ผ์น์ฌ๋ถ๋ฅผ ํ๋จํฉ๋๋ค.
UserDetails๋ ์ฐ๋ฆฌ๊ฐ UserDetailsService์์ ๋ง๋ค์ด์ค User ๊ฐ์ฒด์ด๊ณ ,
์ด๊ฒ์ password ์
JsonUsernamePasswordAuthenticationFilter (์ ํํ๋ AbstractAuthenticationProcessingFilter)์์
Request์ ์ ๋ณด๋ฅผ ํตํด ์ธ์๋ก ์ ๋ฌํด์ค Authentication ๊ฐ์ฒด(์ฌ๊ธฐ์๋ UsernamePasswordAuthenticationToken)์ Credentials (์ฐ๋ฆฌ๋ password๋ฅผ ๋ฃ์ด์ฃผ์๋ค)๋ฅผ ๋น๊ตํฉ๋๋ค.
๋ฐ๋ผ์ ์ ํฌ๋ DB์์ User์ ๋ณด๋ฅผ ์ฐพ์์ ๋ฐํ๋ง ํด์ฃผ๋ฉด ๋ฉ๋๋ค.
์ด์ ์ฑ๊ณต ์ฒ๋ฆฌ์ ์คํจ ์ฒ๋ฆฌ๋ฅผ ํ Handler๋ฅผ ๊ตฌํํด๋ณด๊ฒ ์ต๋๋ค.
LoginSuccessJWTProvideHandler ์์ฑ
SimpleUrlAuthenticationSuccessHandler๋ฅผ ์์๋ฐ์ ๊ตฌํํฉ๋๋ค.
@Slf4j
public class LoginSuccessJWTProvideHandler extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
log.info( "๋ก๊ทธ์ธ์ ์ฑ๊ณตํฉ๋๋ค JWT๋ฅผ ๋ฐ๊ธํฉ๋๋ค. username: {}" ,userDetails.getUsername());
response.getWriter().write("success");
}
}
(์์ง JWT๋ฅผ ๋ฐ๊ธํ๋ ์ฝ๋๋ ์์ฑํ์ง ์์์ต๋๋ค.)
LoginFailureHandler ์์ฑ
SimpleUrlAuthenticationFailureHandler๋ฅผ ์์๋ฐ์ ๊ตฌํํ์ต๋๋ค.
@Slf4j
public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);//๋ณด์์ ์ํด ๋ก๊ทธ์ธ ์ค๋ฅ์ง๋ง 200 ๋ฐํ
response.getWriter().write("fail");
log.info("๋ก๊ทธ์ธ์ ์คํจํ์ต๋๋ค");
}
}
(์ํ์ฝ๋๋ฅผ 200์ผ๋ก ์ค์ ํ๋ ์ด์ ๋ ์๋ฒ ๋ณด์์ ์ํด ๋ชจ๋ ์์ฒญ์ ๋ํด ์ํ์ฝ๋๋ฅผ 200์ผ๋ก ๋ฐํํ๊ณ ์ปค์คํ ํ ์ํ์ฝ๋๋ฅผ ๊ฐ์ด ์ ๊ณตํ๋ค๋ ์ด์ผ๊ธฐ๋ฅผ ๋ค์ด์ ์ด๋ฒ์๋ ๊ทธ๋ ๊ฒ ์์ฑํ๋ ค๊ณ ๋ง๋ค์์ต๋๋ค.
์ดํ ๋ณ๊ฒฝ๋ ์ ์์ผ๋ฉฐ, fail์ ๋ฐํํ๋ ๊ฒ์ ์ดํ ๋ณ๊ฒฝํ๋๋ก ํ๊ฒ ์ต๋๋ค.
์ฐ์ ์ ์คํจ ์ฌ๋ถ๋ฅผ ํ๋จํ๊ธฐ ์ํด ์์ฑํ์ต๋๋ค)
SecurityConfig ์ค์ ์ถ๊ฐ
@Configuration
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final LoginService loginService;
private final ObjectMapper objectMapper;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().disable()// formLogin ์ธ์ฆ๋ฐฉ๋ฒ ๋นํ์ฑํ
.httpBasic().disable()// httpBasic ์ธ์ฆ๋ฐฉ๋ฒ ๋นํ์ฑํ(ํน์ ๋ฆฌ์์ค์ ์ ๊ทผํ ๋ username๊ณผ password ๋ฌผ์ด๋ด)
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login", "/signUp","/").permitAll()
.anyRequest().authenticated();
http.addFilterAfter(jsonUsernamePasswordLoginFilter(), LogoutFilter.class);
}
@Bean
public PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(loginService);
return new ProviderManager(provider);
}
@Bean
public LoginSuccessJWTProvideHandler loginSuccessJWTProvideHandler(){
return new LoginSuccessJWTProvideHandler();
}
@Bean
public LoginFailureHandler loginFailureHandler(){
return new LoginFailureHandler();
}
@Bean
public JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordLoginFilter(){
JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordLoginFilter = new JsonUsernamePasswordAuthenticationFilter(objectMapper);
jsonUsernamePasswordLoginFilter.setAuthenticationManager(authenticationManager());
jsonUsernamePasswordLoginFilter.setAuthenticationSuccessHandler(loginSuccessJWTProvideHandler());
jsonUsernamePasswordLoginFilter.setAuthenticationFailureHandler(loginFailureHandler());
return jsonUsernamePasswordLoginFilter;
}
}
ํ ์คํธ์ฝ๋ ์์ฑ
- ๋ก๊ทธ์ธ ์ฑ๊ณต - 200์ ์ฑ๊ณต ๋ฉ์ธ์ง ๋ฐํ
- ๋ก๊ทธ์ธ ์คํจ - ์์ด๋ ํ๋ฆผ - 200์ ์คํจ ๋ฉ์ธ์ง
- ๋ก๊ทธ์ธ ์คํจ - ๋น๋ฐ๋ฒํธ ํ๋ฆผ -200์ ์คํจ ๋ฉ์ธ์ง
- ๋ก๊ทธ์ธ ์ฃผ์๊ฐ ํ๋ฆฌ๋ฉด 403 Forbidden
- ๋ก๊ทธ์ธ ๋ฐ์ดํฐํ์์ด Json์ด ์๋๋ฉด 200์ ์คํจ ๋ฉ์ธ์ง (๋ก๊ทธ์ธ ์คํจ์ ๋์ผ)
- ๋ก๊ทธ์ธ Http Method๊ฐ Post๊ฐ ์๋๋ฉด 404 NotFound
๊ธฐ๋ณธ ๋ก๊ทธ์ธ ํ ์คํธ ํ์
@Transactional
@SpringBootTest
@AutoConfigureMockMvc
public class LoginTest {
@Autowired MockMvc mockMvc;
@Autowired MemberRepository memberRepository;
@Autowired EntityManager em;
PasswordEncoder delegatingPasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
ObjectMapper objectMapper = new ObjectMapper();
private static String KEY_USERNAME = "username";
private static String KEY_PASSWORD = "password";
private static String USERNAME = "username";
private static String PASSWORD = "123456789";
private static String LOGIN_RUL = "/login";
private void clear(){
em.flush();
em.clear();
}
@BeforeEach
private void init(){
memberRepository.save(Member.builder()
.username(USERNAME)
.password(delegatingPasswordEncoder.encode(PASSWORD))
.name("Member1")
.nickName("NickName1")
.role(Role.USER)
.age(22)
.build());
clear();
}
private Map getUsernamePasswordMap(String username, String password){
Map<String, String> map = new HashMap<>();
map.put(KEY_USERNAME, username);
map.put(KEY_PASSWORD, password);
return map;
}
private ResultActions perform(String url, MediaType mediaType, Map usernamePasswordMap) throws Exception {
return mockMvc.perform(MockMvcRequestBuilders
.post(url)
.contentType(mediaType)
.content(objectMapper.writeValueAsString(usernamePasswordMap)));
}
๋ก๊ทธ์ธ ์ฑ๊ณต
@Test
public void ๋ก๊ทธ์ธ_์ฑ๊ณต() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME, PASSWORD);
//when, then
MvcResult result = perform(LOGIN_RUL, APPLICATION_JSON, map)
.andDo(print())
.andExpect(status().isOk())
.andReturn();
}
๋ก๊ทธ์ธ ์คํจ - ์์ด๋ ์ค๋ฅ
@Test
public void ๋ก๊ทธ์ธ_์คํจ_์์ด๋ํ๋ฆผ() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME+"123", PASSWORD);
//when, then
MvcResult result = perform(LOGIN_RUL, APPLICATION_JSON, map)
.andDo(print())
.andExpect(status().isOk())
.andReturn();
}
๋ก๊ทธ์ธ ์คํจ - ๋น๋ฐ๋ฒํธ ์ค๋ฅ
@Test
public void ๋ก๊ทธ์ธ_์คํจ_๋น๋ฐ๋ฒํธํ๋ฆผ() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME, PASSWORD+"123");
//when, then
MvcResult result = perform(LOGIN_RUL, APPLICATION_JSON, map)
.andDo(print())
.andExpect(status().isOk())
.andReturn();
}
๋ก๊ทธ์ธ ์ฃผ์๊ฐ ํ๋ฆฌ๋ฉด Forbidden
@Test
public void ๋ก๊ทธ์ธ_์ฃผ์๊ฐ_ํ๋ฆฌ๋ฉด_FORBIDDEN() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME, PASSWORD);
//when, then
perform(LOGIN_RUL+"123", APPLICATION_JSON, map)
.andDo(print())
.andExpect(status().isForbidden());
}
๋ก๊ทธ์ธ ํ์ JSON์ด ์๋๋ฉด 200
@Test
public void ๋ก๊ทธ์ธ_๋ฐ์ดํฐํ์_JSON์ด_์๋๋ฉด_200() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME, PASSWORD);
//when, then
perform(LOGIN_RUL, APPLICATION_FORM_URLENCODED, map)
.andDo(print())
.andExpect(status().isOk())
.andReturn();
}
๋ก๊ทธ์ธ Http Method๊ฐ Post๊ฐ ์๋๋ฉด 404 NotFound
@Test
public void ๋ก๊ทธ์ธ_HTTP_METHOD_GET์ด๋ฉด_NOTFOUND() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME, PASSWORD);
//when
mockMvc.perform(MockMvcRequestBuilders
.get(LOGIN_RUL)
.contentType(APPLICATION_FORM_URLENCODED)
.content(objectMapper.writeValueAsString(map)))
.andDo(print())
.andExpect(status().isNotFound());
}
@Test
public void ์ค๋ฅ_๋ก๊ทธ์ธ_HTTP_METHOD_PUT์ด๋ฉด_NOTFOUND() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME, PASSWORD);
//when
mockMvc.perform(MockMvcRequestBuilders
.put(LOGIN_RUL)
.contentType(APPLICATION_FORM_URLENCODED)
.content(objectMapper.writeValueAsString(map)))
.andDo(print())
.andExpect(status().isNotFound());
}
์ ์ฒด ํ ์คํธ์ฝ๋
import static org.springframework.http.MediaType.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.transaction.annotation.Transactional;
import com.fasterxml.jackson.databind.ObjectMapper;
import boardexample.myboard.domain.member.Member;
import boardexample.myboard.domain.member.MemberRepository;
import boardexample.myboard.domain.member.Role;
@Transactional
@SpringBootTest
@AutoConfigureMockMvc
class LoginTest {
@Autowired
MockMvc mockMvc;
@Autowired
MemberRepository memberRepository;
@Autowired
EntityManager em;
PasswordEncoder delegatingPasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
ObjectMapper objectMapper = new ObjectMapper();
private static String KEY_USERNAME = "username";
private static String KEY_PASSWORD = "password";
private static String USERNAME = "username";
private static String PASSWORD = "123456789";
private static String LOGIN_RUL = "/login";
private void clear() {
em.flush();
em.clear();
}
@BeforeEach
private void init() {
memberRepository.save(Member.builder()
.username(USERNAME)
.password(delegatingPasswordEncoder.encode(PASSWORD))
.name("Member1")
.nickName("NickName1")
.role(Role.USER)
.age(22)
.build());
clear();
}
private Map getUsernamePasswordMap(String username, String password) {
Map<String, String> map = new HashMap<>();
map.put(KEY_USERNAME, username);
map.put(KEY_PASSWORD, password);
return map;
}
private ResultActions perform(String url, MediaType mediaType, Map usernamePasswordMap) throws Exception {
return mockMvc.perform(MockMvcRequestBuilders
.post(url)
.contentType(mediaType)
.content(objectMapper.writeValueAsString(usernamePasswordMap)));
}
@Test
public void ๋ก๊ทธ์ธ_์ฑ๊ณต() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME, PASSWORD);
//when
MvcResult result = perform(LOGIN_RUL, APPLICATION_JSON, map)
.andDo(print())
.andExpect(status().isOk())
.andReturn();
}
@Test
public void ๋ก๊ทธ์ธ_์คํจ_์์ด๋ํ๋ฆผ() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME+"123", PASSWORD);
//when
MvcResult result = perform(LOGIN_RUL, APPLICATION_JSON, map)
.andDo(print())
.andExpect(status().isOk())
.andReturn();
}
@Test
public void ๋ก๊ทธ์ธ_์คํจ_๋น๋ฐ๋ฒํธํ๋ฆผ() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME, PASSWORD+"123");
//when
MvcResult result = perform(LOGIN_RUL, APPLICATION_JSON, map)
.andDo(print())
.andExpect(status().isOk())
.andReturn();
}
@Test
public void ๋ก๊ทธ์ธ_์ฃผ์๊ฐ_ํ๋ฆฌ๋ฉด_FORBIDDEN() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME, PASSWORD);
//when, then
perform(LOGIN_RUL+"123", APPLICATION_JSON, map)
.andDo(print())
.andExpect(status().isForbidden());
}
@Test
public void ๋ก๊ทธ์ธ_๋ฐ์ดํฐํ์_JSON์ด_์๋๋ฉด_200() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME, PASSWORD);
//when, then
perform(LOGIN_RUL, APPLICATION_FORM_URLENCODED, map)
.andDo(print())
.andExpect(status().isOk())
.andReturn();
}
@Test
public void ๋ก๊ทธ์ธ_HTTP_METHOD_GET์ด๋ฉด_NOTFOUND() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME, PASSWORD);
//when
mockMvc.perform(MockMvcRequestBuilders
.get(LOGIN_RUL)
.contentType(APPLICATION_FORM_URLENCODED)
.content(objectMapper.writeValueAsString(map)))
.andDo(print())
.andExpect(status().isNotFound());
}
@Test
public void ์ค๋ฅ_๋ก๊ทธ์ธ_HTTP_METHOD_PUT์ด๋ฉด_NOTFOUND() throws Exception {
//given
Map<String, String> map = getUsernamePasswordMap(USERNAME, PASSWORD);
//when
mockMvc.perform(MockMvcRequestBuilders
.put(LOGIN_RUL)
.contentType(APPLICATION_FORM_URLENCODED)
.content(objectMapper.writeValueAsString(map)))
.andDo(print())
.andExpect(status().isNotFound());
}
}
์ด๋ ๊ฒ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ๊น์ง ์ฑ๊ณต์ ์ผ๋ก ๋๋ง์ณค์ต๋๋ค.
์ด์ ๋ก๊ทธ์ธ ํ์ ๊ฒฝ์ฐ JWT๋ฅผ ๋ฐ๊ธํ๊ณ , ์ธ์ฆ์ด ํ์ํ ์์ฒญ์ ๋ํด์ AccessToken์ ๊ฐ์ง๊ณ ์ธ์ฆ์ ์ํํ๋ ์ฝ๋๋ฅผ ์์ฑํ๊ฒ ์ต๋๋ค.
์ ์ฒด ์ฝ๋๋ ๊นํ๋ธ์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
https://github.com/ShinDongHun1/SpringBoot-Board-API