apigateway ๊ตฌ์ฑ
application.yml์ ๋ง์ดํฌ๋ก์๋น์ค ์ ๋ณด ์ถ๊ฐ
- 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
- user-service์ login๊ณผ users(ํ์๊ฐ์ ) ๊ธฐ๋ฅ์ ์ฟ ํค ์ญ์ ํ 'AuthorizationHeaderFilter'๋ฅผ ์ ์ฉํ์ง ์์. (๊ถํ์ฒดํฌ๊ฐ ์๋ค๋ ๋ป)
- ๋๋จธ์ง GET๋ฉ์๋์ ๊ดํด์๋ 'AuthorizationHeaderFilter'๋ฅผ ์ ์ฉ
AuthorizationHeaderFilter (๊ถํ ํํฐ)
@Component
@Slf4j
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {
Environment env;
public AuthorizationHeaderFilter(Environment env) {
super(Config.class);
this.env = env;
}
// login -> token -> users(with token) -> header์ ํ ํฐ ๊ฒ์ฌ
@Override
public GatewayFilter apply(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(env.getProperty("token.secret"))
.parseClaimsJws(jwt).getBody()
.getSubject();
} catch (Exception exception) {
returnValue = false;
}
if (subject == null || subject.isEmpty()) {
returnValue = false;
}
return returnValue;
}
public static class Config {
}
}
- AbstractGatewayFilterFactory๋ฅผ ์์๋ฐ์์ ๊ตฌํ
- apply(Config config)๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ์ฌ ์ ์ฉํ ํํฐ๊ธฐ๋ฅ ์์ฑ
- request์ ํค๋์ AUTHORIZATION์ ๋ณด๋ฅผ ํ์ธํ์ฌ .ymlํ์ผ์ ์ ์ฅ๋ 'token.secret'์ ๋ณด๋ฅผ ์ด์ฉํด jwt์ ๋ณด๋ฅผ ํ์ฑํด๋ณธ๋ค.
- ์ ํจํ ์ ๋ณด๋ผ๋ฉด ๋ค์ ํํฐ๋ฅผ ์งํํ๊ณ ์๋๋ผ๋ฉด Error๋ฅผ ๋ฐ์์ํจ๋ค.
User-Service ๋ง์ดํฌ๋ก์๋น์ค
ํ์๊ฐ์ API (POST user-service/users)
- Controller
@PostMapping("/users")
public ResponseEntity<ResponseUser> createUser(@RequestBody RequestUser user) {
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
UserDto userDto = mapper.map(user, UserDto.class);
userService.createUser(userDto);
ResponseUser responseUser = mapper.map(userDto, ResponseUser.class);
return ResponseEntity.status(HttpStatus.CREATED).body(responseUser);
}
Client๋ก ๋ถํฐ ๋ฐ์ RequestUser์ ๋ณด๋ฅผ UserDto๋ก ๋ณํํ ์๋น์ค๋ฅผ ์คํํ๊ณ ์คํ ํ ๋ฐ์ dto์ ๋ณด๋ฅผ ResponseUser์ ๋ณด๋ก ๋ณํํ์ฌ 201์ํ๋ฉ์์ง์ ํจ๊ป ๋ฐํํ๋ค.
- Service
@Override
public UserDto createUser(UserDto userDto) {
userDto.setUserId(UUID.randomUUID().toString());
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
UserEntity userEntity = mapper.map(userDto, UserEntity.class);
userEntity.setEncryptedPwd(passwordEncoder.encode(userDto.getPwd()));
userRepository.save(userEntity);
UserDto retrunUserDto = mapper.map(userEntity, UserDto.class);
return retrunUserDto;
}
์ด ์๋น์ค์์ ์ค์ํ ๊ฒ์ ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ๋ฐ์์จ ํจ์ค์๋์ ๋ณด๋ฅผ ์ธ์ฝ๋ฉํ์ฌ db์ ์ ์ฅํ๋ ๋ถ๋ถ์ด๋ค.
ํ์์ ๋ณด ์กฐํ API
@GetMapping("/users")
public ResponseEntity<List<ResponseUser>> getUser() {
Iterable<UserEntity> userList = userService.getUserByAll();
List<ResponseUser> result = new ArrayList<>();
userList.forEach(v -> {
result.add(new ModelMapper().map(v, ResponseUser.class));
});
return ResponseEntity.status(HttpStatus.OK).body(result);
}
@GetMapping("/users/{userId}")
public ResponseEntity<ResponseUser> getUser(@PathVariable("userId") String userId) {
UserDto userDto = userService.getUserByUserId(userId);
ResponseUser result = new ModelMapper().map(userDto, ResponseUser.class);
return ResponseEntity.status(HttpStatus.OK).body(result);
}
ํ์์ ๋ณด๋ฅผ ์๋ฌด๋ ์กฐํํ๋ฉด ์๋๋ค. ๋ฐ๋ผ์, ๊ถํ์ด ์๋์ง ํ์ธํ๋ ๊ณผ์ ์ด ํ์ํ๋ค. ์ฌ๊ธฐ์๋ ์ด ๊ณผ์ ์ ํํฐ๋ฅผ ํตํด ๊ตฌํํ์๋ค.
Security (๊ถํ ์กฐํ)
@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private UserService userService;
private Environment env;
public AuthenticationFilter(AuthenticationManager authenticationManager,
UserService userService,
Environment env) {
super.setAuthenticationManager(authenticationManager);
this.userService = userService;
this.env = env;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
RequestLogin creds = new ObjectMapper().readValue(request.getInputStream(), RequestLogin.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getEmail(),
creds.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// ์๋น์ค์์ ๋ฐํํ User๊ฐ์ฒด
String userName = ((User)authResult.getPrincipal()).getUsername();
UserDto userDetails = userService.getUserDetailsByEmail(userName);
String token = Jwts.builder()
.setSubject(userDetails.getUserId())
.setExpiration(new Date(System.currentTimeMillis() +
Long.parseLong(env.getProperty("token.expiration_time"))))
.signWith(SignatureAlgorithm.HS512, env.getProperty("token.secret"))
.compact();
response.addHeader("token", token);
response.addHeader("userId", userDetails.getUserId());
}
}
'UsernamePasswordAuthenticationFilter'๋ฅผ ์์ํ์ฌ ๊ตฌํํ๋๋ก ํ๋ค. ์ด๋ '๋ก๊ทธ์ธ ์ ์คํ๋๋ ํํฐ' (/login) ์ด๋ค.
๋จผ์ ์์ฒญ์ ๊ถํ์ฒดํฌ๋ก attemptAuthentication(~)๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ๋ค.
body์ ์ค๋ ค์จ ์ ๋ณด๋ฅผ RequestLogin์ ๋ณด๋ก ๋ณํํ์ฌ '์ด๋ฉ์ผ', 'ํจ์ค์๋' ์ ๋ณด๋ฅผ ์ด์ฉํ ๊ถํ์ ๋ณด๋ฅผ ์ค์ ํ์ฌ ๋ฐํํ๋ค.
userServiceImpl์์ UserDetailService์ loadByUserName์ ์ค๋ฒ๋ผ์ด๋ํ์ฌ User๊ฐ์ฒด๋ฅผ ๋ฐํํ๋๋ก ํ๋ค. ์ด๋ฅผ ํตํด ์๋น์ค ์ฑ๊ณต ์ user์ ๋ณด๋ฅผ ํ์ฉํ์ฌ jwt token์ ๋ง๋ค ์ ์๋ค. (successfulAuthentication๋ฉ์๋)
๋ง๋ jwt์ ๋ณด๋ฅผ header์ token์ ์ ์ฅํ์ฌ ๋ฐํํ๋๋ก ํ๋ค. ํด๋ผ์ด์ธํธ์์๋ ์ด ์ ๋ณด๋ฅผ ์ด์ฉํ์ฌ ํ์์ ๋ณด์กฐํ ์์ฒญ์ ์ฌ์ฉํ๋ค.
WebSecurity ์ค์ ์ ๋ณด
@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private UserService userService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
private Environment env;
public WebSecurity(UserService userService, BCryptPasswordEncoder bCryptPasswordEncoder, Environment env) {
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
this.env = env;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// http.authorizeRequests().antMatchers("/users/**").permitAll();
http.authorizeRequests().antMatchers("/**").permitAll()
.and()
.addFilter(getAuthenticationFilter());
http.headers().frameOptions().disable();
}
private AuthenticationFilter getAuthenticationFilter() throws Exception {
AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager(), userService, env);
return authenticationFilter;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
}
}
์์๋ฐ์ ํํฐ๋ฅผ ๋ฑ๋กํ๊ณ ๋ก๊ทธ์ธ ์ ํ์ฉํ๋ค.
api-gateway -> ๊ถํ์กฐํ๊ฐ ํ์ํ๋ค๋ฉด ํค๋์ jwt์ ๋ณด๊ฐ validํ์ง ๊ฒ์ฌ ํ ๋ง์ดํฌ๋ก์๋น์ค๋ก ์์ฒญ ๋ณด๋
user-service -> login์์๋ username(or email)๋ก jwt์ ๋ณด๋ฅผ ์์ฑํ์ฌ ํค๋์ ์ถ๊ฐํ์ฌ ๋ฐํํ๋ค.