Our Farthest Frontier guides will help you learn how to play this Colony Sim and build your own thriving Settlement in the wilderness!

 

Quicklinks: Farthest Frontier Guides Farthest Frontier Starter Guide | F.A.Q.

Farthest Frontier Guides

 

Farthest Frontier Starter Guide

Farthest Frontier can be a bit challenging as far as colony sims go. Our Farthest Frontier Starter Guide will help you learn the basics of the game and set you up for tackling some of the more difficult scenarios.

Starting a New Settlement

 

Naturally, your first step is to start a new settlement. You could just pick a name and hit «Start,» but this is a really bad idea for your very first game.

First, let’s break down the various parts of starting a new game:

Selecting Your Difficulty

There are three general options for selecting your difficulty:

  • Pioneer – The easiest difficulty setting.
  • Trailblazer – The equivalent of normal difficulty.
  • Vanquisher – The hardest difficulty setting.

I recommend that you select «Trailblazer» for your first time playing the game. It will be a challenge, yes, but it will be far from insurmountable.

Advanced Settings

In addition to the basic settings, there’s also a lovely button called «Advanced Settings» that will allow you to fine-tune several aspects of the game.

We’re not going to mess with the Advanced Settings for your first playthrough, but here’s what they do just in case you’re curious:

  • Map Seed – This is the code used to generate your map. It can be used to share maps with other players. If you’ve got a map seed from someone else, this is where you would input it. You can also see your Map Seed on the menu while in a game.
  • Extra Resources – Determines how many Villagers, how much food, and what equipment you’ll start out with.
    • Pioneer
      • 12 Villagers
      • 16 Months of Food
      • 5 Weapons
      • 4 Bows
      • 150 Arrows
      • 30 Tools
    • Trailblazer
      • 12 Villagers
      • 9 Months of Food
      • 5 Weapons
      • 3 Bows
      • 100 Arrows
      • 20 Tools
    • Vanquisher
      • 10 VIllagers
      • 4 Months of Food
      • 4 Weapons
      • 3 Bows
      • 80 Arrows
      • 15 Tools
  • Maladies – Determines how likely Disease will spread amongst Villagers.
    • Pioneer – -60% chance for Disease to spread.
    • Trailblazer – -30% chance for Disease to spread.
    • Vanquisher – No reduction in chance of Disease spreading.
  • Wildlife – Determines spawn rate of Animals and hostile Wildlife like Wolves and Bears.
    • Pioneer
      • Deer – Plentiful
      • Boar – Rare
      • Wolves – Rare
      • Bear – Extremely Rare
    • Trailblazer
      • Deer – Average
      • Boar – Average
      • Wolves – Average
      • Bear – Rare
    • Vanquisher
      • Deer – Rare
      • Boar – Plentiful
      • Wolves – Plentiful
      • Bear – Average
  • Raiders – Affects how often Raiders and hostile armies attack.
    • Pioneer
      • «Raiders will occasionally invade your settlement, wreak havoc, and steal goods.»
    • Trailblazer
      • «Raiders will occasionally invade your settlement, wreak havoc, and steal goods.»
      • «The invading Armies of local Lords may demand a payment for settling on their lands, lest your settlement be razed.»
    • Vanquisher
      • «Organized groups of elite raiders have been seen in the area, and have been known to attack with reckless abandon, seeking glory on the battlefield.»
      • «The local Lords in the area are particularly brutal, and do not take kindly to those that would squat on their lands. Do not expect your presence to go unnoticed.»

Terrain

«Terrain» selects your map type. There are five different options:

  • Arid Highlands – «Dry, desolate highlands with infertile soil, sparse pine barrens, and wasteland[,] but rich in mineral deposits. Hard environment difficulty.»
  • Alpine Valleys – «Northern mountain valleys, thick with conifer forests and interspersed with lakes and occasional meadows. Medium environmental difficulty.»
  • Lowland Lakes – «An idyllic setting of dense forests, lush meadows[,] and lakes teaming with fish. Easy environmental difficulty.»
  • Plains – «Expansive plainland with fertile meadows, dry grassland[,] and brush[,] but few forests and lower mineral resources. Medium environmental difficulty.»
  • Random – «Generates a random map that can vary significantly between mountains and flat land, lakes or dry land, and various mixes of biomes. Variable environmental difficulty.»

This is where we’re going to deviate from the default: select Lowland Lakes for your map. It will be a much easier experience that will help you get acclimated to the game.

I strongly recommend that you don’t select any of the other map types until you have a few hours in the game and a good understanding of its mechanics. The additional challenge will make it much tougher to succeed!

Settlement Name

This is what your Settlement will be called. You can push the dice button to select from randomized options or you can input a custom name.

