Spring Security 적용 중간 커밋
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '2.6.6'
|
||||
id 'org.springframework.boot' version '2.4.2'
|
||||
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ext {
|
||||
set('springCloudVersion', "2021.0.1")
|
||||
set('springCloudVersion', "2020.0.5")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ext {
|
||||
set('springCloudVersion', "2021.0.1")
|
||||
set('springCloudVersion', "2020.0.5")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
ext {
|
||||
set('springCloudVersion', "2021.0.1")
|
||||
set('springCloudVersion', "2020.0.5")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
|
||||
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
|
||||
implementation 'io.jsonwebtoken:jjwt:0.9.1'
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.roy.springcloud.gateway.filter;
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
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;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {
|
||||
private final Environment environment;
|
||||
|
||||
public AuthorizationHeaderFilter(Environment environment) {
|
||||
super(Config.class);
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(AuthorizationHeaderFilter.Config config) {
|
||||
return (exchange, chain) -> {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
if (!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
|
||||
return onError(exchange, "No authorization header", HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
String authorizationHeader = request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0);
|
||||
String jwt = authorizationHeader.replace("Bearer", "");
|
||||
|
||||
if (!isJwtValid(jwt)) {
|
||||
return onError(exchange, "JWT token is not valid", HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
return chain.filter(exchange);
|
||||
};
|
||||
}
|
||||
|
||||
private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
response.setStatusCode(httpStatus);
|
||||
log.error(err);
|
||||
return response.setComplete();
|
||||
}
|
||||
|
||||
private boolean isJwtValid(String jwt) {
|
||||
boolean returnValue = true;
|
||||
|
||||
String subject = null;
|
||||
try {
|
||||
subject = Jwts.parser().setSigningKey(environment.getProperty("token.secret"))
|
||||
.parseClaimsJws(jwt).getBody()
|
||||
.getSubject();
|
||||
} catch (Exception ex) {
|
||||
returnValue = false;
|
||||
}
|
||||
if (Strings.isBlank(subject)) {
|
||||
returnValue = false;
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
public static class Config {}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Conf
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
log.info("Global Filter Message: {}", config.getMessage());
|
||||
if (config.isShowPreLogger()) {
|
||||
log.info("Global Filter Start: request id -> {}", request.getId());
|
||||
log.info("Global Filter Start: request uri -> {}", request.getLocalAddress());
|
||||
}
|
||||
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
|
||||
if (config.isShowPostLogger()) {
|
||||
|
||||
@@ -10,6 +10,9 @@ eureka:
|
||||
service-url:
|
||||
defaultZone: http://localhost:8761/eureka
|
||||
|
||||
token:
|
||||
secret: user_token
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: gateway-service
|
||||
@@ -22,10 +25,31 @@ spring:
|
||||
showPreLogger: true
|
||||
showPostLogger: true
|
||||
routes:
|
||||
- id: user-service
|
||||
uri: lb://USER-SERVICE
|
||||
predicates:
|
||||
- Path=/user-service/login
|
||||
- Method=POST
|
||||
filters:
|
||||
- RemoveRequestHeader=Cookie
|
||||
- RewritePath=/user-service/(?<segment>.*), /$\{segment}
|
||||
- id: user-service
|
||||
uri: lb://USER-SERVICE
|
||||
predicates:
|
||||
- Path=/user-service/users
|
||||
- Method=POST
|
||||
filters:
|
||||
- RemoveRequestHeader=Cookie
|
||||
- RewritePath=/user-service/(?<segment>.*), /$\{segment}
|
||||
- id: user-service
|
||||
uri: lb://USER-SERVICE
|
||||
predicates:
|
||||
- Path=/user-service/**
|
||||
- Method=GET
|
||||
filters:
|
||||
- RemoveRequestHeader=Cookie
|
||||
- RewritePath=/user-service/(?<segment>.*), /$\{segment}
|
||||
- AuthorizationHeaderFilter
|
||||
- id: catalog-service
|
||||
uri: lb://CATALOG-SERVICE
|
||||
predicates:
|
||||
@@ -34,25 +58,29 @@ spring:
|
||||
uri: lb://ORDER-SERVICE
|
||||
predicates:
|
||||
- Path=/order-service/**
|
||||
- id: test-server-1
|
||||
uri: lb://TEST-SERVER-1
|
||||
predicates:
|
||||
- Path=/test-server-1/**
|
||||
filters:
|
||||
- name: CustomFilter
|
||||
- name: LoggingFilter
|
||||
args:
|
||||
message: TEST-SERVER-1
|
||||
showPreLogger: true
|
||||
showPostLogger: true
|
||||
- id: test-server-2
|
||||
uri: lb://TEST-SERVER-2
|
||||
predicates:
|
||||
- Path=/test-server-2/**
|
||||
filters:
|
||||
- name: CustomFilter
|
||||
- name: LoggingFilter
|
||||
args:
|
||||
message: TEST-SERVER-2
|
||||
showPreLogger: true
|
||||
showPostLogger: true
|
||||
# - id: test-server-1
|
||||
# uri: lb://TEST-SERVER-1
|
||||
# predicates:
|
||||
# - Path=/test-server-1/**
|
||||
# filters:
|
||||
# - name: CustomFilter
|
||||
# - name: LoggingFilter
|
||||
# args:
|
||||
# message: TEST-SERVER-1
|
||||
# showPreLogger: true
|
||||
# showPostLogger: true
|
||||
# - id: test-server-2
|
||||
# uri: lb://TEST-SERVER-2
|
||||
# predicates:
|
||||
# - Path=/test-server-2/**
|
||||
# filters:
|
||||
# - name: CustomFilter
|
||||
# - name: LoggingFilter
|
||||
# args:
|
||||
# message: TEST-SERVER-2
|
||||
# showPreLogger: true
|
||||
# showPostLogger: true
|
||||
|
||||
logging:
|
||||
lelvel:
|
||||
com.roy.springcloud.gateway: DEBUG
|
||||
@@ -1,5 +1,5 @@
|
||||
ext {
|
||||
set('springCloudVersion', "2021.0.1")
|
||||
set('springCloudVersion', "2020.0.5")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ext {
|
||||
set('springCloudVersion', "2021.0.1")
|
||||
set('springCloudVersion', "2020.0.5")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ext {
|
||||
set('springCloudVersion', "2021.0.1")
|
||||
set('springCloudVersion', "2020.0.5")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ext {
|
||||
set('springCloudVersion', "2021.0.1")
|
||||
set('springCloudVersion', "2020.0.5")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -10,6 +10,7 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
|
||||
implementation 'io.jsonwebtoken:jjwt:0.9.1'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.List;
|
||||
import static com.roy.springcloud.util.mapper.MapperUtil.toObject;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/user-service")
|
||||
@RequestMapping("/")
|
||||
@RequiredArgsConstructor
|
||||
public class UserController {
|
||||
private final Environment environment;
|
||||
|
||||
@@ -7,5 +7,6 @@ import java.util.Optional;
|
||||
|
||||
public interface UserRepository extends CrudRepository<User, Long> {
|
||||
Optional<User> findByUserId(String userId);
|
||||
Optional<User> findByEmail(String email);
|
||||
Iterable<User> findAll();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.roy.springcloud.userservice.security;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.roy.springcloud.userservice.dto.UserDto;
|
||||
import com.roy.springcloud.userservice.service.UserService;
|
||||
import com.roy.springcloud.userservice.vo.request.LoginRequest;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.env.Environment;
|
||||
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.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.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
@SuppressWarnings("UastIncorrectHttpHeaderInspection")
|
||||
@Slf4j
|
||||
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
|
||||
private final UserService userService;
|
||||
private final Environment environment;
|
||||
|
||||
public AuthenticationFilter(AuthenticationManager authenticationManager,
|
||||
UserService userService,
|
||||
Environment environment) {
|
||||
super(authenticationManager);
|
||||
this.userService = userService;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
|
||||
try {
|
||||
LoginRequest loginRequest = new ObjectMapper().readValue(request.getInputStream(), LoginRequest.class);
|
||||
return getAuthenticationManager().authenticate(
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
loginRequest.getEmail(),
|
||||
loginRequest.getPassword(),
|
||||
Collections.emptyList()
|
||||
)
|
||||
);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
|
||||
String userName = ((User) authResult.getPrincipal()).getUsername();
|
||||
UserDto userDto = userService.getUserDetailsByEmail(userName);
|
||||
|
||||
String token = Jwts.builder()
|
||||
.setSubject(userDto.getUserId())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + Long.parseLong(Objects.requireNonNull(environment.getProperty("token.expiration_time")))))
|
||||
.signWith(SignatureAlgorithm.HS512, environment.getProperty("token.secret"))
|
||||
.compact();
|
||||
|
||||
response.addHeader("token", token);
|
||||
response.addHeader("userId", userDto.getUserId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,18 +1,45 @@
|
||||
package com.roy.springcloud.userservice.security;
|
||||
|
||||
import com.roy.springcloud.userservice.service.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
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.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class WebSecurity extends WebSecurityConfigurerAdapter {
|
||||
private final UserService userService;
|
||||
private final BCryptPasswordEncoder passwordEncoder;
|
||||
private final Environment environment;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable();
|
||||
http.authorizeHttpRequests().antMatchers("/users/**").permitAll();
|
||||
|
||||
http.authorizeRequests().antMatchers("/actuator/**").permitAll();
|
||||
http.authorizeRequests().antMatchers("/health-check/**").permitAll();
|
||||
http.authorizeRequests().antMatchers("/**")
|
||||
.hasIpAddress("192.168.0.2")
|
||||
.and()
|
||||
.addFilter(getAuthenticationFilter());
|
||||
|
||||
http.headers().frameOptions().disable();
|
||||
}
|
||||
|
||||
private AuthenticationFilter getAuthenticationFilter() throws Exception {
|
||||
AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager(), userService, environment);
|
||||
authenticationFilter.setAuthenticationManager(authenticationManager());
|
||||
return authenticationFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.roy.springcloud.userservice.service;
|
||||
|
||||
import com.roy.springcloud.userservice.dto.UserDto;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserService {
|
||||
public interface UserService extends UserDetailsService {
|
||||
void createUser(UserDto userDTO);
|
||||
UserDto getUserByUserId(String userId);
|
||||
UserDto getUserDetailsByEmail(String email);
|
||||
List<UserDto> getAllUser();
|
||||
}
|
||||
|
||||
@@ -5,11 +5,13 @@ import com.roy.springcloud.userservice.dto.UserDto;
|
||||
import com.roy.springcloud.userservice.repository.UserRepository;
|
||||
import com.roy.springcloud.userservice.service.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -36,6 +38,13 @@ public class UserServiceImpl implements UserService {
|
||||
return toObject(savedUser, UserDto.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDto getUserDetailsByEmail(String email) {
|
||||
User savedUser = userRepository.findByEmail(email)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
return toObject(savedUser, UserDto.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserDto> getAllUser() {
|
||||
Iterable<User> savedUsers = userRepository.findAll();
|
||||
@@ -45,4 +54,15 @@ public class UserServiceImpl implements UserService {
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
||||
User savedUser = userRepository.findByEmail(email)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
return new org.springframework.security.core.userdetails.User(
|
||||
savedUser.getEmail(), savedUser.getEncryptedPassword(),
|
||||
true, true, true, true,
|
||||
Collections.emptyList()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.roy.springcloud.userservice.vo.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
@Data
|
||||
public class LoginRequest {
|
||||
@Email
|
||||
@NotBlank(message = "Email cannot be blank")
|
||||
@Size(min = 2, message = "Email not be less than two characters")
|
||||
private String email;
|
||||
|
||||
@NotNull(message = "Password cannot be null")
|
||||
@Size(min = 8, message = "Password must be equal or grater than 8 characters and less than 16 characters")
|
||||
private String password;
|
||||
}
|
||||
@@ -23,5 +23,13 @@ eureka:
|
||||
service-url:
|
||||
defaultZone: http://localhost:8761/eureka
|
||||
|
||||
token:
|
||||
expiration_time: 864000000
|
||||
secret: user_token
|
||||
|
||||
greeting:
|
||||
message: Welcome to the Simple E-Commerce(User Service).
|
||||
message: Welcome to the Simple E-Commerce(User Service).
|
||||
|
||||
logging:
|
||||
lelvel:
|
||||
com.roy.springcloud.userservice: DEBUG
|
||||
Reference in New Issue
Block a user