728x90

Spring 프레임워크의 핵심 기술에는 다음과 같은 것들이 있습니다.

  1. Inversion of Control (IoC)

Spring의 IoC는 객체 생성, 관리, 의존성 주입 등의 작업을 자동으로 수행하여 개발자가 비즈니스 로직에 집중할 수 있도록 합니다. 스프링에서는 빈(Bean)이라는 개념을 도입하여 객체를 생성하고 관리합니다.

  1. Aspect-Oriented Programming (AOP)

Spring의 AOP는 다양한 애플리케이션에서 공통적으로 발생하는 문제들을 해결하기 위해 사용됩니다. AOP는 메소드 호출 전후에 수행되는 작업(어드바이스)과 해당 메소드를 정의한 포인트컷(Pointcut)을 조합하여 프로그램의 여러 부분에서 재사용할 수 있는 기능을 제공합니다.

  1. Spring MVC

Spring MVC는 Model-View-Controller 아키텍처 패턴을 기반으로 한 웹 애플리케이션 개발을 위한 프레임워크입니다. 스프링 MVC는 각 요청에 대한 처리를 컨트롤러(Controller)에서 담당하며, 모델(Model)과 뷰(View)를 분리하여 개발의 유연성을 높여줍니다.

  1. Spring Data

Spring Data는 다양한 데이터베이스를 쉽게 다룰 수 있는 기술을 제공합니다. Spring Data는 JPA, MongoDB, Redis 등의 데이터베이스에 대한 지원을 제공합니다.

  1. Spring Security

Spring Security는 웹 애플리케이션의 보안을 담당하는 프레임워크입니다. 스프링 시큐리티는 인증(Authentication)과 권한 부여(Authorization)를 처리하며, 보안에 관련된 다양한 기능을 제공합니다.

  1. Spring Integration

Spring Integration은 다양한 시스템 간의 메시지 통합을 위한 프레임워크입니다. 스프링 인티그레이션은 다양한 프로토콜을 지원하며, 여러 시스템 간의 데이터 통합을 용이하게 해줍니다.


내저장소 바로가기 luxury515

'Springboot3.0 > 핵심기능' 카테고리의 다른 글

Spring AOP에 관련하여.  (0) 2023.04.15
Spring IoC 에 관하여.  (0) 2023.04.15
Spring Security 와 Siro 에 대한 비교  (0) 2023.04.15
Redis를 이용한 refresh token  (0) 2023.04.11
Redis를 이용한 중복요청 방지  (0) 2023.04.11
728x90

들어가면서

IoC(Inversion of Control, 제어의 역전)는 객체 지향 프로그래밍에서 객체 생성과 의존성 관리 등을 개발자가 직접 제어하는 것이 아니라 프레임워크나 컨테이너가 제어하는 것을 말합니다.

Spring 프레임워크에서의 IoC는 객체의 생성과 소멸, 의존성 주입 등을 Spring 컨테이너가 제어하도록 합니다. 이를 통해 객체간의 결합도를 낮출 수 있고, 유연하고 확장 가능한 코드를 작성할 수 있습니다.

IoC종류 뭐가 있을까?

Spring Framework에서는 다음과 같은 종류의 IOC(Inversion of Control) 컨테이너가 있습니다.

  1. BeanFactory
  1. ApplicationContext
  1. WebApplicationContext
  • BeanFactory는 가장 기본적인 IOC 컨테이너로서, Bean 객체의 생성, 관리, 제공에 대한 기능을 제공합니다.
  • ApplicationContext는 BeanFactory를 상속받아 기능을 보완한 것으로서, AOP, 메시지 처리 등 다양한 기능을 지원합니다.
  • WebApplicationContext는 웹 어플리케이션에서 사용하는 IOC 컨테이너로서, HTTP 요청에 대한 처리와 관련된 Bean 객체를 관리합니다.

여러가지 설정방법

Spring Framework에서는 다양한 환경에서 사용할 수 있는 다양한 IOC 컨테이너를 제공합니다. 예를 들어, XML, 어노테이션, 자바 설정 파일 등을 이용해서 Bean 객체를 등록하고 관리하는 방법을 지원합니다. 이러한 다양한 방법을 통해 유연하게 IOC 컨테이너를 사용할 수 있습니다.

아래는 Spring IoC를 사용한 간단한 코드입니다.

public interface UserService {
    void addUser(User user);
}

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void addUser(User user) {
        userDao.addUser(user);
    }
}