Map Size

You can choose between a Small, Medium, and Large map. Medium is the default, so we’ll go with that for now.

Pacifist Mode?

Turning on «Pacifist Mode» will disable Raiders, invading armies, Wolves, and Bears. Do not turn this option on for your first playthrough.

Starting the Game

Now that we’ve covered the settings, it’s time to start the game! Remember, this is what you should have picked:

  • Trailblazer Difficulty
  • Lowland Lakes Map
  • Medium Map Size
  • Pacifist Mode Off

Name your Settlement and click «Start.» You’ll get a loading screen and we’ll be in the game after a short while.

Placing Your Town Center (or Rerolling the Map)

Once you’re in the game, you’ll be presented with a large area of the map revealed and a prompt to place your Town Center.

Be very careful in selecting where you place your Town Center. While you can easily move most Buildings in the game, your Town Center is stuck wherever you place it. Furthermore, it serves as a defensive Building where your Villagers can garrison within and shoot arrows at invaders — you’ll want to ensure it’s in an important location, too.

I recommend placing your Town Center on relatively flat land and 20 or so blocks away from water. You’ll also want to check the fertility of land by pressing «F» — this will show you a good spot to build Farms.

You can place your Town Center anywhere within the circle of the scouted area. If it’s not to your liking, you can go into the options menu and «Reroll» the map, generating a new map of the same type but with a different seed. Don’t hesitate to reroll the map if you don’t like what you see — you’re going to be playing here for a good few hours at least!

Understanding the Farthest Frontier UI

You’ll need to know how the UI works in order to succeed in this game, so take a gander at this image.

Here’s a description of each of these items:

  1. Report for Last 12 Months – Provides graphs and other information showing how your Settlement did over the last 12 months including Food Production, Goods & Material Production, and Villager Stats.
  2. Sick Villager Counter – Shows you how many Villagers are ill (if any).
  3. Population Counter – How many Villagers you currently have vs. how many you have enough Housing for.
  4. Villager Happiness – Shows you how Happy your Villagers are overall.
  5. Food Stores – Shows you how many months of Food you have stored away. The second number in parenthesis shows you have many months worth of food will spoil within the next year.
  6. Calendar – Shows you the month and the season. Each block is a month, and the images behind the blocks represent the seasons.
  7. Resources – A tracker for your various resources. From left to right:
    • Wood – Acquired from Trees.
    • Firewood – Made at a Firewood Splitter.
    • Planks – Made at a Saw Pit.
    • Stone – Gathered from large stones in the world.
    • Clay – Gathered from Clay Pits.
    • Bricks – Produced at the Tier 3 building Brickyard.
    • Gold – Produced through Taxes or Trade.
  8. Auto Upgrade and Settings Buttons
    • Auto Upgrade – When turned on, Buildings will automatically be flagged to upgrade to the next Tier. On by default.
    • Settings – Your mouse-driven access to the Settings menu.

Planning Your Settlement with the «Crossroads» System

Now that your Town Center is placed, we’ll have to figure out how we’re actually going to lay out the town.

If you’re like me, you may be occasionally stuck with «Blank Canvas» syndrome. You don’t really know what should go where when confronted with an abundance of open space.

That’s why I’ve developed the «Crossroads» system of Building a Settlement. It’s a simple way of determining what goes where:

In this image, you can see a road splits the Settlement into four Quarters.

  • The top left is the «Commercial» Quarter. This is where Storage and non-polluting workshops like the Cobbler and Basket Shop go.
  • The top right is the «Residential» Quarter. This is where Houses go and where you’ll put some key Buildings that improve Desirability such as the School and Healer’s House.
  • The bottom right is the «Agricultural» Quarter. This is where your Farms and Orchards go.
  • Finally, the bottom left is the «Industrial» Quarter. This is where we’ll place all of the Buildings that negatively impact Desirability such as the Firewood Splitter, Tanner, Compost Yard, etc. This is the same quadrant that holds the Town Center and will ensure there’s enough distance between these Buildings and your Houses, preventing any loss in Desirability.

The Commercial, Agricultural, and Industrial Quarters in this plan are fairly straightforward — simply place the Buildings however you think it would make sense.

The Residential Quarter, however, will need a little more planning:

Our goal for this Farthest Frontier Starter Guide will be to get you up to Tier 2 with the ability to expand further. To make that happen, the Residential Quarter will be laid out with nine grids made with roads.

The central space is best used for key Buildings that will improve Desirability of most (if not all) of the surrounding Houses:

  • Market
  • Healer’s House
  • School
  • Shrine
  • Parks, Gardens, etc.

Additionally, each of the squares in the cardinal directions (north, south, east, and west) have a little extra space between the houses. This will give you more room for Decorations which can improve Desirability.

