[Spring Security] - 로그인을 진행해보자

yeom yaloo·2023년 6월 13일
0
post-thumbnail
post-custom-banner

들어가기에 앞서서

단일 서버로 구성된 프로젝트에서의 spring security 사용과 jwt, redis 사용

  • 단일 서버에서 진행하는 spring security authentication 과정과 jwt 적용 그리고 상태 유지를 위한 redis 저장 작업입니다.
  • spring security - authentication 확인
  • 확인된 인증 사용자라면? jwt 발급, 해당 jwt를 redis 저장
  • 확인되지 않은 사용자라면? 다시 회원 로그인 시도하게 하기

분산 서버라면?

  • 조금 더 복잡해진다.
    • 예를 들어 Front(사용자 요청을 받는 곳, 사용자에게 정보를 노출하는 곳), Auth(사용자의 인증, 인가 작업을 진행하는 곳), API(Rest API를 담당하는 곳) 서버로 나뉜 MSA 구조로 해당 프로젝트를 진행한다고 하고 설명을 하자면...
  • front server(client 요청을 받는 서버)에서 http request가 넘어오면 해당 정보를 auth server로 넘겨 해당 인증 작업을 위임해야 한다.
  • auth server에서 해당 요청 정보를 가지고 인증된 객체라면 jwt발급과 redis 저장 작업을 진행해준다.
  • 이때 API server를 따로 두고 해당 회원 정보를 가진 DB를 이곳에서 관리하면 auth server는 userDetailsService작업을 진행할 때 해당 회원 정보를 API server에게 요청해 받아와야 한다.
  • 또한 Front 서버에서는 Interceptor 작업으로 인가가 필요한 모든 작업에 해당 인가 확인 인터셉터를 등록해서 사용해야 한다.(jwt 토큰 재발급도 이렇게 가능)
  • API 서버에 어떤 요청을 보낼 때 Front서버에서는 인가와 관련된 header 설정을 진행해서 해당 API 사용시에 인가 정보를 넘겨줄 수 있게 한다.

0. 라이브러리 추가

0.1 jwt을 java로 사용하기 위해서 아래 세가지 추가

        <!--jjwt java json web token-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
        </dependency>

0.2 Redis 사용을 위한 라이브러리 추가


        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </dependency>

1. AuthenticationFilter

  • 해당 작업에서 미인증 authentication 객체를 AuthenticationManager에게 위임하고 이를 처리한다.

1.1 attemptAuthentication() 작성

import com.spring.security.practice.springsecuritypractice.auth.jwt.JwtProvider;
import com.spring.security.practice.springsecuritypractice.member.exception.InvalidLoginRequestException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

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.Date;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;

@Slf4j
public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/auth/login", "POST");
    private boolean postOnly = true;

    private final AuthenticationManager authenticationManager;
    private final JwtProvider jwtProvider;


    private static final String AUTHENTICATION = "Authentication ";
    private static final String PREFIX_BEARER = "Bearer ";
    private static final String EXPIRE = "Expire ";

    public CustomAuthenticationFilter(AuthenticationManager authenticationManager, JwtProvider jwtProvider) {
        super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
        this.authenticationManager = authenticationManager;
        this.jwtProvider = jwtProvider;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

        String loginId = request.getParameter("loginId");
        String password = request.getParameter("password");

        if (!this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        else if (Objects.isNull(loginId) || Objects.isNull(password) || loginId.isBlank() || password.isBlank()){
            throw new InvalidLoginRequestException();
        }

        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginId, password);

        return authenticationManager.authenticate(authenticationToken);
    }
}
  • 해당 작업은 로그인 폼에서 넘어온 회원 아이디와 비밀번호를 받아서 해당 정보로 다음 authenticationManager에게 넘겨줄 authentication 객체를 만들어 넘겨준다.
  • 로그인 폼을 사용하는 경우라면 UsernamePasswordAuthenticatioFilter를 사용해도 괜찮으나 해당 클래스가 상속받아 사용한 AbstractAuthenticationProcessingFilter를 상속 받아 사용해도 무방하다
  • 이때 반환값인 Authentication 객체로는 UsernamePasswordAuthenticationToken을 넘겨주면 된다.

