feat(customer-apigateway-service, user-servcie): customer gateway filter 추가

- customer gateway service에 jwt header filter 추가
- user-service에서 OAuth
service refactor
This commit is contained in:
hoon7566
2022-02-18 15:10:50 +09:00
parent e0455da9a3
commit 32e93aaad3
11 changed files with 322 additions and 100 deletions

View File

@@ -0,0 +1,73 @@
package com.justpickup.customerapigatewayservice.filter;
import com.justpickup.customerapigatewayservice.security.JwtTokenProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {
private final JwtTokenProvider jwtTokenProvider;
@Autowired
public AuthorizationHeaderFilter(JwtTokenProvider jwtTokenProvider) {
super(Config.class);
this.jwtTokenProvider = jwtTokenProvider;
}
static class Config {
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
return onError(exchange, "No authorization header", HttpStatus.UNAUTHORIZED);
}
String authorizationHeader = headers.get(HttpHeaders.AUTHORIZATION).get(0);
// JWT 토큰 판별
String token = authorizationHeader.replace("Bearer", "");
if (!jwtTokenProvider.validateJwtToken(token)) {
return onError(exchange, "JWT token is not valid", HttpStatus.UNAUTHORIZED);
}
String subject = jwtTokenProvider.getUserId(token);
if (false == jwtTokenProvider.getRoles(token).contains("Customer")) {
return onError(exchange, "권한 없음", HttpStatus.UNAUTHORIZED);
}
ServerHttpRequest newRequest = request.mutate()
.header("user-id", subject)
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
};
}
// Mono(단일 값), Flux(다중 값) -> Spring WebFlux
private Mono<Void> onError(ServerWebExchange exchange, String errorMsg, HttpStatus httpStatus) {
log.error(errorMsg);
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(httpStatus);
return response.setComplete();
}
}

View File

@@ -0,0 +1,103 @@
package com.justpickup.customerapigatewayservice.security;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Component
@Slf4j
public class JwtTokenProvider {
@Value("${token.access-expired-time}")
private long ACCESS_EXPIRED_TIME;
@Value("${token.refresh-expired-time}")
private long REFRESH_EXPIRED_TIME;
@Value("${token.secret}")
private String SECRET;
public String createJwtAccessToken(String userId, String uri, List<String> roles) {
Claims claims = Jwts.claims().setSubject(userId);
claims.put("roles", roles);
return Jwts.builder()
.addClaims(claims)
.setExpiration(
new Date(System.currentTimeMillis() + ACCESS_EXPIRED_TIME)
)
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS512, SECRET)
.setIssuer(uri)
.compact();
}
public String createJwtRefreshToken() {
Claims claims = Jwts.claims();
claims.put("value", UUID.randomUUID());
return Jwts.builder()
.addClaims(claims)
.setExpiration(
new Date(System.currentTimeMillis() + REFRESH_EXPIRED_TIME)
)
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public String getUserId(String token) {
return getClaimsFromJwtToken(token).getSubject();
}
private Claims getClaimsFromJwtToken(String token) {
try {
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
} catch (ExpiredJwtException e) {
return e.getClaims();
}
}
public String getRefreshTokenId(String token) {
return getClaimsFromJwtToken(token).get("value").toString();
}
public List<String> getRoles(String token) {
return (List<String>) getClaimsFromJwtToken(token).get("roles");
}
public boolean validateJwtToken(String token) {
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
return true;
} catch (SignatureException e) {
log.error("Invalid JWT signature: {}", e.getMessage());
return false;
} catch (MalformedJwtException e) {
log.error("Invalid JWT token: {}", e.getMessage());
return false;
} catch (ExpiredJwtException e) {
log.error("JWT token is expired: {}", e.getMessage());
return false;
} catch (UnsupportedJwtException e) {
log.error("JWT token is unsupported: {}", e.getMessage());
return false;
} catch (IllegalArgumentException e) {
log.error("JWT claims string is empty: {}", e.getMessage());
return false;
}
}
public boolean equalRefreshTokenId(String refreshTokenId, String refreshToken) {
String compareToken = this.getRefreshTokenId(refreshToken);
return refreshTokenId.equals(compareToken);
}
}

View File