Here’s a more specific layout:

 

This system can definitely be used to take you to Tier 2 and even Tier 3. Advancing to Tier 4 — which doesn’t have any Buildings as of Early Access Version 0.1.4 — will likely require a change in layout or a different strategy which this guide will not cover.

Why all the complexity with laying out Housing? It’s simple: High Desirability will allow you to upgrade your Houses. Upgraded Houses are required for advancing to higher Tiers and they also pay taxes which can offset any Guards, Soldiers, or Services you have that cost money to run every month (such as the Compost Yard).

The Crossroads system of planning will serve as the framework for this guide. Now that we got that covered, let’s move on to actually Building things out!

Clearing Land for your Town Center and Building the Crossroads

Trees and Rocks will be automatically flagged for removal once you pick your location for the Town Center. It will take a short time for the Villagers to actually do that. While they’re working, take a look at the immediate area around town.

You can clear additional land by pressing «H» and dragging the box over Trees and Rocks you’d like to flag for removal. Your Villagers will get to them eventually. You can, if you wish, increase or decrease the number of workers and Prioritize jobs by clicking on the individual Tree or Rock.

While they’re working, press «N» to bring up the interface for Building a Dirt Road. Drag out a Crossroads with your Town Hall on one of the corners. Remember, the Town Hall will be the quarter where your «polluting» Buildings will go.

Building Houses, a Well, and a Firewood Splitter

Our next step will be to Build the first grid square for the Residential Quarter.

Use a road to make a 6×6 Square in the quadrant opposite your Town Center:

Place three Shelters in the corners. Build a Well in the farthest corner as shown in the image above.

You’ll need Stone for the Well; if you don’t have any, designate a nearby Rock for removal to get it by pressing H and dragging a box over it or by selecting the Rock individually and selecting «Harvest Resource.» You’ll also likely need more Wood soon, so designate some trees in the immediate area for harvesting.

At the same time, we’ll want to place a Firewood Splitter:

 

'Farthest Frontier' 카테고리의 다른 글

Farthest Frontier – Save Game Data & File Location  (0) 2022.10.18

개발 스펙

  • Java(11)
  • Amazon Corretto JDK(11)
  • Spring Boot(2.5.3)
  • Spring Security(boot-starer)
  • JWT(0.9.1)

Config

1. Dependency 추가

Spring Security와 JWT의 Dependency 추가

dependencies {
	...
    
	// Spring Security
    implementation "org.springframework.boot:spring-boot-starter-security"
    // Spring Security Test
	testImplementation 'org.springframework.security:spring-security-test'
    // JWT
    implementation 'io.jsonwebtoken:jjwt:0.9.1'
    
	...
}

2. 정적 자원 제공 클래스 생성

정적 자원을 제공하는 클래스를 생성하여 아래와 같이 설정한다.

@Configuration 
public class WebMvcConfig implements WebMvcConfigurer {

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
            "classpath:/static/",
            "classpath:/public/",
            "classpath:/",
            "classpath:/resources/",
            "classpath:/META-INF/resources/",
            "classpath:/META-INF/resources/webjars/"
    };

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 경로에 해당하는 url을 forword
//        registry.addViewController("/loginPage").setViewName("login");
        // 우선순위를 가장 높게 잡는다.
//        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 정적 자원의 경로를 허용
        registry.addResourceHandler("/**").addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS);
    }

}

3. SpringSecurity 설정

스프링 시큐리티의 웹 보안 기능을 초기화 및 설정

