#24 simple sns: caching with redis
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user