Merge branch 'jwt' into feature/authentication
# Conflicts: # user-service/src/main/java/com/justpickup/userservice/domain/user/entity/User.java # user-service/src/main/java/com/justpickup/userservice/domain/user/service/UserService.java # user-service/src/main/java/com/justpickup/userservice/domain/user/service/UserServiceImpl.java
This commit is contained in:
@@ -30,6 +30,8 @@ dependencies {
|
||||
implementation 'io.jsonwebtoken:jjwt:0.9.1'
|
||||
// https://mvnrepository.com/artifact/io.netty/netty-resolver-dns-native-macos
|
||||
implementation 'io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-aarch_64'
|
||||
// https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api
|
||||
implementation 'javax.xml.bind:jaxb-api:2.3.1'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.justpickup.ownerapigatewayservice.filter;
|
||||
|
||||
import com.justpickup.ownerapigatewayservice.security.JwtTokenProvider;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.isExpired(token)) {
|
||||
return onError(exchange, "Access Token is Expired", HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
String subject = jwtTokenProvider.getUserId(token);
|
||||
if (subject == null) {
|
||||
return onError(exchange, "JWT token is not valid", HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
ServerHttpRequest request1 = request.mutate()
|
||||
.header("jwt-sub", subject)
|
||||
.build();
|
||||
|
||||
return chain.filter(exchange.mutate().request(request1).build());
|
||||
};
|
||||
}
|
||||
|
||||
// Mono(단일 값), Flux(다중 값) -> Spring WebFlux
|
||||
private Mono<Void> onError(ServerWebExchange exchange, String errorMsg, HttpStatus httpStatus) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
response.setStatusCode(httpStatus);
|
||||
|
||||
log.error(errorMsg);
|
||||
return response.setComplete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.justpickup.ownerapigatewayservice.security;
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@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 getUserId(String token) {
|
||||
return getClaimsFromJwtToken(token).getBody().getSubject();
|
||||
}
|
||||
|
||||
public boolean isExpired(String token) {
|
||||
try {
|
||||
return getClaimsFromJwtToken(token).getBody().getExpiration().before(new Date());
|
||||
} catch (ExpiredJwtException e) {
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Jws<Claims> getClaimsFromJwtToken(String token) {
|
||||
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
|
||||
}
|
||||
}
|
||||
@@ -33,9 +33,36 @@ spring:
|
||||
- Path=/store-service/**
|
||||
filters:
|
||||
- RewritePath=/store-service/(?<segment>.*),/$\{segment}
|
||||
- id: user-service
|
||||
uri: lb://USER-SERVICE
|
||||
predicates:
|
||||
- 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/**
|
||||
filters:
|
||||
- RewritePath=/user-service/(?<segment>.*),/$\{segment}
|
||||
- AuthorizationHeaderFilter
|
||||
- RewritePath=/user-service/(?<segment>.*),/$\{segment}
|
||||
|
||||
token:
|
||||
access-expired-time: 3600000
|
||||
refresh-expired-time: 604800000
|
||||
secret: my-secret
|
||||
@@ -1,8 +1,12 @@
|
||||
package com.justpickup.userservice;
|
||||
|
||||
import com.justpickup.userservice.domain.user.dto.StoreOwnerDto;
|
||||
import com.justpickup.userservice.domain.user.service.UserService;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableEurekaClient
|
||||
@@ -12,4 +16,13 @@ public class UserServiceApplication {
|
||||
SpringApplication.run(UserServiceApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
CommandLineRunner run(UserService userService) {
|
||||
return args -> {
|
||||
StoreOwnerDto park = StoreOwnerDto.builder()
|
||||
.email("test@gmail.com").password("1234").name("Park").phoneNumber("010-1234-5678")
|
||||
.build();
|
||||
userService.saveStoreOwner(park);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.justpickup.userservice.domain.jwt.exception;
|
||||
|
||||
import com.justpickup.userservice.global.exception.CustomException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class TokenRefreshException extends CustomException {
|
||||
|
||||
public TokenRefreshException(String message) {
|
||||
super(HttpStatus.FORBIDDEN, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.justpickup.userservice.domain.jwt.service;
|
||||
|
||||
import com.justpickup.userservice.domain.user.dto.JwtTokenDto;
|
||||
|
||||
public interface RefreshTokenService {
|
||||
void updateRefreshToken(Long id, String refreshToken);
|
||||
JwtTokenDto refreshJwtToken(String accessToken, String refreshToken);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.justpickup.userservice.domain.jwt.service;
|
||||
|
||||
import com.justpickup.userservice.domain.jwt.exception.TokenRefreshException;
|
||||
import com.justpickup.userservice.domain.user.dto.JwtTokenDto;
|
||||
import com.justpickup.userservice.domain.user.entity.User;
|
||||
import com.justpickup.userservice.domain.user.exception.NotExistUserException;
|
||||
import com.justpickup.userservice.domain.user.repository.UserRepository;
|
||||
import com.justpickup.userservice.domain.jwt.utils.JwtTokenProvider;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
@Slf4j
|
||||
public class RefreshTokenServiceImpl implements RefreshTokenService {
|
||||
private final UserRepository userRepository;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void updateRefreshToken(Long id, String refreshToken) {
|
||||
User user = userRepository.findById(id)
|
||||
.orElseThrow(() -> new NotExistUserException("사용자 고유번호 : " + id + "는 없는 사용자입니다."));
|
||||
|
||||
user.changeRefreshToken(refreshToken);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public JwtTokenDto refreshJwtToken(String accessToken, String refreshToken) {
|
||||
String userId = jwtTokenProvider.getUserId(accessToken);
|
||||
|
||||
User user = userRepository.findById(Long.valueOf(userId))
|
||||
.orElseThrow(() -> new NotExistUserException("사용자 고유번호 : " + userId + "는 없는 사용자입니다."));
|
||||
|
||||
// refresh token 검증
|
||||
if (!jwtTokenProvider.validateJwtToken(refreshToken)) {
|
||||
// 익셉션 발생 - 로그 아웃 후 로그인 페이지로 이동 처리
|
||||
user.deleteRefreshToken();
|
||||
throw new TokenRefreshException("Not validate jwt token = " + refreshToken);
|
||||
}
|
||||
|
||||
String userRefreshTokenId = user.getRefreshTokenId();
|
||||
if (!jwtTokenProvider.equalRefreshTokenId(userRefreshTokenId, refreshToken)) {
|
||||
// 익셉션 발생 - 로그인 아웃 후 로그인 페이지로 이동 처리
|
||||
user.deleteRefreshToken();
|
||||
throw new TokenRefreshException("Not equal jwt token! user = " + userRefreshTokenId +
|
||||
", refreshToken = " + refreshToken);
|
||||
}
|
||||
|
||||
Authentication authentication = jwtTokenProvider.getAuthentication(user.getEmail());
|
||||
List<String> roles = authentication.getAuthorities()
|
||||
.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
|
||||
|
||||
String newAccessToken = jwtTokenProvider.createJwtAccessToken(userId, "/refreshToken", roles);
|
||||
String newRefreshToken = jwtTokenProvider.createJwtRefreshToken();
|
||||
|
||||
user.changeRefreshToken(newRefreshToken);
|
||||
|
||||
return JwtTokenDto.builder()
|
||||
.accessToken(newAccessToken)
|
||||
.refreshToken(newRefreshToken)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.justpickup.userservice.domain.jwt.utils;
|
||||
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class JwtTokenProvider {
|
||||
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
@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 Authentication getAuthentication(String email) {
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
|
||||
return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
|
||||
}
|
||||
|
||||
public String getUserId(String token) {
|
||||
try {
|
||||
return getClaimsFromJwtToken(token).getBody().getSubject();
|
||||
} catch (ExpiredJwtException expiredJwtException) {
|
||||
return expiredJwtException.getClaims().getSubject();
|
||||
}
|
||||
}
|
||||
|
||||
private Jws<Claims> getClaimsFromJwtToken(String token) {
|
||||
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
|
||||
}
|
||||
|
||||
public String getRefreshTokenId(String token) {
|
||||
try {
|
||||
return getClaimsFromJwtToken(token).getBody().get("value").toString();
|
||||
} catch (ExpiredJwtException expiredJwtException) {
|
||||
return expiredJwtException.getClaims().get("value").toString();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.justpickup.userservice.domain.jwt.web;
|
||||
|
||||
import com.justpickup.userservice.domain.jwt.service.RefreshTokenServiceImpl;
|
||||
import com.justpickup.userservice.domain.user.dto.JwtTokenDto;
|
||||
import com.justpickup.userservice.global.dto.Result;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class AuthController {
|
||||
|
||||
private final RefreshTokenServiceImpl refreshTokenServiceImpl;
|
||||
|
||||
@GetMapping("/refreshToken")
|
||||
public ResponseEntity<Result> refreshToken(@RequestHeader("X-AUTH-TOKEN") String accessToken,
|
||||
@RequestHeader("REFRESH-TOKEN") String refreshToken) {
|
||||
|
||||
JwtTokenDto jwtTokenDto = refreshTokenServiceImpl.refreshJwtToken(accessToken, refreshToken);
|
||||
|
||||
return ResponseEntity.ok(Result.createSuccessResult(new RefreshTokenResponse(jwtTokenDto)));
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
static class RefreshTokenResponse {
|
||||
private String accessToken;
|
||||
private String refreshToken;
|
||||
|
||||
public RefreshTokenResponse(JwtTokenDto jwtTokenDto) {
|
||||
this.accessToken = jwtTokenDto.getAccessToken();
|
||||
this.refreshToken = jwtTokenDto.getRefreshToken();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public ResponseEntity<Result> logout(@RequestHeader("X-AUTH-TOKEN") String accessToken,
|
||||
@RequestHeader("REFRESH-TOKEN") String refreshToken) {
|
||||
log.info("########### logout!");
|
||||
// TODO: 2022/02/16 logout 구현 필요
|
||||
return ResponseEntity.ok(Result.createSuccessResult("success"));
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,9 @@ public class CustomerDto extends UserDto {
|
||||
}
|
||||
|
||||
@Builder
|
||||
public CustomerDto(Long id, String password, String name, String phoneNumber) {
|
||||
super(id, password, name, phoneNumber);
|
||||
public CustomerDto(Long id, String email, String password, String name,
|
||||
String phoneNumber, String dtype, String refreshTokenId) {
|
||||
super(id, email, password, name, phoneNumber, dtype, refreshTokenId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.justpickup.userservice.domain.user.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class JwtTokenDto {
|
||||
private String accessToken;
|
||||
private String refreshToken;
|
||||
|
||||
@Builder
|
||||
public JwtTokenDto(String accessToken, String refreshToken) {
|
||||
this.accessToken = accessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.justpickup.userservice.domain.user.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class StoreOwnerDto extends UserDto {
|
||||
private String businessNumber;
|
||||
|
||||
@Builder
|
||||
public StoreOwnerDto(Long id, String email, String password, String name,
|
||||
String phoneNumber, String dtype, String businessNumber, String refreshTokenId) {
|
||||
super(id, email, password, name, phoneNumber, dtype, refreshTokenId);
|
||||
this.businessNumber = businessNumber;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
package com.justpickup.userservice.domain.user.dto;
|
||||
|
||||
import com.justpickup.userservice.domain.user.entity.Customer;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class UserDto {
|
||||
public abstract class UserDto {
|
||||
private Long id;
|
||||
private String email;
|
||||
private String password;
|
||||
private String name;
|
||||
private String phoneNumber;
|
||||
private String dtype;
|
||||
private String refreshTokenId;
|
||||
|
||||
// == 생성 메소드 == //
|
||||
public UserDto(Customer customer) {
|
||||
@@ -19,10 +21,14 @@ public class UserDto {
|
||||
this.phoneNumber = customer.getPhoneNumber();
|
||||
}
|
||||
|
||||
public UserDto(Long id, String password, String name, String phoneNumber) {
|
||||
public UserDto(Long id, String email, String password, String name, String phoneNumber,
|
||||
String dtype, String refreshTokenId) {
|
||||
this.id = id;
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.name = name;
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.dtype = dtype;
|
||||
this.refreshTokenId = refreshTokenId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.justpickup.userservice.domain.user.entity;
|
||||
|
||||
import com.justpickup.userservice.domain.user.dto.StoreOwnerDto;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -12,4 +13,10 @@ import javax.persistence.Table;
|
||||
@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public class StoreOwner extends User {
|
||||
private String businessNumber;
|
||||
|
||||
public StoreOwner(String email, String password, String name, String phoneNumber,
|
||||
String businessNumber, String refreshTokenId) {
|
||||
super(email, password, name, phoneNumber, refreshTokenId);
|
||||
this.businessNumber = businessNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import javax.persistence.*;
|
||||
@Inheritance(strategy = InheritanceType.JOINED)
|
||||
@DiscriminatorColumn(name = "DTYPE")
|
||||
@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public abstract class User extends BaseEntity {
|
||||
public class User extends BaseEntity {
|
||||
|
||||
@Id @GeneratedValue
|
||||
@Column(name = "user_id")
|
||||
@@ -26,6 +26,8 @@ public abstract class User extends BaseEntity {
|
||||
|
||||
private String phoneNumber;
|
||||
|
||||
private String refreshTokenId;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private Role role;
|
||||
@@ -40,4 +42,20 @@ public abstract class User extends BaseEntity {
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public User(String email, String password, String name, String phoneNumber, String refreshTokenId) {
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.name = name;
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.refreshTokenId = refreshTokenId;
|
||||
}
|
||||
|
||||
public void changeRefreshToken(String refreshToken) {
|
||||
this.refreshTokenId = refreshToken;
|
||||
}
|
||||
|
||||
public void deleteRefreshToken() {
|
||||
this.refreshTokenId = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.justpickup.userservice.domain.user.repository;
|
||||
|
||||
import com.justpickup.userservice.domain.user.entity.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
Optional<User> findByEmail(String username);
|
||||
}
|
||||
@@ -5,8 +5,10 @@ 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> {
|
||||
CustomerDto findCustomerByUserId(Long userId);
|
||||
|
||||
StoreOwner saveStoreOwner(StoreOwnerDto storeOwnerDto);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,17 @@ package com.justpickup.userservice.domain.user.service;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.justpickup.userservice.domain.user.dto.CustomerDto;
|
||||
import com.justpickup.userservice.domain.user.dto.StoreOwnerDto;
|
||||
import com.justpickup.userservice.domain.user.dto.OAuthAttributeDto;
|
||||
import com.justpickup.userservice.domain.user.dto.OAuthAttributeDto;
|
||||
import com.justpickup.userservice.domain.user.entity.Customer;
|
||||
import com.justpickup.userservice.domain.user.entity.StoreOwner;
|
||||
import com.justpickup.userservice.domain.user.entity.User;
|
||||
import com.justpickup.userservice.domain.user.exception.NotExistUserException;
|
||||
import com.justpickup.userservice.domain.user.repository.CustomerRepository;
|
||||
import lombok.*;
|
||||
import com.justpickup.userservice.domain.user.repository.UserRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
@@ -16,6 +22,13 @@ 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.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.core.env.Environment;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -23,16 +36,36 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
@Slf4j
|
||||
public class UserServiceImpl implements UserService {
|
||||
public class UserServiceImpl implements UserService, UserDetailsService {
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
private final HttpServletResponse response;
|
||||
private final Environment env;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
User user = userRepository.findByEmail(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found in the database"));
|
||||
|
||||
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
|
||||
authorities.add(new SimpleGrantedAuthority(user.getDtype()));
|
||||
return new org.springframework.security.core.userdetails.User(user.getId().toString(), user.getPassword(), authorities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomerDto findCustomerByUserId(Long userId) {
|
||||
Customer customer = customerRepository.findById(userId)
|
||||
@@ -41,6 +74,17 @@ public class UserServiceImpl implements UserService {
|
||||
return new CustomerDto(customer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public StoreOwner saveStoreOwner(StoreOwnerDto storeOwnerDto) {
|
||||
String encode = passwordEncoder.encode(storeOwnerDto.getPassword());
|
||||
|
||||
StoreOwner storeOwner = new StoreOwner(storeOwnerDto.getEmail(), encode, storeOwnerDto.getName(),
|
||||
storeOwnerDto.getPhoneNumber(), storeOwnerDto.getBusinessNumber(), storeOwnerDto.getRefreshTokenId());
|
||||
|
||||
return userRepository.save(storeOwner);
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@@ -82,7 +126,7 @@ public class UserServiceImpl implements UserService {
|
||||
|
||||
Customer customer = customerRepository.save(
|
||||
customerRepository.findByEmail(attributeDto.getEmail())
|
||||
.orElse(attributeDto.toEntity(attributeDto))
|
||||
.orElse(attributeDto.toEntity(attributeDto))
|
||||
);
|
||||
|
||||
// TODO: 2022/02/16 Response에 token 담아 보내기
|
||||
@@ -104,9 +148,4 @@ public class UserServiceImpl implements UserService {
|
||||
this.email = user.getEmail();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.justpickup.userservice.global.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.justpickup.userservice.global.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class LoginRequest {
|
||||
private String name;
|
||||
private String email;
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.justpickup.userservice.global.security;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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
|
||||
public class HeaderAuthorizationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
if (request.getServletPath().equals("/login")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String email = request.getHeader("jwt-sub");
|
||||
log.info("email jwt-sub = {}", email);
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.justpickup.userservice.global.security;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.justpickup.userservice.domain.jwt.service.RefreshTokenServiceImpl;
|
||||
import com.justpickup.userservice.domain.jwt.utils.JwtTokenProvider;
|
||||
import com.justpickup.userservice.global.dto.LoginRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final RefreshTokenServiceImpl refreshTokenServiceImpl;
|
||||
|
||||
// login 리퀘스트 패스로 오는 요청을 판단
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
|
||||
Authentication authentication;
|
||||
|
||||
try {
|
||||
LoginRequest credential = new ObjectMapper().readValue(request.getInputStream(), LoginRequest.class);
|
||||
|
||||
authentication = authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(credential.getEmail(), credential.getPassword())
|
||||
);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return authentication;
|
||||
}
|
||||
|
||||
// 로그인 성공 이후 토큰 생성
|
||||
@Override
|
||||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
|
||||
org.springframework.security.core.userdetails.User user = (User) authResult.getPrincipal();
|
||||
|
||||
List<String> roles = user.getAuthorities()
|
||||
.stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
String userId = user.getUsername();
|
||||
|
||||
String accessToken = jwtTokenProvider.createJwtAccessToken(userId, request.getRequestURI(), roles);
|
||||
String refreshToken = jwtTokenProvider.createJwtRefreshToken();
|
||||
|
||||
refreshTokenServiceImpl.updateRefreshToken(Long.valueOf(userId), jwtTokenProvider.getRefreshTokenId(refreshToken));
|
||||
|
||||
Map<String, String> tokens = Map.of(
|
||||
"access_token", accessToken,
|
||||
"refresh_token", refreshToken
|
||||
);
|
||||
|
||||
response.setContentType(APPLICATION_JSON_VALUE);
|
||||
|
||||
new ObjectMapper().writeValue(response.getOutputStream(), tokens);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unsuccessfulAuthentication
|
||||
(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
|
||||
throws IOException, ServletException {
|
||||
log.warn("로그인 실패!!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.justpickup.userservice.global.security;
|
||||
|
||||
import com.justpickup.userservice.domain.jwt.service.RefreshTokenServiceImpl;
|
||||
import com.justpickup.userservice.domain.jwt.utils.JwtTokenProvider;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
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.WebSecurityConfigurerAdapter;
|
||||
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.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
private final UserDetailsService userDetailsService;
|
||||
private final BCryptPasswordEncoder bCryptPasswordEncoder;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final RefreshTokenServiceImpl refreshTokenServiceImpl;
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
LoginAuthenticationFilter loginAuthenticationFilter =
|
||||
new LoginAuthenticationFilter(authenticationManagerBean(), jwtTokenProvider, refreshTokenServiceImpl);
|
||||
loginAuthenticationFilter.setFilterProcessesUrl("/login");
|
||||
|
||||
http.csrf().disable();
|
||||
|
||||
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||
|
||||
http.authorizeRequests().anyRequest().permitAll();
|
||||
|
||||
http.logout()
|
||||
.logoutUrl("/logout")
|
||||
.deleteCookies("");
|
||||
|
||||
http.addFilter(loginAuthenticationFilter);
|
||||
http.addFilterBefore(new HeaderAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||
return super.authenticationManagerBean();
|
||||
}
|
||||
}
|
||||
@@ -43,4 +43,9 @@ logging:
|
||||
|
||||
# jpa query, parameter 로그 (p6spy)
|
||||
decorator.datasource.p6spy:
|
||||
enable-logging: true
|
||||
enable-logging: true
|
||||
|
||||
token:
|
||||
access-expired-time: 3600000
|
||||
refresh-expired-time: 604800000
|
||||
secret: my-secret
|
||||
Reference in New Issue
Block a user