public interface UserDao {
    void addUser(User user);
}

public class UserDaoImpl implements UserDao {
    public void addUser(User user) {
        // user를 DB에 저장하는 코드
    }
}

위 코드에서 UserServiceImpl 클래스는 UserDao 클래스에 의존하고 있습니다. 하지만 IoC를 사용하면 UserDao 클래스를 생성하고 UserServiceImpl 객체에 주입하는 작업을 Spring 컨테이너가 대신합니다.

<bean id="userDao" class="com.example.UserDaoImpl" />
<bean id="userService" class="com.example.UserServiceImpl">
    <property name="userDao" ref="userDao" />
</bean>

위 XML 설정 파일에서 UserDaoImpl 클래스를 userDao 빈으로 등록하고, UserServiceImpl 클래스를 userService 빈으로 등록하면 Spring 컨테이너는 userDao 빈을 생성한 후에 userService 빈을 생성하면서 userDao 빈을 주입합니다.

어떤 장단은 뭐가 있을까?

IoC를 사용하면 객체 생성과 의존성 관리를 Spring 프레임워크가 대신 처리하기 때문에 개발자는 객체 생성과 관련된 코드를 작성하지 않아도 되고, 객체간의 결합도가 낮아져 유연하고 확장 가능한 코드를 작성할 수 있습니다.

Spring 프레임워크에서 IoC는 DI(Dependency Injection, 의존성 주입)라는 방식으로 구현됩니다. DI는 객체간의 의존성을 외부에서 주입하는 방식으로, 객체간의 결합도를 낮출 수 있습니다. Spring 컨테이너는 DI를 사용하여 객체를 생성하고 의존성을 주입합니다.

따라서, Spring 프레임워크에서 IoC를 사용하면 유연하고 확장 가능한 코드를 작성할 수 있으며, 객체간의 결합도를 낮출 수 있습니다.


내저장소 바로가기 luxury515

'Springboot3.0 > 핵심기능' 카테고리의 다른 글

Spring AOP에 관련하여.  (0) 2023.04.15
Spring의 핵심요소  (0) 2023.04.15
Spring Security 와 Siro 에 대한 비교  (0) 2023.04.15
Redis를 이용한 refresh token  (0) 2023.04.11
Redis를 이용한 중복요청 방지  (0) 2023.04.11
728x90

Introduction

Spring Security는 애플리케이션 보안에 대한 표준 기능을 제공하는 프레임워크입니다. 최근 Spring Security 5.7 버전이 출시되면서 이전 버전과 다른 점이 몇 가지 있다는 것을 알아보겠습니다. 또한, Spring Security와 Siro의 차이점과 기능 및 성능 비교에 대해서도 알아볼 것입니다.

Spring Security와 Siro 차이점 및 기능, 성능 비교

Spring Security와 Siro는 모두 보안 프레임워크입니다. 그러나 두 프레임워크는 목적이 다릅니다. Spring Security는 다양한 인증 및 권한 부여 방식을 제공하며, Siro는 주로 RBAC(Role-Based Access Control)를 지원합니다. 또한, Spring Security는 많은 기능을 제공하며, 설정이 복잡할 수 있습니다. 반면, Siro는 간단한 설정으로 사용할 수 있지만, 기능이 제한될 수 있습니다. 성능 측면에서도 Spring Security가 더 우수합니다.

다음은 Spring Security와 Siro의 기능 및 성능을 비교한 표입니다.

Spring SecuritySiro
인증 방식다양한 인증 방식 제공기본적으로 Form 인증
권한 부여 방식다양한 권한 부여 방식 제공주로 RBAC 지원
설정의 복잡성높음낮음
기능많음제한적
성능우수보통

Spring Security 5.7 이후 버전 설정 방법 및 예시 코드