WebSecurityConfigurerAdapter를 상속받아 HttpSecurity를 이용해 설정한다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // JWT 제공 클래스
    private final JwtProvider jwtProvider;
    // 인증 실패 또는 인증헤더가 전달받지 못했을때 핸들러
    private final AuthenticationEntryPoint authenticationEntryPoint;
    // 인증 성공 핸들러
    private final AuthenticationSuccessHandler authenticationSuccessHandler;
    // 인증 실패 핸들러
    private final AuthenticationFailureHandler authenticationFailureHandler;
    // 인가 실패 핸들러
    private final AccessDeniedHandler accessDeniedHandler;

    /**
     * Security 적용 무시
     */
    @Override
    public void configure(WebSecurity web) {
        web.ignoring()
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations())
                .mvcMatchers("/docs/**");
    }

    /**
     * 보안 기능 초기화 및 설정
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        final String[] GET_WHITELIST = new String[]{
                "/login",
                "/user/login-id/**",
                "/user/email/**",
                "/affiliate"
        };

        final String[] POST_WHITELIST = new String[]{
                "/client-user"
        };

        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint) // 인증 실패
                .accessDeniedHandler(accessDeniedHandler) // 인가 실패
                .and().authorizeRequests()
                .antMatchers(HttpMethod.GET, GET_WHITELIST).permitAll() // 해당 GET URL은 모두 허용
                .antMatchers(HttpMethod.POST, POST_WHITELIST).permitAll() // 해당 POST URL은 모두 허용
                .antMatchers("/client-user/**").hasAnyRole(UserType.CL.getRoll()) // 권한 적용
                .anyRequest().authenticated() // 나머지 요청에 대해서는 인증을 요구
                .and() // 로그인하는 경우에 대해 설정함
                .formLogin().disable() // 로그인 페이지 사용 안함
//                .loginPage("/user/loginView") // 로그인 성공 URL을 설정함
//                .successForwardUrl("/index") // 로그인 실패 URL을 설정함
//                .failureForwardUrl("/index").permitAll()
                .addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    /**
     * 사용자 요청 정보로 UserPasswordAuthenticationToken 발급하는 필터
     */
    @Bean
    public CustomAuthenticationFilter authenticationFilter() throws Exception {
        CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManager());
        // 필터 URL 설정
        customAuthenticationFilter.setFilterProcessesUrl("/login");
        // 인증 성공 핸들러
        customAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        // 인증 실패 핸들러
        customAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
        // BeanFactory에 의해 모든 property가 설정되고 난 뒤 실행
        customAuthenticationFilter.afterPropertiesSet();
        return customAuthenticationFilter;
    }

    /**
     * JWT의 인증 및 권한을 확인하는 필터
     */
    @Bean
    public JwtFilter jwtFilter() {
        return new JwtFilter(jwtProvider);
    }

}

회원 인증

1. UsernamePasswordAuthenticationFilter 구현

사용자 요청 정보로 UserPasswordAuthenticationToken 발급 후 AuthenticationManager에게 전달하고 AuthenticationProvider의 인증 메서드를 실행하는 UsernamePasswordAuthenticationFilter를 구현

public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    public CustomAuthenticationFilter(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        final UsernamePasswordAuthenticationToken authRequest;

        final LoginDTO loginDTO;
        try {
            // 사용자 요청 정보로 UserPasswordAuthenticationToken 발급
            loginDTO = new ObjectMapper().readValue(request.getInputStream(), LoginDTO.class);
            authRequest = new UsernamePasswordAuthenticationToken(loginDTO.getLogin(), loginDTO.getPass());
        } catch (IOException e) {
            throw new NotValidException();
        }
        setDetails(request, authRequest);

        // AuthenticationManager에게 전달 -> AuthenticationProvider의 인증 메서드 실행
        return this.getAuthenticationManager().authenticate(authRequest);
    }

}

2. AuthenticationProvider 구현

AuthenticationManager 하위에 실제로 인증을 처리할 AuthenticationProvider를 구현

@Component
@RequiredArgsConstructor
public class AuthenticationProviderImpl implements AuthenticationProvider {

    private final UserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;

    /**
     * 인증 구현
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 전달 받은 UsernamePasswordAuthenticationToken
        UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;

        // AuthenticaionFilter에서 생성된 토큰으로부터 아이디와 비밀번호를 추출
        String username = token.getName();
        String password = (String) token.getCredentials();
        // 해당 회원 Database 조회
        UserDetailsImpl userDetail = (UserDetailsImpl) userDetailsService.loadUserByUsername(username);

        // 비밀번호 확인
        if (!passwordEncoder.matches(password, userDetail.getPassword()))
            throw new BadCredentialsException(userDetail.getUsername() + "Invalid password");

        // 인증 성공 시 UsernamePasswordAuthenticationToken 반환
        return new UsernamePasswordAuthenticationToken(userDetail.getUsername(), "", userDetail.getAuthorities());
    }

    /**
     * provider의 동작 여부를 설정
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

}

3. UserDetailsService 구현

인증 과정 중 실제 Database에 회원을 데이터를 조회하는UserDetailsService를 구현

@RequiredArgsConstructor
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) {
        User user = userRepository.findFirstUserByLoginOrderByIdAsc(username).orElseThrow(() -> new NotFoundDataException("User"));
        return new UserDetailsImpl(
                user.getLogin(),
                user.getPass(),
                user.getEmail(),
                Collections.singleton(new SimpleGrantedAuthority("ROLE_" + user.getType().getRoll()))
        );
    }

}

4. UserDetails 구현

회원 데이터를 조회하고 해당 정보와 권한을 저장하는 UserDetails를 구현

@AllArgsConstructor
@Getter
@ToString
public class UserDetailsImpl implements UserDetails {

    private final String username;
    private final String password;
    private final String email;
    private final Collection<? extends GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return false;
    }
}

5. PasswordEncoder 구현

기본적으로 제공해주는 PasswordEncoder를 구현한 클래스를 사용할 경우 Bean 등록만 하면된다.  
(BCryptPasswordEncoder OR DelegatingPasswordEncoder)

@Bean
public PasswordEncoder passwordEncoder() {
	return new BCryptPasswordEncoder();
}

 

 

Spring Password Encoder

Spring에서는 인증/권한인가 등의 처리가 필요할 때 사용하라고 만든 Spring Security 패키지가 존재한다. 그 중 유저가 입력하는 Password를 암호화해서 저장하는 방법에 대해서 알아보자 아, 그 전에

gompangs.tistory.com

 

다른 로직의 비밀번호 인증이 필요할 경우는 PasswordEncoder를 구현해서 사용한다.

@Component
public class PasswordEncoderImpl implements PasswordEncoder {

    /**
     * 비밀번호 해시
     */
    @Override
    public String encode(CharSequence rawPassword) {
        return passwordEncode((String) rawPassword); // 커스텀 메서드
    }

