スプリング起動-スプリング安全セッション


Spring BootでRedisをバインドする練習項目を作成しましょう!

スプリング安全性の基本概念


ダウンジャケットの整理本を参考にしてください!
https://github.com/namusik/TIL-SampleProject/blob/main/Spring%20Boot/%EC%8A%A4%ED%94%84%EB%A7%81%20%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0/%EC%8A%A4%ED%94%84%EB%A7%81%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0%20%EA%B0%9C%EB%85%90.md

ソースコード


https://github.com/namusik/TIL-SampleProject/tree/main/Spring%20Boot/%EC%8A%A4%ED%94%84%EB%A7%81%20%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0/%EC%8A%A4%ED%94%84%EB%A7%81%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0%20%EC%84%B8%EC%85%98%EB%B0%A9%EC%8B%9D

作業環境

IntelliJ
Spring Boot
java 11
gradle

build.gradle

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-thymeleaf:2.6.3'
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'mysql:mysql-connector-java'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
}
スプリングセキュリティ、h 2データベース、および時間軸依存性が追加されました.

application.properties

spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:springminiprojectdb
spring.datasource.username=sa
spring.datasource.password=
簡単なh 2をメンバー情報を保存するDBとして使用する.
(ただしサーバーがシャットダウンするとデータベースも消えます!)

WebSecurityConfig


スプリングのセキュリティ依存性を追加すると、WebSecurityConfigurator Adapterクラスが実行されます.
ここではセキュリティの初期化と設定を担当します.
WebSecurityConfigurator Adapterを継承するカスタム構成を個人プロジェクトに基づいて作成すればよい.
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder encoderPassword() {
        return new BCryptPasswordEncoder();
    }

    //스프링시큐리티 앞단 설정 해주는 곳.
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

    //스프링시큐리티의 설정을 해주는 곳
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //URL 인증여부 설정.
        http.authorizeRequests()
                .antMatchers("/images/**", "/user/signup").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated();

        //로그인 관련 설정.
        http.formLogin()
                .loginPage("/user/login")
                .loginProcessingUrl("/user/login")
                .defaultSuccessUrl("/")
                .failureUrl("/user/login?error")
                .and()
                .logout()
                .logoutUrl("/user/logout")
                .logoutSuccessUrl("/")
                .permitAll();
    }
}
Annotation
@EnableWebSecurity
Security 활성화 Annotation. 

클릭해보면 @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class,HttpSecurityConfiguration.class })이 달려서 
해당 Class들을 실행시켜줌.

또한, @AuthenticationPrincipal을 통해 Authentication 객체 속에 있는 principal 필드 가져올 수 있음.
@EnableGlobalMethodSecurity(securedEnabled=true)
Controller에 직접 @Secured("ROLE_ADMIN")을 쓸 수 있게 됨.
ここで待って!HttpSecurity permitAll()とWebSecurity ingnoring()の違いは何ですか?
WebSecurity는 Spring Security Filter Chain을 아예 거치지 않기에

"인증", "인가" 모두 적용되지 않음. 또한, XSS에 대한 보호가 제공안됨. 

HttpSecurity보다 우선 적용되기 때문에 두곳에 모두 설정해도 WebSecurity만 적용됨.


HttpSecurity는 "인증"을 무시함. Security Filter Chain을 거쳐서 요청이 들어옴.
HttpSecurtiy
!!重要なのは、HttpSecurityオブジェクトを通してスプリングの安全に各種設定をすることです!!
URLアクセス権の設定
http.authorizeRequests()
	.antMatchers("/login", "/web-resources/**")
    뒤에 써준 URL(리소스)에 대해 권한을 설정
    
    .antMatchers("/login", "/web-resources/**").permitAll()
    해당 URL에는 인증절차(로그인) 없이 접근허용
    
    .antMathcers("/admin/**").hasAnyRole("ADMIN")
	해당 URL에는 ADMIN레벨 권한을 가진 사용자만 접근허용
    
    .anyRequest().authenticated();
    나머지 URL은 모두 스프링시큐리티 인증을 거쳐야됨을 설정.
ログイン設定
http.formLogin()
일반적인 로그인 방식, 로그인 Form페이지와 로그인 성공/실패 처리 등을 사용하겠다는 의미.
	
    .loginPage("/user/login") Get방식
    로그인 페이지로 넘어가는 Get 방식 Controller URI.
    따로 설정해주지않으면 Default값인 "/login"로 설정되어있는 기본로그인페이지 제공됨. 
    
    .loginProcessUrl("/user/login") Post방식.
    해당 Controller URI로 요청을 할 경우 자동으로 스프링시큐리티 로그인 인증과정이 시작되도록 설정.
    
    .defaultSuccessUrl("/") Get방식 
    로그인 성공시 요청하는 Controller URI
    설정하지 않는경우 "/"값이 defaultf로 설정됨.
    
    .successHandler(new AuthenticationSuccessHandlerImpl("/main"))
    로그인 성공했을 때 추가 로직을 해주기 위한 커스텀 핸들러. 
    AuthenticationSuccessHanlder의 구현체.
    
    .failureUrl("/user/login?error")
    로그인 실패했을 때 요청하는 Controller URI
    
    .failureHandler(new AuthenticationFailureHandlerImpl("/login/fail)
    로그인 실패했을 때 추가 로직을 위한 커스텀 핸들러 설정.

RedisConfig

@Configuration
public class RedisConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory();
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(ChatMessage.class));
        return redisTemplate;
    }
}
オブジェクトはJson形式でValueに格納されるため、ValueSerializerをJacksonにプリセットするRedisTemplateが作成されます.
StringRedisTemplateはbeanにしなくても使えます.

RedisService

	@Service
    @RequiredArgsConstructor
    public class RedisService {

        private final StringRedisTemplate stringRedisTemplate;

        public void setRedisStringValue(ChatMessage chatMessage) {
            ValueOperations<String, String> stringValueOperations = stringRedisTemplate.opsForValue();
            stringValueOperations.set("sender", chatMessage.getSender());
            stringValueOperations.set("context", chatMessage.getContext());
        }

        public void getRedisStringValue(String key) {

            ValueOperations<String, String> stringValueOperations = stringRedisTemplate.opsForValue();
            System.out.println(key +" : " + stringValueOperations.get(key));
        }
    }
    
        public void setRedisValue(ChatMessage chatMessage) {
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        String key = chatMessage.getSender();
        valueOperations.set(key, chatMessage);
    }

        public void getRedisValue(String key) {
            ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
            ChatMessage chatMessage = (ChatMessage) valueOperations.get(key);
            System.out.println("sender = " + chatMessage.getSender());
            System.out.println("context = " + chatMessage.getContext());
        }
Redisサービスクラス
RedisTemplateはget/set用のオブジェクトです.
templateのvalueOperationオブジェクトを受信して使用します.
Spring Bootでは、次のRedisTemplateが自動的に生成されます.
@Autowired RedisTemplate redisTemplate; 
@Autowired StringRedisTemplate stringRedisTemplate; 
@Autowired ReactiveRedisTemplate reactiveRedisTemplate; 
@Autowired ReactiveStringRedisTemplate reactiveStringRedisTemplate;
redisTemplateとstringRedsiTemplateはシリアル化に差がある
stringRedisTemplateは、文字列固有のテンプレートを提供します.ほとんどのrediskey-valueは文字列を主としているからです.
RedisTemplateは、JavaオブジェクトをRedisに保存するために使用します.
setRedisStringValue(ChatMessage ChatMessage)を介して送信されるメッセージオブジェクトをそれぞれRedisに設定します.
getRedisStringValue(Strin Key)出力で正しく保存されているかどうか.
setRedisValue(ChatMessage chatMessage)RedisTemplateを使用してJavaオブジェクト自体をvalueに格納する

RedisController

@RestController
@RequiredArgsConstructor
public class RedisController {

    public final RedisService redisService;

    @PostMapping("api/redisStringTest")
    public String send(@RequestBody ChatMessage chatMessage) {
        redisService.setRedisStringValue(chatMessage);

        redisService.getRedisStringValue("sender");
        redisService.getRedisStringValue("context");

        return "success";
    }
    
    @PostMapping("api/redisTest")
    public String send(@RequestBody ChatMessage chatMessage) {
        redisService.setRedisValue(chatMessage);

        String key = chatMessage.getSender();
        redisService.getRedisValue(key);

        return "success";
    }
}
作成者依存性インジェクションにRedisServiceをインポートしてJSON形式でオブジェクトを渡す場合は、set/getを一度に実行してオブジェクトを最終出力するコントローラです.
両方の方法の結果は同じです

実行結果


Postmanを使用してapiを実行します.

成功した場合は「success」を返します.

保存したChatMessageのsenderとcontextの出力に成功しました

リファレンス


https://kimchanjung.github.io/programming/2020/07/03/spring-security-03/
https://velog.io/@seongwon97/security
https://bcp0109.tistory.com/303