다음은 Spring Security 5.7 이전 버전에서 JWT 인증을 사용하는 방법의 예시 코드입니다.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtTokenFilter jwtTokenFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/**").authenticated()
            .and()
            .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService());
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager(
            User.builder()
                .username("user")
                .password(passwordEncoder().encode("password"))
                .roles("USER")
                .build()
        );
    }
}

5.7 이후 버전 아래 형식으로 바뀜

참고: https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());
        return http.build();
    }
		@Bean
		    public WebSecurityCustomizer webSecurityCustomizer() {
		        return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
		    }

}

Postman으로 JWT 토큰 발급 과정 구체적인 코드 작성

Postman을 사용하여 JWT 토큰 발급을 구현하기 위해서는 먼저 Spring Security 설정에서 JWT 인증을 사용해야 합니다. JWT 토큰을 생성하고 발급하는 방법은 다음과 같습니다.

@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {

    Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                    loginRequest.getUsername(),
                    loginRequest.getPassword()
            )
    );

    SecurityContextHolder.getContext().setAuthentication(authentication);

    String jwt = tokenProvider.generateToken(authentication);
    return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}

MySQL DB 설정과 application.yml 파일 설정

Spring Boot에서는 application.yml 또는 application.properties 파일을 사용하여 애플리케이션 설정을 관리합니다. MySQL DB를 사용하는 경우, application.yml 파일은 다음과 같이 설정할 수 있습니다.

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/dbName
    username: dbUser
    password: dbPassword
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

ORM으로 JPA 사용하기

Spring Boot에서는 JPA를 사용하여 ORM(Object-Relational Mapping)을 구현할 수 있습니다. JPA는 Java Persistence API의 약자로, 객체와 관계형 데이터베이스 간의 매핑을 처리합니다. JPA를 사용하는 경우, 다음과 같이 의존성을 추가하고, Entity 클래스를 작성하고, Repository 인터페이스를 작성하면 됩니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    // getters and setters
}

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

Conclusion

Spring Security 5.7 이후 버전에서는 여러 개선 사항이 추가되어 있습니다. 또한, Spring Security와 Siro의 차이점과 기능 및 성능 비교를 통해 각각의 장단점을 알아봤습니다. JWT 토큰 발급 과정과 MySQL DB 설정, JPA 사용 방법에 대해서도 자세히 알아보았습니다. 이를 토대로 Spring Security를 보다 효율적으로 사용할 수 있을 것입니다.


내저장소 바로가기 luxury515

'Springboot3.0 > 핵심기능' 카테고리의 다른 글

Spring의 핵심요소  (0) 2023.04.15
Spring IoC 에 관하여.  (0) 2023.04.15
Redis를 이용한 refresh token  (0) 2023.04.11
Redis를 이용한 중복요청 방지  (0) 2023.04.11
Springboot3.0 에서 kafka적용해보기  (0) 2023.04.11
728x90

Spring Boot에서 Redis를 이용한 Refresh Token 처리 방법은 다음과 같습니다.

  1. 의존성 추가
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. Redis 설정
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
  1. Refresh Token 생성 후 Redis에 저장
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.UUID;

@Component
public class RefreshTokenService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private final String PREFIX_REFRESH_TOKEN = "refresh_token:";

    // Refresh Token 만료시간 (1주일)
    private final Duration REFRESH_TOKEN_EXPIRATION = Duration.ofDays(7);

    public String createRefreshToken() {
        String refreshToken = PREFIX_REFRESH_TOKEN + UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(refreshToken, "", REFRESH_TOKEN_EXPIRATION);
        return refreshToken;
    }
}
  1. 1. Access Token 만료 시, Refresh Token을 이용하여 새로운 Access Token을 생성.
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Date;

@Component
public class AccessTokenService {

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private final String PREFIX_REFRESH_TOKEN = "refresh_token:";

    // Access Token 만료시간 (1시간)
    private final Duration ACCESS_TOKEN_EXPIRATION = Duration.ofHours(1);

    public String createAccessToken(String refreshToken) throws Exception {
        if (!redisTemplate.hasKey(refreshToken)) {
            throw new Exception("Invalid Refresh Token");
        }

        String subject = ""; // Access Token의 subject
        Date now = new Date();
        Date expiredAt = new Date(now.getTime() + ACCESS_TOKEN_EXPIRATION.toMillis());

        redisTemplate.expire(refreshToken, Duration.ZERO);

        String accessToken = Jwts.builder()
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expiredAt)
                .signWith(SignatureAlgorithm.HS256, jwtSecret)
                .compact();

        return accessToken;
    }
}

예시 코드는 Redis에 Refresh Token만 저장하고, Access Token은 JWT로 생성합니다.

이외에도 Redis를 이용하여 Access Token까지 저장할 수 있습니다.

  1. Redis에 Access Token과 Refresh Token을 저장합니다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.UUID;

@Component
public class TokenService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private final String PREFIX_ACCESS_TOKEN = "access_token:";
    private final String PREFIX_REFRESH_TOKEN = "refresh_token:";

    // Access Token 만료시간 (1시간)
    private final Duration ACCESS_TOKEN_EXPIRATION = Duration.ofHours(1);

    // Refresh Token 만료시간 (1주일)
    private final Duration REFRESH_TOKEN_EXPIRATION = Duration.ofDays(7);

    public String createAccessToken() {
        String accessToken = PREFIX_ACCESS_TOKEN + UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(accessToken, "", ACCESS_TOKEN_EXPIRATION);
        return accessToken;
    }

    public String createRefreshToken() {
        String refreshToken = PREFIX_REFRESH_TOKEN + UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(refreshToken, "", REFRESH_TOKEN_EXPIRATION);
        return refreshToken;
    }

    public void deleteToken(String accessToken) {
        String refreshToken = accessTokenToRefreshToken(accessToken);
        redisTemplate.delete(accessToken, refreshToken);
    }

    public String accessTokenToRefreshToken(String accessToken) {
        return accessToken.replace(PREFIX_ACCESS_TOKEN, PREFIX_REFRESH_TOKEN);
    }

    public boolean isValidRefreshToken(String refreshToken) {
        return redisTemplate.hasKey(refreshToken);
    }

    public boolean isValidAccessToken(String accessToken) {
        return redisTemplate.hasKey(accessToken);
    }
}
  1. Access Token의 만료시간을 Redis에서 확인합니다.
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component
public class JwtService {

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Autowired
    private TokenService tokenService;

    // Access Token 만료시간 (1시간)
    private final long ACCESS_TOKEN_EXPIRATION_TIME = 3600000L;

    public String createAccessToken() {
        String accessToken = tokenService.createAccessToken();

        String subject = ""; // Access Token의 subject
        Date now = new Date();
        Date expiredAt = new Date(now.getTime() + ACCESS_TOKEN_EXPIRATION_TIME);

        String refreshToken = tokenService.createRefreshToken();

        // Redis에 Access Token과 Refresh Token 저장
        RedisTemplate<String, String> redisTemplate = tokenService.getRedisTemplate();
        redisTemplate.opsForValue().set(accessToken, refreshToken);
        redisTemplate.expire(accessToken, ACCESS_TOKEN_EXPIRATION_TIME);
        redisTemplate.opsForValue().set(refreshToken, accessToken);
        redisTemplate.expire(refreshToken, tokenService.getREFRESH_TOKEN_EXPIRATION_TIME());

        String jwt = Jwts.builder()
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expiredAt)
                .signWith(SignatureAlgorithm.HS256, jwtSecret)
                .compact();

        return jwt;
    }

    public String refreshToken(String refreshToken) throws Exception {
        if (!tokenService.isValidRefreshToken(refreshToken)) {
            throw new Exception("Invalid Refresh Token");
        }

        String accessToken = tokenService.accessTokenToRefreshToken(refreshToken);
        if (!tokenService.isValidAccessToken(accessToken)) {
            throw new Exception("Invalid Access Token");
        }

        // Access Token의 만료시간을 Redis에서 확인
        RedisTemplate<String, String> redisTemplate = tokenService.getRedisTemplate();
        long expiration = redisTemplate.getExpire(accessToken);

        if (expiration < 0) {
            throw new Exception("Expired Access Token");
        }

        String newAccessToken = createAccessToken();

        // 기존 Access Token을 삭제하고 새로운 Access Token과 연결된 Refresh Token 저장
        redisTemplate.delete(accessToken);
        redisTemplate.opsForValue().set(newAccessToken, refreshToken);
        redisTemplate.expire(newAccessToken, ACCESS_TOKEN_EXPIRATION_TIME);
        redisTemplate.opsForValue().set(refreshToken, newAccessToken);
        redisTemplate.expire(refreshToken, tokenService.getREFRESH_TOKEN_EXPIRATION_TIME());

        return newAccessToken;
    }

}

위의 코드에서는 Redis에 저장된 Access Token의 만료시간을 확인하여 만료된 Access Token인 경우 새로운 Access Token을 생성합니다. 그리고 기존 Access Token을 삭제하고 새로운 Access Token과 연결된 Refresh Token을 Redis에 저장합니다.

위 코드를 참고하여 Redis를 이용하여 Access Token과 Refresh Token을 저장하고, Access Token의 만료시간을 확인하는 코드를 작성해보세요.


내저장소 바로가기 luxury515

+ Recent posts