1.2 현재까지의 security configuration

import com.spring.security.practice.springsecuritypractice.auth.filter.CustomAuthenticationFilter;
import com.spring.security.practice.springsecuritypractice.auth.filter.JwtAuthenticationFilter;
import com.spring.security.practice.springsecuritypractice.auth.jwt.JwtAuthenticationProvider;
import com.spring.security.practice.springsecuritypractice.auth.jwt.JwtProvider;
import com.spring.security.practice.springsecuritypractice.auth.jwt.JwtFailureHandler;
import com.spring.security.practice.springsecuritypractice.member.service.UserDetailsServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
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.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;


/**
 * 스프링 시큐리티와 관련해서 환경 설정을 진행하는 클래스입니다.
 */
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfiguration {


    @Resource(name = "jwtProvider")
    private final JwtProvider jwtProvider;
    private final RedisTemplate<String,Object> redisTemplate;

    private final UserDetailsServiceImpl userDetailsService;
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.csrf().disable();
        http.cors().disable();

        http.formLogin()
                .loginPage("/members/login")
                .usernameParameter("loginId")
                .usernameParameter("password")
                .successForwardUrl("/");

        http.headers().frameOptions().sameOrigin();

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }


    private AbstractAuthenticationProcessingFilter customAuthenticationFilter() throws Exception {
        CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManager(null), jwtProvider);
 
 		//Filter 적용 url
    	customAuthenticationFilter.setFilterProcessesUrl("/auth/login"); 
       //인증 실패시 작동할 FailurHandler
       customAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
        
        return customAuthenticationFilter;
    }


    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return new JwtFailureHandler();

    }


    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder =
                http.getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder.authenticationProvider(authenticationProvider());
        return authenticationManagerBuilder.build();
    }
    
    @Bean
    public AuthenticationProvider authenticationProvider() {
        return new JwtAuthenticationProvider(userDetailsService, bCryptPasswordEncoder());
    }
	/**
     * 비밀번호 평문 저장을 방지하기 위한 엔코더 빈등록
     * */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
  1. AuthenticationFilter -> (AuthenticationToken) -> AuthenticationManager: http reques(요청)으로 넘어원 회원 아이디, 비밀번호를 Authentication 객체로 만들어서 다음 작업으로 해당 객체를 넘겨준다.
  2. AuthenticationManager -> (AuthenticationToken, 단순 위임) -> AuthenticationProvider: 실질적으로 DB에서 해당 회원 정보를 불러와서 이 정보가 request할 때 보내준 정보와 맞는지를 확인하는 작업을 진행)

2. AuthenticationManager

2.1 AuthenticationManger Bean 등록

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder =
                http.getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder.authenticationProvider(authenticationProvider());
        return authenticationManagerBuilder.build();
    }
  • security configuration에 해당 빈을 등록한 뒤 Filter적용에 해당 authenticationManager를 주입해준다.

3. AuthenticationProvider와 UserDetailsService

3.1 UserDetailsService

  • 실제 DB에 접근해서 해당 회원이 존재할 때 그 정보를 가지고 User 객체를 AuthenticationProvider에게 돌려주는 서비스로직
import com.spring.security.practice.springsecuritypractice.member.domain.entity.Member;
import com.spring.security.practice.springsecuritypractice.member.persistence.inter.QueryMemberRepository;
import com.spring.security.practice.springsecuritypractice.member.persistence.inter.QueryMemberRoleRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;


@RequiredArgsConstructor
@Repository
public class UserDetailsServiceImpl implements UserDetailsService {

    private final QueryMemberRepository queryMemberRepository;
    private final QueryMemberRoleRepository queryMemberRoleRepository;


    /**
     * @param username http request에서 넘어온 회원 정보로 이 정보를 이용해서 db에 실제 데이터가 있는지를 찾아온다.
     * */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        Member member = queryMemberRepository.findMemberByLoginId(username);
        List<String> roles = queryMemberRoleRepository.findMemberRoleByMemberLoginId(username);

        if (Objects.isNull(member)){
            throw new RuntimeException("not found member");
        }

