#24 simple sns: 포스트 작성 api 구현
This commit is contained in:
@@ -1,15 +1,27 @@
|
|||||||
package com.example.sns.config;
|
package com.example.sns.config;
|
||||||
|
|
||||||
|
import com.example.sns.config.filter.JwtTokenFilter;
|
||||||
|
import com.example.sns.exception.CustomAuthenticationEntryPoint;
|
||||||
|
import com.example.sns.service.UserService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class AuthenticationConfig extends WebSecurityConfigurerAdapter {
|
public class AuthenticationConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
@Value("${jwt.secret-key}")
|
||||||
|
private String key;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
http.csrf().disable()
|
http.csrf().disable()
|
||||||
@@ -20,9 +32,9 @@ public class AuthenticationConfig extends WebSecurityConfigurerAdapter {
|
|||||||
.sessionManagement()
|
.sessionManagement()
|
||||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||||
.and()
|
.and()
|
||||||
// TODO
|
.addFilterBefore(new JwtTokenFilter(key, userService), UsernamePasswordAuthenticationFilter.class)
|
||||||
// .exceptionHandling()
|
.exceptionHandling()
|
||||||
// .authenticationEntryPoint()
|
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.example.sns.config.filter;
|
||||||
|
|
||||||
|
import com.example.sns.model.User;
|
||||||
|
import com.example.sns.service.UserService;
|
||||||
|
import com.example.sns.util.JwtTokenUtils;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JwtTokenFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final String key;
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
|
||||||
|
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||||
|
|
||||||
|
if (header == null || !header.startsWith("Bearer ")) {
|
||||||
|
log.error("Error occurs while getting header. header is null or invalid");
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final String token = header.split(" ")[1].trim();
|
||||||
|
|
||||||
|
if (JwtTokenUtils.isExpired(token, key)) {
|
||||||
|
log.error("Token is expired");
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String username = JwtTokenUtils.getUsername(token, key);
|
||||||
|
|
||||||
|
User user = userService.loadUserByUsername(username);
|
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken authentication =
|
||||||
|
new UsernamePasswordAuthenticationToken(
|
||||||
|
user, null, user.getAuthorities()
|
||||||
|
);
|
||||||
|
|
||||||
|
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
log.error("Error occurs while validating. {}", e.toString());
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.example.sns.controller;
|
||||||
|
|
||||||
|
import com.example.sns.controller.request.PostCreateRequest;
|
||||||
|
import com.example.sns.controller.response.Response;
|
||||||
|
import com.example.sns.service.PostService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/posts")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PostController {
|
||||||
|
|
||||||
|
private final PostService postService;
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public Response<Void> create(@RequestBody PostCreateRequest request,
|
||||||
|
Authentication authentication) {
|
||||||
|
|
||||||
|
postService.create(request.getTitle(), request.getBody(), authentication.getName());
|
||||||
|
|
||||||
|
return Response.success();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,23 @@ public class Response<T> {
|
|||||||
return new Response<>("SUCCESS", result);
|
return new Response<>("SUCCESS", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Response<Void> success() {
|
||||||
|
return new Response<>("SUCCESS", null);
|
||||||
|
}
|
||||||
|
|
||||||
public static Response<Void> error(String errorCode) {
|
public static Response<Void> error(String errorCode) {
|
||||||
return new Response<>(errorCode, null);
|
return new Response<>(errorCode, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toStream() {
|
||||||
|
if (result == null) {
|
||||||
|
return "{" +
|
||||||
|
"\"resultCode\":" + "\"" + resultCode + "\"," +
|
||||||
|
"\"result\":" + null +"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{" +
|
||||||
|
"\"resultCode\":" + "\"" + resultCode + "\"," +
|
||||||
|
"\"result\":" + "\"" + result + "\"" + "}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.example.sns.exception;
|
||||||
|
|
||||||
|
import com.example.sns.controller.response.Response;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||||
|
@Override
|
||||||
|
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
|
||||||
|
response.setContentType("application/json");
|
||||||
|
response.setStatus(ErrorCode.INVALID_TOKEN.getStatus().value());
|
||||||
|
response.getWriter().write(Response.error(ErrorCode.INVALID_TOKEN.name()).toStream());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ public enum ErrorCode {
|
|||||||
DUPLICATED_USER_NAME(HttpStatus.CONFLICT, "Username is duplicated."),
|
DUPLICATED_USER_NAME(HttpStatus.CONFLICT, "Username is duplicated."),
|
||||||
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "User not founded."),
|
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "User not founded."),
|
||||||
INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "Password is invalid."),
|
INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "Password is invalid."),
|
||||||
|
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "Token is invalid."),
|
||||||
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error.")
|
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error.")
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -3,12 +3,17 @@ package com.example.sns.model;
|
|||||||
import com.example.sns.model.entity.UserEntity;
|
import com.example.sns.model.entity.UserEntity;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class User {
|
public class User implements UserDetails {
|
||||||
|
|
||||||
private Integer id;
|
private Integer id;
|
||||||
private String username;
|
private String username;
|
||||||
@@ -29,4 +34,29 @@ public class User {
|
|||||||
entity.getDeletedAt()
|
entity.getDeletedAt()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return List.of(new SimpleGrantedAuthority(this.getUserRole().toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return this.deletedAt == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return this.deletedAt == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return this.deletedAt == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return this.deletedAt == null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,4 +45,12 @@ public class PostEntity {
|
|||||||
void updatedAt() {
|
void updatedAt() {
|
||||||
this.updatedAt = Timestamp.from(Instant.now());
|
this.updatedAt = Timestamp.from(Instant.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PostEntity of(String title, String body, UserEntity userEntity) {
|
||||||
|
PostEntity entity = new PostEntity();
|
||||||
|
entity.setTitle(title);
|
||||||
|
entity.setBody(body);
|
||||||
|
entity.setUser(userEntity);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ public class PostService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void create(String title, String body, String username) {
|
public void create(String title, String body, String username) {
|
||||||
|
|
||||||
// TODO user find
|
|
||||||
UserEntity userEntity = userEntityRepository.findByUsername(username)
|
UserEntity userEntity = userEntityRepository.findByUsername(username)
|
||||||
.orElseThrow(
|
.orElseThrow(
|
||||||
() -> new SnsApplicationException(
|
() -> new SnsApplicationException(
|
||||||
@@ -29,10 +27,7 @@ public class PostService {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO post save
|
postEntityRepository.save(PostEntity.of(title, body, userEntity));
|
||||||
postEntityRepository.save(new PostEntity());
|
|
||||||
|
|
||||||
// TODO return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ public class UserService {
|
|||||||
@Value("${jwt.token.expired-time-ms}")
|
@Value("${jwt.token.expired-time-ms}")
|
||||||
private Long expiredTimeMs;
|
private Long expiredTimeMs;
|
||||||
|
|
||||||
|
public User loadUserByUsername(String username) {
|
||||||
|
return userEntityRepository.findByUsername(username).map(User::fromEntity)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new SnsApplicationException(USER_NOT_FOUND, String.format("%s not founded", username))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public User join(String username, String password) {
|
public User join(String username, String password) {
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,21 @@ import java.util.Date;
|
|||||||
|
|
||||||
public class JwtTokenUtils {
|
public class JwtTokenUtils {
|
||||||
|
|
||||||
|
public static String getUsername(String token, String key) {
|
||||||
|
return extractClaims(token, key).get("username", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isExpired(String token, String key) {
|
||||||
|
Date expiredDate = extractClaims(token, key).getExpiration();
|
||||||
|
|
||||||
|
return expiredDate.before(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Claims extractClaims(String token, String key) {
|
||||||
|
return Jwts.parserBuilder().setSigningKey(getKey(key))
|
||||||
|
.build().parseClaimsJws(token).getBody();
|
||||||
|
}
|
||||||
|
|
||||||
public static String generateToken(String username, String key, long expiredTimeMs) {
|
public static String generateToken(String username, String key, long expiredTimeMs) {
|
||||||
Claims claims = Jwts.claims();
|
Claims claims = Jwts.claims();
|
||||||
claims.put("username", username);
|
claims.put("username", username);
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.example.sns.controller;
|
package com.example.sns.controller;
|
||||||
|
|
||||||
import com.example.sns.controller.request.PostCreateRequest;
|
import com.example.sns.controller.request.PostCreateRequest;
|
||||||
|
import com.example.sns.service.PostService;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.test.context.support.WithAnonymousUser;
|
import org.springframework.security.test.context.support.WithAnonymousUser;
|
||||||
import org.springframework.security.test.context.support.WithMockUser;
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
@@ -25,6 +27,9 @@ public class PostControllerTest {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private PostService postService;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@WithMockUser
|
@WithMockUser
|
||||||
void 포스트작성() throws Exception {
|
void 포스트작성() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user