1. AuthenticationFilter란?
- Spring Security를 이용한 로그인 요청 발생 시 작업을 처리해 주는 Custom Filter 클래스
2. User-Service -> AuthenticationFilter.java
@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
// final AuthenticationManager authenticationManager;
private final UserService userService;
private final Environment env;
public AuthenticationFilter(AuthenticationManager authenticationManager,
UserService userService,
Environment env) {
super.setAuthenticationManager(authenticationManager);
this.userService = userService;
this.env = env;
}
// 1. 로그인 요청
// 2. 본문에 JSON 데이터를 RequestLogin 객체로 변환
// 3. 이메일과 비밀번호를 UsernamePasswordAuthenticationToken로 감싸서 인증 시도
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
// RequestLogin 객체로 요청 데이터를 변환
RequestLogin creds = new ObjectMapper().readValue(request.getInputStream(), RequestLogin.class);
// email과 password를 UsernamePasswordAuthenticationToken에다가 저장하고 인증시도
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 {
// 인증된 사용자 이메일 가져오기
String userName = ((User)authResult.getPrincipal()).getUsername();
// UserService를 통해 사용자 정보(UserDto) 가져오기
UserDto userDetails = userService.getUserDetailsByEmail(userName);
// JWT 토큰 생성
String token = Jwts.builder()
// 토큰의 subject로 사용자 ID 설정
.setSubject(userDetails.getUserId())
.setExpiration(new Date(System.currentTimeMillis() +
// 토큰 만료 시간 설정
Long.parseLong(env.getProperty("token.expiration_time"))))
// 토큰을 HS512 알고리즘 만들기
.signWith(SignatureAlgorithm.HS512, env.getProperty("token.secret"))
// 토큰 생성 완료
.compact();
//JWT 토큰과 사용자 ID를 HTTP 응답 헤더에 추가
response.addHeader("token", token);
response.addHeader("userId", userDetails.getUserId());
}
}
3. User-Service -> WebSecurity.java
// 권한 설정
@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.env = env;
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF 보호 비활성화
http.csrf().disable();
// 요청에 대한 권할 설정
http.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(getAuthenticationFiler());
http.headers().frameOptions().disable();
}
private AuthenticationFilter getAuthenticationFiler() throws Exception {
AuthenticationFilter authenticationFilter =
new AuthenticationFilter(authenticationManager(), userService, env);
// authenticationFilter.setAuthenticationManager(authenticationManager());
return authenticationFilter;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 로그인했던 password를 암호화
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
}
}
4. Gateway-Service -> application.yml
spring:
application:
name: apigateway-service
cloud:
gateway:
default-filters:
- name: GlobalFilter
args:
baseMessage: Spring Cloud Gateway Global Filter
preLogger: true
postLogger: true
routes:
# - id: user-service
# uri: lb://USER-SERVICE
# predicates:
# - Path=/user-service/**
- id: user-service
uri: lb://USER-SERVICE
predicates:
# /user-service/login에 POST 요청만 규칙 적용
- Path=/user-service/login
- Method=POST
filters:
- RemoveRequestHeader=Cookie
# 기존 = /user-service/login
# 변경 = /login
- RewritePath=/user-service/(?<segment>.*), /$\{segment}
5. GateWay-Service -> AuthorizationHeaderFilter.java
@Component
@Slf4j
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {
Environment env;
public AuthorizationHeaderFilter(Environment env) {
super(Config.class);
this.env = env;
}
public static class Config {
}
// login -> token -> users (with token) -> header(include token)
@Override
public GatewayFilter apply(Config config) {
return ((exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// Authorization 헤더가 없는 경우
if (!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
return onError(exchange, "No authorization header", HttpStatus.UNAUTHORIZED);
}
// Authorization 헤더에서 JWT 추출
String authorizationHeader = request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0);
String jwt = authorizationHeader.replace("Bearer", "");
// JWT 유효성 검사
if (!isJwtValid(jwt)) {
return onError(exchange, "JWT token is not valid", HttpStatus.UNAUTHORIZED);
}
// 유효한 경우 다음 필터로 전달
return chain.filter(exchange);
});
}
// Mono, Flux -> Spring WebFlux
// 에러 처리 메서드: 요청을 차단하고 오류 응답을 반환
private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(httpStatus);
log.error(err);
return response.setComplete();
}
// JWT 유효성 검사
private boolean isJwtValid(String jwt) {
boolean returnValue = true;
String subject = null;
// JWT 파싱 검증
try {
subject = Jwts
.parser()
// 비밀키를 통해 검증
.setSigningKey(env.getProperty("token.secret"))
// JWT 파싱
.parseClaimsJws(jwt)
// 클레임, subject 추출
.getBody()
.getSubject();
} catch (Exception ex) {
returnValue = false;
}
if (subject == null || subject.isEmpty()) {
returnValue = false;
}
return returnValue;
}
}
6. 테스트
1). 회원가입
2). 로그인
3). 조회
(1). 토큰 미적용
(2). 토큰 적용
'Java > Spring Boot' 카테고리의 다른 글
[MSA] Spring Cloud로 MSA를 개발해보자 6편 [Spring Cloud Bus] (0) | 2024.08.27 |
---|---|
[MSA] Spring Cloud로 MSA를 개발해보자 5편 [Config] (0) | 2024.08.26 |
[MSA] Spring Cloud로 MSA를 개발해보자 3편 [Gateway Service-2] (0) | 2024.08.16 |
[MSA] Spring Cloud로 MSA를 개발해보자 2편 [Gateway Service] (1) | 2024.08.13 |
[MSA] Spring Cloud로 MSA를 개발해보자 1편 [Service Discovery] (0) | 2024.08.11 |