        User user = new User(member.getLoginId(),
                member.getPassword(),
                roles.stream()
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList())
        );

        return user;
    }
}
  • username - loginId
  • password - password
  • 해당 작업을 진행하기 위해서 username으로 받은 값은 loginId 라는 컬럼과 비교해야하고 이때 해당 loginId와 일치하면 해당 회원 객체를 넘겨준다.
  • 또한 유저의 역할을 따로 저장해둔 테이블도 존재하기 때문에 해당 loginId를 가지고 있는 회원 객체의 역할들도 모두 넘겨줄 수 있게 한다.
  • User 객체를 최종적으로 넘어온 정보로 만들어서 넘겨준다.

3.2 UserDetailsService에서 사용할 Repository 메소드

  • Jpa + QueryDsl을 사용해 구현
package com.spring.security.practice.springsecuritypractice.member.persistence.impl;

import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.spring.security.practice.springsecuritypractice.member.domain.dto.response.MemberResponse;
import com.spring.security.practice.springsecuritypractice.member.domain.entity.Member;
import com.spring.security.practice.springsecuritypractice.member.domain.entity.QMember;
import com.spring.security.practice.springsecuritypractice.member.persistence.inter.QueryMemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Constructor;


@Repository
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class QueryMemberRepositoryImpl implements QueryMemberRepository {

    private final JPAQueryFactory jpaQueryFactory;
    @Override
    public Member findMemberByLoginId(String loginId) {

        QMember member = QMember.member;

        return jpaQueryFactory.selectFrom(member).where(member.loginId.eq(loginId))
                .fetchOne();
    }
}
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.spring.security.practice.springsecuritypractice.member.domain.entity.QMemberRole;
import com.spring.security.practice.springsecuritypractice.member.persistence.inter.QueryMemberRoleRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;


@Repository
@RequiredArgsConstructor
public class QueryMemberRoleRepositoryImpl implements QueryMemberRoleRepository {

    private final JPAQueryFactory jpaQueryFactory;

    @Override
    public List<String> findMemberRoleByMemberLoginId(String loginId) {
        QMemberRole memberRole = QMemberRole.memberRole;
        return jpaQueryFactory.select(memberRole.role.roleName)
                .from(memberRole).where(memberRole.member.loginId.eq(loginId)).fetch();
    }
}

3.3 AuthenticationProvider


import com.spring.security.practice.springsecuritypractice.member.service.UserDetailsServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Objects;


@RequiredArgsConstructor
@Slf4j
public class JwtAuthenticationProvider implements AuthenticationProvider {

    private final UserDetailsServiceImpl userDetailsService;
    private final BCryptPasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication auth) throws AuthenticationException {


        String loginId = auth.getName();
        String password = (String) auth.getCredentials();

        UserDetails user = userDetailsService.loadUserByUsername(loginId);


        if(Objects.isNull(user)) {
            throw new BadCredentialsException("user id not found!");
        }
        else if (!this.passwordEncoder.matches(password, user.getPassword())){
            throw new BadCredentialsException("password is not matches");
        }

        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                loginId,
                null,
                user.getAuthorities()
        );
        return authenticationToken;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}
  • UserDetailsService에서 일치하는 회원이 있다면 User 객체를 가져와서 해당 객체가 가지고 있는 비밀번호와 입력받은 비밀번호가 같은지 확인해준다.
  • 해당 작업에 BCrytPasswordEncoder를 사용한 이유는 비밀번호를 평문저장 하지 않기 때문이다. (회원 등록시 입력받은 비밀번호를 encoder를 사용해서 저장하기 때문이다.)
  • 패스워드까지 일치하는 회원이라면 AuthenticationToken을 다시 AuthenticationManager에게 넘겨주는 작업을 진행한다.
  • 이때 Credential을 null값을 넘겨주는 이유는 해당 객체의 비밀번호가 노출되면 안 되는 민감 정보이기 때문이다.

4. AuthenticationManger

  • AuthenticationProvider에서 넘겨받은 정보를 가지고 다시 AuthenticationFilter로 넘겨준다

5. AuthenticationFilter

5.1 인증 성공 - successfulAuthentication()