    /**
     * 비밀번호 확인
     */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return passwordMatches((String) rawPassword, encodedPassword); // 커스텀 메서드
    }

}

6. AuthenticationSuccessHandler 구현

인증 성공 시 핸들링하는 AuthenticationSuccessHandler를 구현

@Component
@RequiredArgsConstructor
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {

    private final JwtProvider jwtProvider;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        // 전달받은 인증정보 SecurityContextHolder에 저장
        SecurityContextHolder.getContext().setAuthentication(authentication);
        // JWT Token 발급
        final String token = jwtProvider.generateJwtToken(authentication);
        // Response
        ApiResponse.token(response, token);
    }

}

7. AuthenticationFailureHandler 구현

인증 실패 시 핸들링하는 AuthenticationFailureHandler를 구현

@Component
public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
        ApiResponse.error(response, ApiResponseType.UNAUTHORIZED_RESPONSE);
    }

}

JWT 인증

1. JwtProvider 생성

JWT를 발급하고 확인하는 클래스 생성

@Component
@RequiredArgsConstructor
public final class JwtProvider {

    private final UserDetailsService userDetailsService;

    // secret key
    @Value("${jwt.secret-key}")
    private String secretKey;

    // access token 유효시간
    private final long accessTokenValidTime = 2 * 60 * 60 * 1000L;

    // refresh token 유효시간
    private final long refreshTokenValidTime = 2 * 7 * 24 * 60 * 60 * 1000L;

    @PostConstruct
    private void init() {
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
    }

    /**
     * 토큰에서 Claim 추출
     */
    private Claims getClaimsFormToken(String token) {
        return Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(secretKey)).parseClaimsJws(token).getBody();
    }

    /**
     * 토큰에서 인증 subject 추출
     */
    private String getSubject(String token) {
        return getClaimsFormToken(token).getSubject();
    }

    /**
     * 토큰에서 인증 정보 추출
     */
    public Authentication getAuthentication(String token) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(this.getSubject(token));
        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    }

    /**
     * 토큰 발급
     */
    public String generateJwtToken(Authentication authentication) {
        Claims claims = Jwts.claims().setSubject(String.valueOf(authentication.getPrincipal()));
        claims.put("roles", authentication.getAuthorities());
        Date now = new Date();
        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + accessTokenValidTime))
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }

    /**
     * 토큰 검증
     */
    public boolean isValidToken(String token) {
        try {
            Claims claims = getClaimsFormToken(token);
            return !claims.getExpiration().before(new Date());
        } catch (JwtException | NullPointerException exception) {
            return false;
        }
    }

}

2. JwtFilter 생성

Header를 통해 JWT의 인증 요청이 왔을때 처리하는 Filter 생성

@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {

    public static final String AUTHORIZATION_HEADER = "Authorization";
    public static final String BEARER_PREFIX = "Bearer ";
    private final JwtProvider jwtProvider;

    /**
     * 토큰 인증 정보를 현재 쓰레드의 SecurityContext 에 저장하는 역할 수행
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {

        // Request Header에서 토큰 추출
        String jwt = resolveToken(request);

        // Token 유효성 검사
        if (StringUtils.hasText(jwt) && jwtProvider.isValidToken(jwt)) {
            // 토큰으로 인증 정보를 추출
            Authentication authentication = jwtProvider.getAuthentication(jwt);
            // SecurityContext에 저장
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }

    /**
     * Request Header에서 토큰 추출
     */
    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
            return bearerToken.substring(7);
        }
        return null;
    }
    
}

3. AuthenticationEntryPoint 구현

