-
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.
- Loading branch information
Showing
19 changed files
with
491 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
# auth | ||
layer-api/src/main/resources/application-auth.properties | ||
|
||
HELP.md | ||
.gradle | ||
build/ | ||
|
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
28 changes: 28 additions & 0 deletions
28
layer-api/src/main/java/org/layer/auth/api/AuthController.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,28 @@ | ||
package org.layer.auth.api; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.layer.auth.jwt.JwtToken; | ||
import org.layer.auth.service.JwtService; | ||
import org.layer.member.MemberRole; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RequiredArgsConstructor | ||
@RestController | ||
public class AuthController { | ||
private final JwtService jwtService; | ||
|
||
// 테스트용 임시 컨트롤러입니다. (토큰 없이 접속 가능) | ||
// "/create-token?id=멤버아이디" uri로 get 요청을 보내면 토큰이 발급됩니다. | ||
@GetMapping("/create-token") | ||
public JwtToken authTest(@RequestParam("id") Long memberId) { | ||
return jwtService.issueToken(memberId, MemberRole.USER); | ||
} | ||
|
||
// header에 액세스 토큰을 넣어 요청을 보내면 인증됩니다. | ||
@GetMapping("/authentication-test") | ||
public String authTest() { | ||
return "인증 성공"; | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
layer-api/src/main/java/org/layer/auth/exception/TokenException.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,5 @@ | ||
package org.layer.auth.exception; | ||
|
||
// 임시로 만들어놓은 Exception입니다 | ||
public class TokenException extends RuntimeException { | ||
} |
58 changes: 58 additions & 0 deletions
58
layer-api/src/main/java/org/layer/auth/jwt/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,58 @@ | ||
package org.layer.auth.jwt; | ||
|
||
import com.nimbusds.oauth2.sdk.Role; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.layer.member.Member; | ||
import org.layer.member.MemberRole; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.util.StringUtils; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import java.io.IOException; | ||
import java.util.LinkedHashMap; | ||
import java.util.List; | ||
|
||
@Slf4j | ||
@RequiredArgsConstructor | ||
@Component | ||
public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||
private final JwtProvider jwtProvider; | ||
private final JwtValidator jwtValidator; | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { | ||
String accessToken = getJwtFromRequest(request); | ||
|
||
if(isValidToken(accessToken)) { | ||
Long memberId = jwtValidator.getMemberIdFromToken(accessToken); | ||
List<String> role = jwtValidator.getRoleFromToken(accessToken); | ||
setAuthenticationToContext(memberId, MemberRole.valueOf(role.get(0))); | ||
} | ||
filterChain.doFilter(request, response); | ||
} | ||
|
||
// Spring Security Context에 저장 | ||
private void setAuthenticationToContext(Long memberId, MemberRole memberRole) { | ||
Authentication authentication = MemberAuthentication.create(memberId, memberRole); | ||
SecurityContextHolder.getContext().setAuthentication(authentication); | ||
} | ||
|
||
// 요청 헤더에서 액세스 토큰을 가져오는 메서드 | ||
private String getJwtFromRequest(HttpServletRequest request) { | ||
String bearerToken = request.getHeader("Authorization"); | ||
return (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) ? bearerToken.replace("Bearer ", ""): null; | ||
} | ||
|
||
// 정상적인 토큰인지 판단하는 메서드 | ||
private boolean isValidToken(String token) { | ||
return StringUtils.hasText(token) && jwtValidator.validateToken(token) == JwtValidationType.VALID_JWT; | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
layer-api/src/main/java/org/layer/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,31 @@ | ||
package org.layer.auth.jwt; | ||
|
||
import io.jsonwebtoken.Jwts; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.Date; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
@RequiredArgsConstructor | ||
@Component | ||
public class JwtProvider { | ||
private final SecretKeyFactory secretKeyFactory; | ||
|
||
public String createToken(Authentication authentication, Long tokenExpirationTime) { | ||
Map<String, Object> claims = new HashMap<>(); | ||
claims.put("memberId", authentication.getPrincipal()); | ||
claims.put("role", authentication.getAuthorities()); | ||
|
||
Date now = new Date(); | ||
|
||
return Jwts.builder() | ||
.issuedAt(now) | ||
.expiration(new Date(now.getTime() + tokenExpirationTime)) | ||
.claims(claims) | ||
.signWith(secretKeyFactory.createSecretKey()) | ||
.compact(); | ||
} | ||
} |
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.layer.auth.jwt; | ||
|
||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
public class JwtToken { | ||
private final String accessToken; | ||
private final String refreshToken; | ||
|
||
@Builder | ||
public JwtToken(String accessToken, String refreshToken) { | ||
this.accessToken = accessToken; | ||
this.refreshToken = refreshToken; | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
layer-api/src/main/java/org/layer/auth/jwt/JwtValidationType.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,7 @@ | ||
package org.layer.auth.jwt; | ||
|
||
public enum JwtValidationType { | ||
VALID_JWT, | ||
INVALID_JWT; | ||
} | ||
|
48 changes: 48 additions & 0 deletions
48
layer-api/src/main/java/org/layer/auth/jwt/JwtValidator.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,48 @@ | ||
package org.layer.auth.jwt; | ||
|
||
import io.jsonwebtoken.Claims; | ||
import io.jsonwebtoken.Jwts; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.layer.auth.exception.TokenException; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.LinkedHashMap; | ||
import java.util.List; | ||
|
||
import static org.layer.auth.jwt.JwtValidationType.*; | ||
|
||
@Slf4j | ||
@RequiredArgsConstructor | ||
@Component | ||
public class JwtValidator { | ||
private final SecretKeyFactory secretKeyFactory; | ||
|
||
public JwtValidationType validateToken(String token) { | ||
try { | ||
getClaims(token); | ||
return VALID_JWT; | ||
} catch(TokenException e) { | ||
return INVALID_JWT; | ||
} | ||
} | ||
|
||
public long getMemberIdFromToken(String token) { | ||
Claims claims = getClaims(token); | ||
return Long.parseLong(claims.get("memberId").toString()); | ||
} | ||
|
||
public List<String> getRoleFromToken(String token) throws TokenException { | ||
Claims claims = getClaims(token); | ||
return (List<String>) (claims.get("role")); | ||
|
||
} | ||
|
||
private Claims getClaims(String token) { | ||
return Jwts.parser() | ||
.verifyWith(secretKeyFactory.createSecretKey()) | ||
.build() | ||
.parseSignedClaims(token) | ||
.getPayload(); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
layer-api/src/main/java/org/layer/auth/jwt/MemberAuthentication.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.layer.auth.jwt; | ||
|
||
import lombok.Builder; | ||
import lombok.RequiredArgsConstructor; | ||
import org.layer.member.MemberRole; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.GrantedAuthority; | ||
|
||
import java.util.Collection; | ||
import java.util.Collections; | ||
|
||
public class MemberAuthentication extends UsernamePasswordAuthenticationToken { | ||
@Builder | ||
public MemberAuthentication(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { | ||
super(principal, credentials, authorities); | ||
} | ||
|
||
public static MemberAuthentication create(Object principal, MemberRole role) { | ||
return MemberAuthentication.builder() | ||
.principal(principal) | ||
.credentials(null) | ||
.authorities(Collections.singleton(role)) | ||
.build(); | ||
} | ||
|
||
} |
18 changes: 18 additions & 0 deletions
18
layer-api/src/main/java/org/layer/auth/jwt/RefreshToken.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,18 @@ | ||
package org.layer.auth.jwt; | ||
|
||
import lombok.Builder; | ||
import lombok.Getter; | ||
import org.springframework.data.annotation.Id; | ||
import org.springframework.data.redis.core.RedisHash; | ||
import org.springframework.data.redis.core.TimeToLive; | ||
|
||
@Builder | ||
@Getter | ||
@RedisHash(value = "refreshToken") | ||
public class RefreshToken { | ||
@Id | ||
private Long memberId; | ||
private String token; | ||
@TimeToLive | ||
private long ttl; | ||
} |
25 changes: 25 additions & 0 deletions
25
layer-api/src/main/java/org/layer/auth/jwt/SecretKeyFactory.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,25 @@ | ||
package org.layer.auth.jwt; | ||
|
||
import io.jsonwebtoken.security.Keys; | ||
import java.util.Base64; | ||
import javax.crypto.SecretKey; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.layer.config.AuthValueConfig; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.PropertySource; | ||
import org.springframework.stereotype.Component; | ||
|
||
import static java.util.Base64.getEncoder; | ||
|
||
@RequiredArgsConstructor | ||
@Component | ||
public class SecretKeyFactory { | ||
private final AuthValueConfig authValueConfig; | ||
|
||
SecretKey createSecretKey() { | ||
String encodedKey = getEncoder().encodeToString(authValueConfig.getJWT_SECRET().getBytes()); | ||
return Keys.hmacShaKeyFor(encodedKey.getBytes()); | ||
} | ||
|
||
} |
53 changes: 53 additions & 0 deletions
53
layer-api/src/main/java/org/layer/auth/service/JwtService.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.layer.auth.service; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.layer.auth.exception.TokenException; | ||
import org.layer.auth.jwt.*; | ||
import org.layer.config.AuthValueConfig; | ||
import org.layer.member.MemberRole; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.time.Duration; | ||
import java.util.Objects; | ||
|
||
import static org.layer.config.AuthValueConfig.*; | ||
|
||
@RequiredArgsConstructor | ||
@Service | ||
public class JwtService { | ||
private final JwtProvider jwtProvider; | ||
private final RedisTemplate<String, Object> redisTemplate; | ||
|
||
public JwtToken issueToken(Long memberId, MemberRole memberRole) { | ||
String accessToken = jwtProvider.createToken(MemberAuthentication.create(memberId, memberRole), ACCESS_TOKEN_EXPIRATION_TIME); | ||
String refreshToken = jwtProvider.createToken(MemberAuthentication.create(memberId, memberRole), REFRESH_TOKEN_EXPIRATION_TIME); | ||
|
||
saveRefreshTokenToRedis(memberId, memberRole, refreshToken); | ||
|
||
return JwtToken.builder() | ||
.accessToken(accessToken) | ||
.refreshToken(refreshToken) | ||
.build(); | ||
} | ||
|
||
private void saveRefreshTokenToRedis(Long memberId, MemberRole memberRole, String refreshToken) { | ||
redisTemplate.opsForValue().set(refreshToken, memberId, Duration.ofDays(14)); | ||
} | ||
|
||
private Long getMemberIdFromRefreshToken(String refreshToken) throws TokenException { | ||
Long memberId = null; | ||
try { | ||
memberId = Long.parseLong((String) Objects.requireNonNull(redisTemplate.opsForValue().get(refreshToken))); | ||
} catch(Exception e) { | ||
throw new TokenException(); | ||
} | ||
return memberId; | ||
} | ||
|
||
public void deleteRefreshToken(String refreshToken) { | ||
redisTemplate.delete(refreshToken); | ||
} | ||
|
||
} |
27 changes: 27 additions & 0 deletions
27
layer-api/src/main/java/org/layer/config/AuthValueConfig.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,27 @@ | ||
package org.layer.config; | ||
|
||
import jakarta.annotation.PostConstruct; | ||
import lombok.Getter; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.context.annotation.PropertySource; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.util.Base64; | ||
|
||
@Getter | ||
@PropertySource("classpath:application-auth.properties") | ||
@Configuration | ||
public class AuthValueConfig { | ||
@Value("${jwt.secret}") | ||
private String JWT_SECRET; | ||
|
||
public static final Long ACCESS_TOKEN_EXPIRATION_TIME = 1000 * 60 * 30L; // 30분 | ||
public static final Long REFRESH_TOKEN_EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 14L; // 2주 | ||
|
||
|
||
@PostConstruct | ||
protected void init() { | ||
JWT_SECRET = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes(StandardCharsets.UTF_8)); | ||
} | ||
} |
Oops, something went wrong.