-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from Team-Going/feature/3
[feat] Spring Security & JWT 설정
- Loading branch information
Showing
15 changed files
with
446 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
doorip-api/src/main/java/org/doorip/auth/ExceptionHandlerFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package org.doorip.auth; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.doorip.common.ApiResponse; | ||
import org.doorip.common.Constants; | ||
import org.doorip.exception.UnauthorizedException; | ||
import org.doorip.message.ErrorMessage; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import java.io.IOException; | ||
import java.io.PrintWriter; | ||
|
||
@Slf4j | ||
public class ExceptionHandlerFilter extends OncePerRequestFilter { | ||
private final ObjectMapper objectMapper = new ObjectMapper(); | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException { | ||
try { | ||
filterChain.doFilter(request, response); | ||
} catch (UnauthorizedException e) { | ||
handleUnauthorizedException(response, e); | ||
} catch (Exception ee) { | ||
handleException(response, ee); | ||
} | ||
} | ||
|
||
private void handleUnauthorizedException(HttpServletResponse response, Exception e) throws IOException { | ||
UnauthorizedException ue = (UnauthorizedException) e; | ||
ErrorMessage errorMessage = ue.getErrorMessage(); | ||
HttpStatus httpStatus = errorMessage.getHttpStatus(); | ||
setResponse(response, httpStatus, errorMessage); | ||
} | ||
|
||
private void handleException(HttpServletResponse response, Exception e) throws IOException { | ||
log.error(">>> Exception Handler Filter : ", e); | ||
setResponse(response, HttpStatus.INTERNAL_SERVER_ERROR, ErrorMessage.INTERNAL_SERVER_ERROR); | ||
} | ||
|
||
private void setResponse(HttpServletResponse response, HttpStatus httpStatus, ErrorMessage errorMessage) throws IOException { | ||
response.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||
response.setCharacterEncoding(Constants.CHARACTER_TYPE); | ||
response.setStatus(httpStatus.value()); | ||
PrintWriter writer = response.getWriter(); | ||
writer.write(objectMapper.writeValueAsString(ApiResponse.of(errorMessage))); | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
doorip-api/src/main/java/org/doorip/auth/JwtAuthenticationEntryPoint.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package org.doorip.auth; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.doorip.common.ApiResponse; | ||
import org.doorip.common.Constants; | ||
import org.doorip.message.ErrorMessage; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.security.core.AuthenticationException; | ||
import org.springframework.security.web.AuthenticationEntryPoint; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.io.IOException; | ||
import java.io.PrintWriter; | ||
|
||
@Slf4j | ||
@Component | ||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { | ||
private final ObjectMapper objectMapper = new ObjectMapper(); | ||
|
||
@Override | ||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { | ||
handleException(response); | ||
} | ||
|
||
private void handleException(HttpServletResponse response) throws IOException { | ||
setResponse(response, HttpStatus.UNAUTHORIZED, ErrorMessage.UNAUTHORIZED); | ||
} | ||
|
||
private void setResponse(HttpServletResponse response, HttpStatus httpStatus, ErrorMessage errorMessage) throws IOException { | ||
response.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||
response.setCharacterEncoding(Constants.CHARACTER_TYPE); | ||
response.setStatus(httpStatus.value()); | ||
PrintWriter writer = response.getWriter(); | ||
writer.write(objectMapper.writeValueAsString(ApiResponse.of(errorMessage))); | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
doorip-api/src/main/java/org/doorip/auth/JwtAuthenticationFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package org.doorip.auth; | ||
|
||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import org.doorip.auth.jwt.JwtProvider; | ||
import org.doorip.auth.jwt.JwtValidator; | ||
import org.doorip.common.Constants; | ||
import org.doorip.exception.UnauthorizedException; | ||
import org.doorip.message.ErrorMessage; | ||
import org.springframework.security.core.context.SecurityContext; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.web.authentication.WebAuthenticationDetails; | ||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||
import org.springframework.util.StringUtils; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import java.io.IOException; | ||
|
||
import static org.doorip.auth.UserAuthentication.createUserAuthentication; | ||
|
||
@RequiredArgsConstructor | ||
public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||
private final JwtValidator jwtValidator; | ||
private final JwtProvider jwtProvider; | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { | ||
final String accessToken = getAccessToken(request); | ||
jwtValidator.validateAccessToken(accessToken); | ||
doAuthentication(request, jwtProvider.getSubject(accessToken)); | ||
filterChain.doFilter(request, response); | ||
} | ||
|
||
private String getAccessToken(HttpServletRequest request) { | ||
String accessToken = request.getHeader(Constants.AUTHORIZATION); | ||
if (StringUtils.hasText(accessToken) && accessToken.startsWith(Constants.BEARER)) { | ||
return accessToken.substring(Constants.BEARER.length()); | ||
} | ||
throw new UnauthorizedException(ErrorMessage.INVALID_ACCESS_TOKEN); | ||
} | ||
|
||
private void doAuthentication(HttpServletRequest request, Long userId) { | ||
UserAuthentication authentication = createUserAuthentication(userId); | ||
createAndSetWebAuthenticationDetails(request, authentication); | ||
SecurityContext securityContext = SecurityContextHolder.getContext(); | ||
securityContext.setAuthentication(authentication); | ||
} | ||
|
||
private void createAndSetWebAuthenticationDetails(HttpServletRequest request, UserAuthentication authentication) { | ||
WebAuthenticationDetailsSource webAuthenticationDetailsSource = new WebAuthenticationDetailsSource(); | ||
WebAuthenticationDetails webAuthenticationDetails = webAuthenticationDetailsSource.buildDetails(request); | ||
authentication.setDetails(webAuthenticationDetails); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
doorip-api/src/main/java/org/doorip/auth/UserAuthentication.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package org.doorip.auth; | ||
|
||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.GrantedAuthority; | ||
|
||
import java.util.Collection; | ||
|
||
public class UserAuthentication extends UsernamePasswordAuthenticationToken { | ||
private UserAuthentication(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { | ||
super(principal, credentials, authorities); | ||
} | ||
|
||
public static UserAuthentication createUserAuthentication(Long userId) { | ||
return new UserAuthentication(userId, null, null); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package org.doorip.auth; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Target(ElementType.PARAMETER) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface UserId { | ||
} |
26 changes: 26 additions & 0 deletions
26
doorip-api/src/main/java/org/doorip/auth/UserIdArgumentResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package org.doorip.auth; | ||
|
||
import org.springframework.core.MethodParameter; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.bind.support.WebDataBinderFactory; | ||
import org.springframework.web.context.request.NativeWebRequest; | ||
import org.springframework.web.method.support.HandlerMethodArgumentResolver; | ||
import org.springframework.web.method.support.ModelAndViewContainer; | ||
|
||
@Component | ||
public class UserIdArgumentResolver implements HandlerMethodArgumentResolver { | ||
@Override | ||
public boolean supportsParameter(MethodParameter parameter) { | ||
boolean hasUserIdAnnotation = parameter.hasParameterAnnotation(UserId.class); | ||
boolean isLongType = Long.class.isAssignableFrom(parameter.getParameterType()); | ||
return hasUserIdAnnotation && isLongType; | ||
} | ||
|
||
@Override | ||
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { | ||
return SecurityContextHolder.getContext() | ||
.getAuthentication() | ||
.getPrincipal(); | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
doorip-api/src/main/java/org/doorip/auth/config/SecurityConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package org.doorip.auth.config; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.doorip.auth.ExceptionHandlerFilter; | ||
import org.doorip.auth.JwtAuthenticationEntryPoint; | ||
import org.doorip.auth.JwtAuthenticationFilter; | ||
import org.doorip.auth.jwt.JwtProvider; | ||
import org.doorip.auth.jwt.JwtValidator; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
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.WebSecurityCustomizer; | ||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; | ||
import org.springframework.security.config.http.SessionCreationPolicy; | ||
import org.springframework.security.web.SecurityFilterChain; | ||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
|
||
@RequiredArgsConstructor | ||
@EnableWebSecurity | ||
@Configuration | ||
public class SecurityConfig { | ||
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; | ||
private final JwtValidator jwtValidator; | ||
private final JwtProvider jwtProvider; | ||
|
||
private static final String[] whiteList = {"/api/users/signin", "/api/users/signup", "/api/users/reissue", "/"}; | ||
|
||
@Bean | ||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { | ||
return http | ||
.csrf(AbstractHttpConfigurer::disable) | ||
.formLogin(AbstractHttpConfigurer::disable) | ||
.httpBasic(AbstractHttpConfigurer::disable) | ||
.sessionManagement(sessionManagementConfigurer -> | ||
sessionManagementConfigurer | ||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) | ||
.exceptionHandling(exceptionHandlingConfigurer -> | ||
exceptionHandlingConfigurer | ||
.authenticationEntryPoint(jwtAuthenticationEntryPoint)) | ||
.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> | ||
authorizationManagerRequestMatcherRegistry | ||
.anyRequest() | ||
.authenticated()) | ||
.addFilterBefore(new JwtAuthenticationFilter(jwtValidator, jwtProvider), UsernamePasswordAuthenticationFilter.class) | ||
.addFilterBefore(new ExceptionHandlerFilter(), JwtAuthenticationFilter.class) | ||
.build(); | ||
} | ||
|
||
@Bean | ||
public WebSecurityCustomizer webSecurityCustomizer() { | ||
return web -> web.ignoring().requestMatchers(whiteList); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
doorip-api/src/main/java/org/doorip/auth/config/WebConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package org.doorip.auth.config; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.doorip.auth.UserIdArgumentResolver; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.web.method.support.HandlerMethodArgumentResolver; | ||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||
|
||
import java.util.List; | ||
|
||
@RequiredArgsConstructor | ||
@Configuration | ||
public class WebConfig implements WebMvcConfigurer { | ||
private final UserIdArgumentResolver userIdArgumentResolver; | ||
|
||
@Override | ||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { | ||
resolvers.add(userIdArgumentResolver); | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
doorip-api/src/main/java/org/doorip/auth/jwt/JwtGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package org.doorip.auth.jwt; | ||
|
||
import io.jsonwebtoken.Header; | ||
import io.jsonwebtoken.JwtParser; | ||
import io.jsonwebtoken.Jwts; | ||
import io.jsonwebtoken.SignatureAlgorithm; | ||
import io.jsonwebtoken.security.Keys; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.security.Key; | ||
import java.util.Base64; | ||
import java.util.Date; | ||
|
||
@Component | ||
public class JwtGenerator { | ||
@Value("${jwt.secret}") | ||
private String secretKey; | ||
@Value("${jwt.access-token-expire-time}") | ||
private long ACCESS_TOKEN_EXPIRE_TIME; | ||
@Value("${jwt.refresh-token-expire-time}") | ||
private long REFRESH_TOKEN_EXPIRE_TIME; | ||
|
||
public String generateToken(Long userId, boolean isAccessToken) { | ||
final Date now = generateNowDate(); | ||
final Date expiration = generateExpirationDate(isAccessToken, now); | ||
return Jwts.builder() | ||
.setHeaderParam(Header.TYPE, Header.JWT_TYPE) | ||
.setSubject(String.valueOf(userId)) | ||
.setIssuedAt(now) | ||
.setExpiration(expiration) | ||
.signWith(getSigningKey(), SignatureAlgorithm.HS256) | ||
.compact(); | ||
} | ||
|
||
public JwtParser getJwtParser() { | ||
return Jwts.parserBuilder() | ||
.setSigningKey(getSigningKey()) | ||
.build(); | ||
} | ||
|
||
private Date generateNowDate() { | ||
return new Date(); | ||
} | ||
|
||
private Date generateExpirationDate(boolean isAccessToken, Date now) { | ||
return new Date(now.getTime() + calculateExpirationTime(isAccessToken)); | ||
} | ||
|
||
private Key getSigningKey() { | ||
return Keys.hmacShaKeyFor(encodeSecretKey().getBytes()); | ||
} | ||
|
||
private long calculateExpirationTime(boolean isAccessToken) { | ||
if (isAccessToken) { | ||
return ACCESS_TOKEN_EXPIRE_TIME; | ||
} | ||
return REFRESH_TOKEN_EXPIRE_TIME; | ||
} | ||
|
||
private String encodeSecretKey() { | ||
return Base64.getEncoder() | ||
.encodeToString(secretKey.getBytes()); | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
doorip-api/src/main/java/org/doorip/auth/jwt/JwtProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package org.doorip.auth.jwt; | ||
|
||
import io.jsonwebtoken.JwtParser; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Component; | ||
|
||
@RequiredArgsConstructor | ||
@Component | ||
public class JwtProvider { | ||
private final JwtGenerator jwtGenerator; | ||
|
||
public Token issueToken(Long userId) { | ||
return Token.of(jwtGenerator.generateToken(userId, true), | ||
jwtGenerator.generateToken(userId, false)); | ||
} | ||
|
||
public Long getSubject(String token) { | ||
JwtParser jwtParser = jwtGenerator.getJwtParser(); | ||
return Long.valueOf(jwtParser.parseClaimsJws(token) | ||
.getBody() | ||
.getSubject()); | ||
} | ||
} |
Oops, something went wrong.