@Override
protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {

        log.info("=============================== successful authentication =================================");
        List<String> roles = authResult.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
        String loginId = authResult.getName();

        final String USER_UUID = UUID.randomUUID().toString();
        String accessToken = jwtProvider.createAccessToken(loginId, roles);
        String refreshToken = jwtProvider.createRefreshToken(loginId, roles);
        Date date = jwtProvider.extractExpiredTime(accessToken);

        response.addHeader(AUTHENTICATION, PREFIX_BEARER+accessToken);
        response.addHeader(EXPIRE, date.toString());
        response.addHeader(UUID_HEADER.getValue(), USER_UUID);

    }
  • 해당 메소드가 실행된다.
  • jwt 발급과 해당 jwt redis에 저장 인증 정보 Response header 설정을 진행하여준다.

6. JwtProvider

package com.spring.security.practice.springsecuritypractice.auth.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;
import java.util.List;


@Component
public class JwtProvider {


    private static final long ACCESS_TOKEN_EXPIRED_TIME = 1000L * 60 * 60; // 1시간
    private static final long REFRESH_TOKEN_EXPIRED_TIME = 1000L * 60L * 60L * 24L * 7L; // 7일


    @Value("${jwt.secretKey}")
    private String jwtSecretKey;


    private Key getSecretKey() {
        byte[] keyBytes = jwtSecretKey.getBytes(StandardCharsets.UTF_8);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    public String createToken(String loginId, List<String> roles, long expiredTime){

        Claims claims = Jwts.claims().setSubject(loginId);
        claims.put("roles", roles);
        Date date = new Date();

        Key secretKey = getSecretKey();

        String jwt = Jwts.builder()
                .setSubject(loginId)
                .setIssuedAt(date)
                .setExpiration(new Date(date.getTime() + expiredTime))
                .signWith(SignatureAlgorithm.HS512, secretKey)
                .compact();

        return jwt;
    }

    public String createAccessToken(String loginId, List<String> roles){
        return createToken(loginId, roles, ACCESS_TOKEN_EXPIRED_TIME);
    }

    public String createRefreshToken(String loginId, List<String> roles){
        return createToken(loginId, roles, REFRESH_TOKEN_EXPIRED_TIME);

    }

    public String extractLoginId(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSecretKey())
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getSubject();

    }

    public Date extractExpiredTime(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSecretKey())
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getExpiration();
    }



}

7. Redis 설정과 RedisTemplate 설정

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration
@Getter
public class RedisConfiguration {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.database}")
    private int database;


    /**
     * Redis Connection 설정 bean
     * @return 레디스 설정을 한 레디스 구현체 Lettuce 입니다.
     * */

    @Bean
    public RedisConnectionFactory redisConnectionFactory(){
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();

        configuration.setHostName(this.host);
        configuration.setPort(this.port);
        configuration.setDatabase(this.database);
        configuration.setPassword(this.password);

        return new LettuceConnectionFactory(configuration);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(){

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());

        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper()));

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper()));

        return redisTemplate;
    }


    /**
     * ObjectMapper에서 LocalDateTime 관련 에러가 나지 않게 설정해준 뒤 빈으로 등록해서 사용
     * */
    @Bean
    public ObjectMapper objectMapper(){

        ObjectMapper objectMapper = new ObjectMapper();

        objectMapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
        objectMapper.registerModules(new JavaTimeModule(),
                new Jdk8Module());
        return objectMapper;

    }

}

8. 정리

  • 해당 숫자 흐름대로 (6번, 7번 빼고) 확인하면 spring security가 어떤 흐름으로 해당 인증(authentication) 작업을 하는지 알수 있다.
  • 단일 서버에서는 어떻게 어디서 jwt발급을 하고 해당 정보를 redis에 저장하면 되는지만 고민하면 된다.
  • authenticationFilter와 jwt 발급과 관련해서 authenticationFilter를 따로 구현해도 된다.
    • 필터 등록만 잘해주면 됨

해당 작업은 인증과 관련된 작업!!!!!!! 인가 관련 작업은 다음에 진행

profile
즐겁고 괴로운 개발😎
post-custom-banner

0개의 댓글