토큰 인증이 실패하거나 인증 헤더를 정상적으로 받지 못했을때 핸들링하는 AuthenticationEntryPoint를 구현

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        ApiResponse.error(response, ApiResponseType.UNAUTHORIZED_RESPONSE);
    }

}

4. AccessDeniedHandler 구현

토큰 인가 실패 시 핸들링하는 AccessDeniedHandler를 구현

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        ApiResponse.error(response, ApiResponseType.FORBIDDEN_RESPONSE);
    }

}

Flowchart


전체 소스는 github에서 확인하실 수 있습니다.

 

GitHub - devbeekei/SpringSecurityJwt: SpringSecurity + JWT example

SpringSecurity + JWT example. Contribute to devbeekei/SpringSecurityJwt development by creating an account on GitHub.

github.com

 

컨테이너란?

컨테이너는 데스크탑, 기존의 IT 또는 클라우드 등 어디서나 실행될 수 있도록 애플리케이션 코드가 해당 라이브러리 및 종속 항목과 함께 패키징되어 있는 소프트웨어 실행 유닛입니다.

이를 수행하기 위해 컨테이너는 OS의 기능(Linux 커널의 경우, 이름 공간 및 cgroups 프리미티브)을 활용하여 프로세스를 격리하고 해당 프로세스가 액세스할 수 있는 CPU, 메모리 및 디스크의 양을 제어하는 운영 체제(OS) 가상화의 형식을 활용합니다.

가상 머신과는 달리 컨테이너는 모든 인스턴스에 게스트 OS를 포함할 필요가 없으며, 그 대신 호스트 OS의 기능과 리소스를 간편하게 활용할 수 있습니다. 따라서 컨테이너는 소형이고, 빠르며, 이식성이 뛰어납니다.

컨테이너는 FreeBSD Jails 및 AIX Workload Partitions 등의 버전으로 수십 년 전에 처음 소개되었지만, 최신 개발자들 중 대부분은 Docker의 소개에 따라 2013년에 최신 컨테이너 시대가 도래했다고 기억합니다.

 

-> Docker 가 Container Environment 를 이끌면서 시대가 도래했다고 인식하는 경향이 많다. 실제적으로 이후에 Docker의 전체적인 DevOps 적 Mainternance를 위해서 Orchestration Tools 인 K8s 가 도입되었고, 많은 부분에서 정식적으로 K8s 에 대한 정식지원 발표로 인하여, K8s 가 대표적인 Tool 이 되었다.

 

 

컨테이너 vs. 가상 머신(VM)

컨테이너를 보다 잘 이해하는 한 가지 방법은 기존의 가상 머신(VM)과의 차이점을 살펴보는 것입니다. 온프레미스에 있건 혹은 클라우드에 있건 이와 무관하게, 기존의 가상화에서는 하이퍼바이저를 활용하여 물리적 하드웨어를 가상화합니다. 그리고 각각의 VM에는 애플리케이션, 이와 연관된 라이브러리 및 종속 항목과 함께 OS가 실행해야 하는 하드웨어의 가상 사본인 게스트 OS가 포함됩니다.

기반 하드웨어를 가상화하는 대신, 컨테이너는 운영 체제(일반적으로 Linux)를 가상화함으로써 각 개별 컨테이너에는 오직 애플리케이션과 함께 해당 라이브러리와 종속 항목만 포함됩니다. 게스트 OS의 부재는 컨테이너가 경량이며 빠르고 포터블한 이유를 설명합니다.

이러한 비교를 좀더 자세히 살펴보려면 "컨테이너 vs. VM: 차이점"을 참조하세요.

 

- 간단하게 요약하자면 VM에서는 하이퍼바이저를 활용하여 물리적 하드웨를 가상화한다는 소리는 Partition의 일부를 떼어 실제적으로 가상환경을 꾸릴 수 있게 가상화를 꾸린다고 설명하는 것이다.

그리고 이에 대한 Dependencies 인 Library, Guest OS(상주하는 OS)를 올린다고 생각하면 되는 듯 하다.

 

- Container 의 경우 Application 과 함께 Dependencies 만 올라간다. 그러니까 Guest OS 가 탑재되지 않은 상태에서 초경량 상태로 올라가기 때문에 K8s 는 이를 활용하여 replication 을 구축하고 일정 처리속도를 담아 지속적인 무중단 서비스를 운영할 수 있다.

 

컨테이너의 이점

