#24 simple sns: caching with redis

This commit is contained in:
haerong22
2022-12-02 03:27:53 +09:00
parent 1d8218d491
commit b479560d3e
7 changed files with 108 additions and 13 deletions

View File

@@ -23,6 +23,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'com.vladmihalcea:hibernate-types-52:2.17.3'
compileOnly 'org.projectlombok:lombok'

View File

@@ -6,6 +6,7 @@ import com.example.sns.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -24,14 +25,14 @@ public class AuthenticationConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().regexMatchers("^(?!/api/).*");
web.ignoring().regexMatchers("^(?!/api/).*")
.antMatchers(HttpMethod.POST, "/api/*/users/join", "/api/*/users/login");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/*/users/join", "/api/*/users/login").permitAll()
.antMatchers("/api/**").authenticated()
.and()
.sessionManagement()

View File

@@ -0,0 +1,41 @@
package com.example.sns.config;
import com.example.sns.model.User;
import io.lettuce.core.RedisURI;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableRedisRepositories
@RequiredArgsConstructor
public class RedisConfig {
private final RedisProperties redisProperties;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisURI redisURI = RedisURI.create(redisProperties.getUrl());
RedisConfiguration configuration = LettuceConnectionFactory.createRedisConfiguration(redisURI);
LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration);
factory.afterPropertiesSet();
return factory;
}
@Bean
public RedisTemplate<String, User> userRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, User> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(User.class));
return redisTemplate;
}
}

View File

@@ -1,8 +1,11 @@
package com.example.sns.model;
import com.example.sns.model.entity.UserEntity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@@ -11,8 +14,10 @@ import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
@Getter
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class User implements UserDetails {
private Integer id;
@@ -36,26 +41,31 @@ public class User implements UserDetails {
}
@Override
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority(this.getUserRole().toString()));
}
@Override
@JsonIgnore
public boolean isAccountNonExpired() {
return this.deletedAt == null;
}
@Override
@JsonIgnore
public boolean isAccountNonLocked() {
return this.deletedAt == null;
}
@Override
@JsonIgnore
public boolean isCredentialsNonExpired() {
return this.deletedAt == null;
}
@Override
@JsonIgnore
public boolean isEnabled() {
return this.deletedAt == null;
}

View File

@@ -0,0 +1,36 @@
package com.example.sns.repository;
import com.example.sns.model.User;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
import java.time.Duration;
import java.util.Optional;
@Slf4j
@Repository
@RequiredArgsConstructor
public class UserCacheRepository {
private final RedisTemplate<String, User> userRedisTemplate;
private final static Duration USER_CACHE_TTL = Duration.ofDays(3);
public void setUser(User user) {
String key = getKey(user.getUsername());
log.info("Set User to Redis {}, {}", key, user);
userRedisTemplate.opsForValue().set(key, user, USER_CACHE_TTL);
}
public Optional<User> getUser(String username) {
String key = getKey(username);
User user = userRedisTemplate.opsForValue().get(key);
log.info("Get User from Redis {}, {}", key, user);
return Optional.ofNullable(user);
}
private String getKey(String username) {
return "USER:" + username;
}
}

View File

@@ -5,6 +5,7 @@ import com.example.sns.model.Alarm;
import com.example.sns.model.User;
import com.example.sns.model.entity.UserEntity;
import com.example.sns.repository.AlarmEntityRepository;
import com.example.sns.repository.UserCacheRepository;
import com.example.sns.repository.UserEntityRepository;
import com.example.sns.util.JwtTokenUtils;
import lombok.RequiredArgsConstructor;
@@ -24,6 +25,7 @@ public class UserService {
private final UserEntityRepository userEntityRepository;
private final AlarmEntityRepository alarmEntityRepository;
private final BCryptPasswordEncoder encoder;
private final UserCacheRepository userCacheRepository;
@Value("${jwt.secret-key}")
private String secretKey;
@@ -32,10 +34,12 @@ public class UserService {
private Long expiredTimeMs;
public User loadUserByUsername(String username) {
return userEntityRepository.findByUsername(username).map(User::fromEntity)
.orElseThrow(
() -> new SnsApplicationException(USER_NOT_FOUND, String.format("%s not founded", username))
);
return userCacheRepository.getUser(username).orElseGet(() ->
userEntityRepository.findByUsername(username).map(User::fromEntity)
.orElseThrow(
() -> new SnsApplicationException(USER_NOT_FOUND, String.format("%s not founded", username))
)
);
}
@Transactional
@@ -55,13 +59,12 @@ public class UserService {
public String login(String username, String password) {
// 회원가입 여부 체크
UserEntity userEntity = userEntityRepository.findByUsername(username)
.orElseThrow(
() -> new SnsApplicationException(USER_NOT_FOUND, String.format("%s not founded", username))
);
User user = loadUserByUsername(username);
userCacheRepository.setUser(user);
// 비밀번호 체크
if (!encoder.matches(password, userEntity.getPassword())) {
if (!encoder.matches(password, user.getPassword())) {
throw new SnsApplicationException(INVALID_PASSWORD);
}

View File

@@ -21,6 +21,9 @@ spring:
platform: postgres
driver-class-name: org.postgresql.Driver
redis:
url: redis://default:czGScHDINUmn78dFTZn0Bs5qa55BegQm@redis-19917.c8.us-east-1-2.ec2.cloud.redislabs.com:19917
jwt:
secret-key: simple_sns_application_secret_key
token: