[Spring Cloud] Users Microservice - 로그인 처리

jsieon97·2023년 3월 13일
0

JWT

  • https://jwt.io/
  • 인증 헤더 내에서 사용되는 토큰 포맷
  • 두 개의 시스템끼리 안전한 방법으로 통신 가능
  • 장점
    • 클라이언트 독립적인 서비스(stateless)
    • CDN
    • No Cookie-Session (No CSRF, 사이트간 요청 위조)
    • 지속적인 토큰 저장

로그인 처리 과정

AuthenticationFilter 수정

  • Dependencies (Gradle)
    • implementation 'io.jsonwebtoken:jjwt:0.9.1'추가
  • successfulAuthentication() 함수 추가
// AuthenticationFilter.java

@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private UserService userService;
    private Environment env;

    public AuthenticationFilter(AuthenticationManager authenticationManager,
                                UserService userService,
                                Environment env) {
        this.userService = userService;
        this.env = env;
        super.setAuthenticationManager(authenticationManager);
    }

    ...

    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        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.ES512, env.getProperty("token.secret"))
                .compact();

        response.addHeader("token", token);
        response.addHeader("userId", userDetails.getUserId());
    }
}

Service 수정

  • 사용자 인증을 위한 검색 메소드 추가
// UserService.java

public interface UserService extends UserDetailsService {
    UserDto createUser(UserDto userDto);
    UserDto getUserByUserId(String userId);
    Iterable<UserEntity> getUserByAll();

    UserDto getUserDetailsByEmail(String email);
}
// UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {

    private UserRepository userRepository;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserEntity userEntity = userRepository.findByEmail(username);

        if (userEntity == null) {
            throw new UsernameNotFoundException(username);
        }

        return new User(userEntity.getEmail(),
                userEntity.getEncryptedPwd(),
                true,
                true,
                true,
                true,
                new ArrayList<>());
    }

    @Autowired
    public UserServiceImpl(UserRepository userRepository, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userRepository = userRepository;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    ...

    @Override
    public UserDto getUserDetailsByEmail(String email) {
        UserEntity userEntity = userRepository.findByEmail(email);

        UserDto userDto = new ModelMapper().map(userEntity,UserDto.class);
        return userDto;
}

API Gateway에 Spring Security와 JWT 사용 추가

  • AuthorizationHeaderFilter 추가
  • Dependencies (Gradle)
    • implementation 'io.jsonwebtoken:jjwt:0.9.1'추가
// AuthorizationHeaderFilter.java

@Component
@Slf4j
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {
    Environment env;

    public AuthorizationHeaderFilter(Environment env) {
        this.env = env;
    }

    public static class Config {

    }

	// CustomFilter
    // login -> token -> users (with token) -> header (include token)
    @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);
        });
    }
	
    // JWT 유효성 체크
    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 e) {
            returnValue = false;
        }

        if(subject == null || subject.isEmpty()) {
            returnValue = false;
        }

        return returnValue;
    }

	// Error메시지
    // 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();
    }
}

javax/xml/bind/DatatypeConverter 에러 발생 시

  • build.gradle의 dependencies에
    implementation 'io.jsonwebtoken:jjwt:0.9.1'

Postman 테스트

  • Get요청 시 token이 없으면 401에러 (인증 자격이 없음) 발생
  • token 추가 시 200 OK와 정보를 반환하는 것을 볼 수 있다.
profile
개발자로써 성장하는 방법

0개의 댓글