특히 VM과 비교하여, 컨테이너의 주요 장점은 경량화와 이식성을 제공하는 추상화 레벨을 제공하는 것입니다.

  • 경량: 컨테이너는 시스템 OS 커널을 공유함으로써 애플리케이션마다 전체 OS 인스턴스가 필요하지 않으며, 리소스에서 컨테이너 파일의 소형화와 간편함을 제공합니다. 특히 가상 머신과 비교하여 더 작아진 크기로 덕분에 수평 확장되는 클라우드 네이티브 애플리케이션을 보다 잘 지원하며 신속한 스핀업이 가능합니다.

    - 이 말인 즉슨, 시스템 OS Kernel 을 Sharing 한다는 부분에서, System OS Kernel (미리 컴파일된 커널이 상시 대기중인 상태로 base 로 공유됨 으로 진지구축을 빠르게 할 수 있따는 것이다.

  • 이식성 및 플랫폼 독립성: 컨테이너가 모든 종속 항목들을 자신과 함께 전달하므로, 일단 소프트웨어를 한 번만 작성하면 랩탑, 클라우드 및 온프레미스 컴퓨팅 환경에서 이를 재구성하지 않고도 바로 실행할 수 있습니다.
  • 최신형 개발 및 아키텍처 지원: 플랫폼 간의 배치 이식성/일관성 및 소형 크기의 조합 덕분에, 컨테이너는 최신형 개발에 매우 이상적입니다. 또한 DevOps, 서버리스  마이크로서비스 등의 빌드되어 있는 애플리케이션 패턴은 소규모 증분의 일반 코드 배치입니다.
  • 활용도 향상: 이전의 VM처럼, 컨테이너를 사용하여 개발자와 운영자는 물리적 시스템의 CPU 및 메모리 활용도를 향상시킬 수 있습니다. 컨테이너의 보다 큰 장점은 마이크로서비스 아키텍처도 허용하므로 애플리케이션 컴포넌트를 보다 미세하게 배치 및 스케일링할 수 있다는 점입니다. 이는 단일 컴포넌트의 과중한 로드로 인해 전체 모놀리식 애플리케이션을 확장해야 하는 데 대한 매력적인 대안이 될 수 있습니다.

 

컨테이너에 대한 유스케이스

컨테이너는 특히 클라우드 환경에서 점점 더 두각을 나타내고 있습니다. 많은 기업들은 심지어 자체 애플리케이션과 워크로드에 대한 범용 컴퓨팅 플랫폼으로서 VM의 대용으로 컨테이너를 고려하고 있습니다. 그러나 이러한 광범위한 분야 내에서, 컨테이너가 특히 의미가 있는 주요 유스케이스가 있습니다.

  • 마이크로서비스: 컨테이너는 소형이고 경량입니다. 따라서 이는 애플리케이션이 다수의 느슨하게 결합되고 독립적인 배치가 가능한 소형 서비스들로 구성되는 마이크로서비스 아키텍처에 매우 적절합니다.
  • DevOps: 아키텍처로서 마이크로서비스와 플랫폼으로서 컨테이너의 결합은 소프트웨어의 구축, 장착 및 실행 방안으로서 DevOps를 채택하는 많은 팀들의 공통 기반입니다.
  • 하이브리드, 멀티클라우드: 컨테이너가 랩탑, 온프레미스 및 클라우드 환경의 어디서나 일관되게 실행될 수 있으므로, 이는 기업들이 자체 데이터 센터와 결합하여 다수의 혼합형 퍼블릭 클라우드에서 운영을 수행하는 하이브리드 클라우드  멀티클라우드 시나리오의 이상적인 기반 아키텍처입니다.
  • 애플리케이션 현대화 및 마이그레이션: 애플리케이션 현대화의 가장 일반적인 접근 방법 중 하나는 클라우드로의 마이그레이션이 가능하도록 이를 컨테이너화함으로써 시작됩니다.

    - 이 부분이 가장 중요한 핵심 포인트라 생각한다. 일반적인 접근 방법 중 하나라고 하지만, Container 화를 한다는 것은 Application 현대화 (말이 좀 거창하지) 하여 빨리 빨리 진지구축을 할 수 있도록 한다는 것이다.

컨테이너화

컨테이너를 활용하려면 소프트웨어를 서로 다르게 설계 및 패키징해야 하며, 이러한 프로세스를 통상적으로 컨테이너화라고 합니다.

애플리케이션을 컨테이너화하는 경우, 해당 프로세스에는 이와 관련된 환경 변수, 구성 파일, 라이브러리 및 소프트웨어 종속 항목과 함께 애플리케이션을 패키징하는 작업이 포함됩니다. 최종 결과물은 이후에 컨테이너 플랫폼에서 실행될 수 있는 컨테이너 이미지입니다. 자세한 정보를 얻으려면 아래의 "컨테이너화 설명" 동영상(08:09)을 시청하세요.

 
- 예를 들어, Root Base 인 Container 가 있다고 생각하고 이후에 파생된 Container 에서는 B형태 C형태 등으로 일부 소프트웨어가 구분되어 사용되어 질 수 있다. 그러니까 일일히 하나씩 쪼갠다고 생각하면 된다.
 

Kubernetes의 컨테이너 오케스트레이션

기업들이 종종 최신형 클라우드 네이티브 아키텍처의 일부로서 컨테이너를 채택하기 시작하면서, 개별 컨테이너의 단순성은 분산형 시스템에서 수백 개(심지어 수천 개)의 컨테이너를 관리하는 복잡성과 충돌하기 시작했습니다.

이러한 문제를 해결하기 위해, 다음을 포함하여 해당 라이프사이클 전체에서 방대한 볼륨의 컨테이너를 관리하는 방법으로서 컨테이너 오케스트레이션이 부상하게 되었습니다.

  • 프로비저닝
  • 중복성
  • 상태 모니터링
  • 리소스 할당
  • 스케일링 및 로드 밸런싱
  • 물리적 호스트 간의 이동

이러한 문제들의 해결을 지원하고자 많은 컨테이너 오케스트레이션 플랫폼(예: Apache Mesos, Nomad 및 Docker Swarm)이 구축되었지만, 2014년에 Google에서 소개한 오픈 소스 프로젝트인 Kubernetes는 순식간에 가장 인기 있는 컨테이너 오케스트레이션 플랫폼의 자리를 차지했습니다. 또한 이는 업계의 대부분이 관련 표준화 작업을 수행하는 플랫폼이기도 합니다.

Kubernetes를 이용하여 개발자와 운영자는 YAML 파일을 통해 전체 컨테이너 환경의 원하는 상태를 선언할 수 있습니다. 그 이후에는, Kubernetes가 해당 애플리케이션이나 워크로드에 대한 지정된 수의 인스턴스 배치, 실패 시에 해당 애플리케이션의 재부팅, 로드 밸런싱, 자동 스케일링, 제로 다운타임 배치 등을 포함한 활동을 통해 해당 상태를 설정 및 유지하는 온갖 힘든 작업들을 수행합니다.

Kubernetes에 대해 자세히 알아보려면, Kubernetes의 개요에 대한 Sai Vennam의 설명을 제시하는 아래의 동영상(10:59)을 시청하세요.

 

 

Kubernetes는 현재 Linux Foundation의 후원을 받는 벤더 애고니스틱 산업 그룹인 CNCF(Cloud Native Computing Foundation)에서 운영하고 있습니다.

 

 

Istio, Knative 및 확장형 컨테이너 에코시스템

컨테이너가 애플리케이션의 패키징과 실행을 위한 인기 있는 방법으로 지속적으로 성장함에 따라, 프로덕션 유스케이스를 강화하고 확장하도록 설계된 툴과 프로젝트의 에코시스템 또한 지속적인 증가세를 보이고 있습니다. Kubernetes 외에도, 컨테이너 에코시스템에서 가장 인기 있는 2개의 프로젝트는 바로 Istio 및 Knative입니다.

 

- K8s 이외에 가장 인기있는게 저거 2개라고 하는데 솔직히 잘 모르겠다.

 

 

Istio

개발자가 컨테이너를 활용하여 마이크로서비스 아키텍처를 빌드하고 실행하므로, 관리 문제는 이제 개별 컨테이너의 라이프사이클 고려사항을 벗어나서 엄청난 수의 소형 서비스(이를 종종 "서비스 메시"라고 함)들이 서로 간에 연결되고 서로 간에 관련되는 방식으로 이동하고 있습니다. Istio는 개발자가 검색, 트래픽, 모니터링, 보안 등과의 연관된 문제들을 보다 손쉽게 관리할 수 있도록 만들어졌습니다. Istio에 대해 자세히 알아보려면, "Istio의 정의"를 살펴보고 아래의 "Istio 설명" 동영상(05:06)을 시청하세요.

 

 

Knative

서버리스 아키텍처 역시 특히 클라우드 네이티브 커뮤니티 내에서 지속적으로 늘어나는 인기를 실감하고 있습니다. Knative의 엄청난 가치는 컨테이너형 서비스를 서버리스 기능으로 배치할 수 있는 능력입니다.

항상 실행되면서 필요 시에 응답(서버 작동과 유사함)하는 대신, 서버리스 기능은 "제로(0)로 스케일링"될 수 있습니다. 즉, 이는 호출되지 않는 한 전혀 실행되지 않습니다. 수만 개의 컨테이너에 적용되는 경우, 이 모델은 엄청난 양의 컴퓨팅 파워를 절감할 수 있습니다.

Knative에 대해 자세히 알아보려면 아래의 "Knative 정의" 동영상(07:58)을시청하세요.

 

+ Recent posts