Skip to content

Commit

Permalink
FEAT:: Add auth logic with redis
Browse files Browse the repository at this point in the history
  • Loading branch information
minwoo1999 committed May 12, 2024
1 parent d069275 commit 38177be
Show file tree
Hide file tree
Showing 11 changed files with 469 additions and 1 deletion.
1 change: 0 additions & 1 deletion src/main/java/com/example/rework/ReworkApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

@SpringBootApplication
public class ReworkApplication {

public static void main(String[] args) {
SpringApplication.run(ReworkApplication.class, args);
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/example/rework/auth/MemberRole.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.rework.auth;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum MemberRole {
MEMBER("ROLE_MEMBER"),MANAGER("ROLE_MANAGER"),ADMIN("ROLE_ADMIN");
private final String key;
}
38 changes: 38 additions & 0 deletions src/main/java/com/example/rework/auth/cookie/CookieUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.example.rework.auth.cookie;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class CookieUtil {
public static void addCookie(HttpServletResponse response, String name, String value, long maxAge) {
ResponseCookie cookie = ResponseCookie.from(name, value)
.path("/")
.sameSite("None") // None, Lax, Strict
.httpOnly(true)
// .secure(true) //TODO: secure true로 변경
.maxAge(maxAge)
.build();

response.addHeader("Set-Cookie", cookie.toString());
}

public static String getRefreshTokenCookie(HttpServletRequest request) {
jakarta.servlet.http.Cookie[] cookies = request.getCookies();

String cookieRefreshToken = "";
for (jakarta.servlet.http.Cookie cookie : cookies) {
if (cookie.getName().equals("refreshToken")) {
cookieRefreshToken = cookie.getValue();
}
}
return cookieRefreshToken;
}
public static void deleteRefreshTokenCookie(HttpServletRequest request, HttpServletResponse response) {
CookieUtil.addCookie(response,"refreshToken", null,0);
}

}
18 changes: 18 additions & 0 deletions src/main/java/com/example/rework/auth/entity/RefreshToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example.rework.auth.entity;

import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;


@RedisHash(value = "refreshToken", timeToLive = 60 * 60 * 24 * 14) // 2주 동안의 TTL 설정 (초 단위)
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class RefreshToken {
@Id
private String id; // member username
private String token;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.example.rework.auth.jwt;

import com.example.rework.auth.cookie.CookieUtil;
import com.example.rework.auth.service.RefreshTokenService;
import com.example.rework.member.application.dto.MemberResponseDto;
import com.example.rework.member.application.dto.MemeberRequestDto;
import com.fasterxml.jackson.databind.ObjectMapper;
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.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.io.IOException;

@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final RefreshTokenService refreshTokenService;
private final JwtProvider jwtProvider;

public JwtAuthenticationFilter(AuthenticationManager authenticationManager, RefreshTokenService refreshTokenService, JwtProvider jwtProvider) {
super(authenticationManager);
this.refreshTokenService = refreshTokenService;
this.jwtProvider = jwtProvider;
setFilterProcessesUrl("/api/v1/members/login");
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
log.info("JwtAuthenticationFilter.attemptAuthentication");

ObjectMapper objectMapper = new ObjectMapper();
MemeberRequestDto.MemberLoginRequestDto memberLoginRequestDto = null;
try {
memberLoginRequestDto = objectMapper.readValue(request.getInputStream(), MemeberRequestDto.MemberLoginRequestDto.class);
} catch (Exception e) {
// no login request dto
log.info("no login request dto");
throw new UsernameNotFoundException("계정이 존재하지 않습니다.");
}

UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(memberLoginRequestDto.getUserId(), memberLoginRequestDto.getPassword());
Authentication authentication = null;
authentication = this.getAuthenticationManager().authenticate(usernamePasswordAuthenticationToken);

return authentication;
}

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
log.info("JwtAuthenticationFilter.successfulAuthentication");

MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal();

String accessToken = jwtProvider.generateAccessToken(memberDetails.getUsername(),authentication);
String refreshToken = jwtProvider.generateRefreshToken(memberDetails.getUsername(),authentication);

refreshTokenService.setRefreshToken(memberDetails.getUsername(), refreshToken);

response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_OK);
CookieUtil.addCookie(response, "refreshToken", refreshToken, jwtProvider.REFRESH_TOKEN_EXPIRATION_TIME);

// token body comment
response.getWriter().write(
new ObjectMapper().writeValueAsString(
MemberResponseDto.MemberLoginResponseDto.builder()
.accessToken(accessToken)
.build()
)
);
}

@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
log.info("JwtAuthenticationFilter.unsuccessfulAuthentication");
throw new UsernameNotFoundException("계정이 존재하지 않습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.example.rework.auth.jwt;

import com.example.rework.member.application.MemberService;
import com.example.rework.member.domain.Member;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import java.io.IOException;

@Slf4j
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private final MemberService memberService;
private final JwtProvider jwtProvider;


public JwtAuthorizationFilter(AuthenticationManager authenticationManager, MemberService memberService, JwtProvider jwtProvider) {
super(authenticationManager);
this.memberService = memberService;
this.jwtProvider = jwtProvider;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("JwtAuthorizationFilter.doFilterInternal");
String path = request.getServletPath();
System.out.println("path = " + path);
String header = jwtProvider.getHeader(request);
if (header == null) {
chain.doFilter(request, response);
return;
}

String username = null;
username = jwtProvider.getUserId(request);
if (username != null) {
Member member = memberService.findMemberByUserId(username);
MemberDetails memberDetails = new MemberDetails(member);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(memberDetails, null, null);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}

}
127 changes: 127 additions & 0 deletions src/main/java/com/example/rework/auth/jwt/JwtProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.example.rework.auth.jwt;


import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Component
public class JwtProvider {

@Value("${jwt.secret-key}")
private String SECRET_KEY;

public final long ACCESS_TOKEN_EXPIRATION_TIME = 1000L * 60 * 60; //1 hours
public final long REFRESH_TOKEN_EXPIRATION_TIME = 1000L * 60 * 60 * 24 * 14; // 14 week

private final String HEADER_NAME = "Authorization";
private final String TOKEN_PREFIX = "Bearer ";

public String generateAccessToken(String username, Authentication authentication) {


List<String> roles = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
return JWT.create()
.withSubject(username)
.withExpiresAt(new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TIME))
.withClaim("username", username)
.withClaim("ROLE",roles)
.sign(Algorithm.HMAC512(SECRET_KEY));
}


public String generateRefreshToken(String username, Authentication authentication) {
List<String> roles = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());

return JWT.create()
.withSubject(username)
.withExpiresAt(new Date(System.currentTimeMillis() + REFRESH_TOKEN_EXPIRATION_TIME))
.withClaim("username", username)
.withClaim("ROLE",roles)
.sign(Algorithm.HMAC512(SECRET_KEY));
}

public boolean verifyToken(String token) {
try {
JWT.require(Algorithm.HMAC512(SECRET_KEY)).build().verify(token);
} catch (Exception e) {
return false;
}
return true;
}

public String getHeader(HttpServletRequest request) {
String header = request.getHeader(HEADER_NAME);
if (header != null && !header.startsWith(TOKEN_PREFIX)) header = null;
return header;
}

public String getUserId(HttpServletRequest request) {
String accessToken = getHeader(request).replace(TOKEN_PREFIX, "");
System.out.println("test=>"+accessToken);
return JWT.require(Algorithm.HMAC512(SECRET_KEY))
.build()
.verify(accessToken)
.getClaim("username").asString();
}
public String getUsernameFromToken(String token) {
return JWT.require(Algorithm.HMAC512(SECRET_KEY))
.build()
.verify(token)
.getClaim("username").asString();
}
public List<String> getRolesFromToken(String token) {
return JWT.require(Algorithm.HMAC512(SECRET_KEY))
.build()
.verify(token)
.getClaim("ROLE")
.asList(String.class);
}
public Authentication getAuthentication(String token) {
String username = getUsernameFromToken(token);
List<String> roles = getRolesFromToken(token);

List<SimpleGrantedAuthority> authorities = roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());

User principal = new User(username, "", authorities);

return new UsernamePasswordAuthenticationToken(principal, "", authorities);
}

public Authentication validateAndSetAuthentication(String refreshToken) {
Authentication authentication = this.getAuthentication(refreshToken);

if (authentication == null || !authentication.isAuthenticated()) {
throw new AuthenticationCredentialsNotFoundException("User not authenticated");
}

this.verifyToken(refreshToken);
return authentication;
}

public String renewRefreshToken(Authentication authentication) {
return this.generateRefreshToken(authentication.getName(), authentication);
}


}
Loading

0 comments on commit 38177be

Please sign in to comment.