@@ -37,6 +37,47 @@ spring:
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user-service/**
- Path=/user-service/login
- Method=POST
filters:
- RewritePath=/user-service/(?<segment>.*),/$\{segment}
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user-service/refreshToken
- Method=GET
filters:
- RewritePath=/user-service/(?<segment>.*),/$\{segment}
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user-service/logout
- Method=POST
filters:
- RewritePath=/user-service/(?<segment>.*),/$\{segment}
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user-service/oauth2/authorization/*
filters:
- RewritePath=/user-service/(?<segment>.*),/$\{segment}
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user-service/*/oauth2/code/*
filters:
- RewritePath=/user-service/(?<segment>.*),/$\{segment}
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user-service/**
filters:
- AuthorizationHeaderFilter
- RewritePath=/user-service/(?<segment>.*),/$\{segment}
token:
access-expired-time: 3600000
refresh-expired-time: 604800000
secret: my-secret
refresh-token-name: refresh-token
access-token-name: access-token

View File

@@ -0,0 +1,94 @@
package com.justpickup.userservice.domain.jwt.service;
import com.justpickup.userservice.domain.user.dto.CustomerDto;
import com.justpickup.userservice.domain.user.dto.OAuthAttributeDto;
import com.justpickup.userservice.domain.user.entity.Customer;
import com.justpickup.userservice.domain.user.repository.CustomerRepository;
import com.justpickup.userservice.domain.user.repository.UserRepository;
import com.justpickup.userservice.domain.user.service.UserService;
import com.justpickup.userservice.domain.user.service.UserServiceImpl;
import com.justpickup.userservice.global.utils.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@RequiredArgsConstructor
@Service
public class OAuthService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final CustomerRepository customerRepository;
private final HttpServletResponse response;
private final HttpServletRequest request;
private final JwtTokenProvider jwtTokenProvider;
private final RefreshTokenService refreshTokenService;
private final UserServiceImpl userServiceImpl;
@Override
@Transactional
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService<OAuth2UserRequest,OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
//OAuth 서비스 id
String registrationId = userRequest.getClientRegistration().getRegistrationId();
//OAuth 로그인 진행시 키가 되는 필드값
String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails()
.getUserInfoEndpoint()
.getUserNameAttributeName();
// OAuth2UserService
OAuthAttributeDto attributeDto = OAuthAttributeDto.of(registrationId, userNameAttributeName,oAuth2User.getAttributes());
Customer customer = saveCustomer(attributeDto);
// TODO: 2022/02/16 Response에 token 담아 보내기
String userEmail = customer.getEmail();
Collection<? extends GrantedAuthority> authorities = userServiceImpl.loadUserByUsername(userEmail).getAuthorities();
List<String> roles = authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
String accessToken = jwtTokenProvider.createJwtAccessToken(userEmail, request.getRequestURI(), roles);
String refreshToken = jwtTokenProvider.createJwtRefreshToken();
refreshTokenService.updateRefreshToken(customer.getId(), jwtTokenProvider.getRefreshTokenId(refreshToken));
response.setHeader("Access-token",accessToken);
response.setHeader("refresh-token",refreshToken);
return new DefaultOAuth2User(
authorities
, attributeDto.getAttributes()
, attributeDto.getNameAttributeKey());
}
@Transactional
public Customer saveCustomer(OAuthAttributeDto attributeDto){
return customerRepository.save(
customerRepository.findByEmail(attributeDto.getEmail())
.orElse(attributeDto.toEntity(attributeDto))
);
}
}

View File

@@ -3,6 +3,7 @@ package com.justpickup.userservice.domain.jwt.service;
import com.justpickup.userservice.domain.user.dto.JwtTokenDto;
public interface RefreshTokenService {
void updateRefreshToken(Long id, String uuid);
JwtTokenDto refreshJwtToken(String accessToken, String refreshToken);
void logoutToken(String accessToken);
}

View File

@@ -16,7 +16,6 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

View File

@@ -17,7 +17,7 @@ public class Customer extends User {
private AuthType oauthType;
public Customer(String email, String password, String name, String phoneNumber, AuthType oauthType) {
super(email, password, name, phoneNumber,null);
super(email, password, name, phoneNumber);
this.dtype = Customer.class.getSimpleName();
this.oauthType = oauthType;
}

View File

@@ -26,9 +26,6 @@ public abstract class User extends BaseEntity {
private String phoneNumber;
@Enumerated(EnumType.STRING)
private Role role;
@Column(insertable = false,updatable = false)
protected String dtype;

View File

@@ -1,16 +1,13 @@
package com.justpickup.userservice.domain.user.service;
import com.justpickup.userservice.domain.user.dto.CustomerDto;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.user.OAuth2User;
import com.justpickup.userservice.domain.user.dto.StoreOwnerDto;
import com.justpickup.userservice.domain.user.entity.StoreOwner;
public interface UserService extends OAuth2UserService<OAuth2UserRequest, OAuth2User> {
public interface UserService {
void updateRefreshToken(Long id, String refreshToken);
CustomerDto findCustomerByUserId(Long userId);
StoreOwner saveStoreOwner(StoreOwnerDto storeOwnerDto);
}

View File

@@ -1,8 +1,6 @@
package com.justpickup.userservice.domain.user.service;
import com.justpickup.userservice.domain.jwt.utils.JwtTokenProvider;
import com.justpickup.userservice.domain.user.dto.CustomerDto;
import com.justpickup.userservice.domain.user.dto.OAuthAttributeDto;
import com.justpickup.userservice.domain.user.dto.StoreOwnerDto;
import com.justpickup.userservice.domain.user.entity.Customer;
import com.justpickup.userservice.domain.user.entity.StoreOwner;
@@ -12,28 +10,17 @@ import com.justpickup.userservice.domain.user.repository.CustomerRepository;
import com.justpickup.userservice.domain.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@@ -44,9 +31,6 @@ public class UserServiceImpl implements UserService, UserDetailsService {
private final CustomerRepository customerRepository;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final HttpServletResponse response;
private final HttpServletRequest request;
private final JwtTokenProvider jwtTokenProvider;
@@ -74,77 +58,8 @@ public class UserServiceImpl implements UserService, UserDetailsService {
String encode = passwordEncoder.encode(storeOwnerDto.getPassword());
StoreOwner storeOwner = new StoreOwner(storeOwnerDto.getEmail(), encode, storeOwnerDto.getName(),
storeOwnerDto.getPhoneNumber(), storeOwnerDto.getBusinessNumber(), storeOwnerDto.getRefreshTokenId());
storeOwnerDto.getPhoneNumber(), storeOwnerDto.getBusinessNumber());
return userRepository.save(storeOwner);
}
@Override
@Transactional
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService<OAuth2UserRequest,OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
//OAuth 서비스 id
String registrationId = userRequest.getClientRegistration().getRegistrationId();
//OAuth 로그인 진행시 키가 되는 필드값
String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails()
.getUserInfoEndpoint()
.getUserNameAttributeName();
// OAuth2UserService
OAuthAttributeDto attributeDto = OAuthAttributeDto.of(registrationId, userNameAttributeName,oAuth2User.getAttributes());
Customer customer = saveCustomer(attributeDto);
// TODO: 2022/02/16 Response에 token 담아 보내기
String userEmail = customer.getEmail();
Collection<? extends GrantedAuthority> authorities = loadUserByUsername(userEmail).getAuthorities();
List<String> roles = authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
String accessToken = jwtTokenProvider.createJwtAccessToken(userEmail, request.getRequestURI(), roles);
String refreshToken = jwtTokenProvider.createJwtRefreshToken();
updateRefreshToken(customer.getId(), jwtTokenProvider.getRefreshTokenId(refreshToken));
customer.changeRefreshToken(refreshToken);
response.setHeader("Access-token",accessToken);
response.setHeader("refresh-token",refreshToken);
return new DefaultOAuth2User(
authorities
, attributeDto.getAttributes()
, attributeDto.getNameAttributeKey());
}
@Transactional
public Customer saveCustomer(OAuthAttributeDto attributeDto){
return customerRepository.save(
customerRepository.findByEmail(attributeDto.getEmail())
.orElse(attributeDto.toEntity(attributeDto))
);
}
@Transactional
@Override
public void updateRefreshToken(Long id, String refreshToken) {
User user = userRepository.findById(id)
.orElseThrow(() -> new NotExistUserException("사용자 고유번호 : " + id + "는 없는 사용자입니다."));
user.changeRefreshToken(refreshToken);
}
}

View File

@@ -1,5 +1,6 @@
package com.justpickup.userservice.global.security;
import com.justpickup.userservice.domain.jwt.service.OAuthService;
import com.justpickup.userservice.domain.jwt.service.RefreshTokenServiceImpl;
import com.justpickup.userservice.global.utils.JwtTokenProvider;
import com.justpickup.userservice.domain.user.service.UserService;
@@ -14,6 +15,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@@ -26,7 +28,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final RefreshTokenServiceImpl refreshTokenServiceImpl;
private final CookieProvider cookieProvider;
private final UserService userService;
private final OAuthService oAuthService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
@@ -52,7 +54,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
http.oauth2Login()
.defaultSuccessUrl("http://just-pickup.com:8000/customer-frontend-service/")
.userInfoEndpoint()
.userService(userService);
.userService(oAuthService);
http.addFilter(loginAuthenticationFilter);
http.addFilterBefore(new HeaderAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);