티스토리 뷰

728x90
반응형

개발 환경

java 21.0.4 2024-07-16 LTSJava(TM) SE Runtime Environment (build 21.0.4+8-LTS-274)

Java HotSpot(TM) 64-Bit Server VM (build 21.0.4+8-LTS-274, mixed mode, sharing)

 

STS4


스프링 시큐리티(Spring Security) 설정

"Spring Starter Project" 생성시 "Spring Secutiry"를 추가해 준다.


스프링 시큐리티(Spring Security) 설정 확인

pom.xml의 dependency 체크

<dependency>
	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

 

console의 password 체크

Tomcat 실행시 security password가 생성된다.


스프링 시큐리티(Spring Security) 테스트

HomeController 추가

package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {
	
	@GetMapping("/hello")
	public String hello() {
		
		return "hello";
	}

}

 

호출(인증 정보 미설정)

스프링 시큐리티를 설정하지 않았다면 "hello"라는 텍스트가 출력이 되었을것이다.

하지만 현재 스프링 시큐리티를 설정한 상태이므로 인증 과정을 정상적으로 통과해야 "hello" 텍스트가 출력이 된다.


호출(인증 정보 설정)

인증을 위한 Auth: Username, Password를 설정한 후

호출을 하면 "200 OK" 와 함께 "hello" 텍스트가 정상적으로 출력이 된다.


HTTP Basic 인증이란?

스프링 시큐리티의 HTTP Basic 인증은 웹 애플리케이션에서 가장 단순한 형태의 인증 방식 중 하나입니다. 

이 방식은 HTTP 요청 헤더에 사용자 이름과 비밀번호를 Base64로 인코딩하여 전송하는 방식입니다.

인증 방식

클라이언트는 서버에 접근할 때 Authorization 헤더에 인코딩된 자격 증명(사용자 이름과 비밀번호)을 포함해 전송합니다.

보안 취약점
비암호화

인증 정보가 암호화되지 않고 Base64로만 인코딩되어 있기 때문에 네트워크 상에서 탈취되기 쉽습니다. 

HTTPS(SSL/TLS)를 사용하지 않으면 보안상 취약합니다.


한 번만 요청

클라이언트는 매번 인증 정보를 보내야 하므로, 세션을 유지하지 않는 간단한 애플리케이션에 적합하지만 

더 복잡한 인증에는 부적합합니다.


 

 



권한 부여 구성 재정의

.permitAll() = 인증 필요 없이 접근이 가능

.authendicated = 인증 필요

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ProjectConfig {

    @Bean
    UserDetailsService userDetailsSeervice() {
		var userDetailsService = new InMemoryUserDetailsManager();
		var user = User.withUsername("james")
				.password("12345")
				.authorities("read")
				.build();
		
		userDetailsService.createUser(user);
		return userDetailsService;
	}

    @Bean
    PasswordEncoder passwordEncoder() {
		return NoOpPasswordEncoder.getInstance();
	}

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((requests) -> requests
                .requestMatchers("/", "/home", "/hello").permitAll() // 인증 필요 없음
                .anyRequest().authenticated() //인증 필요
            )
            .formLogin((form) -> form
                .loginPage("/login")
                .permitAll()
            )
            .logout((logout) -> logout.permitAll());

        return http.build();
    }

}

AuthenticationProvider 구현 재정의

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ProjectConfig {
	
	@Bean
    UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("james")
                .password(passwordEncoder().encode("12345"))
                .roles("USER")
                .build());
        
        return manager;
    }

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

    // 사용자 정의 인증 공급자 (Authentication Provider)
    @Bean
    CustomAuthenticationProvider customAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        return new CustomAuthenticationProvider(userDetailsService, passwordEncoder);
    }
    
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults());  // HTTP Basic 인증 활성화
        
        return http.build();
    }
    
}
package com.example.demo.config;

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.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
	
	private final UserDetailsService userDetailsService;
    private final PasswordEncoder passwordEncoder;

    public CustomAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = String.valueOf(authentication.getCredentials());

        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        if (passwordEncoder.matches(password, userDetails.getPassword())) {
            return new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
        } else {
            throw new BadCredentialsException("Invalid username or password");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

}

테스트를 위한 포스맨 다운로드
https://www.postman.com/downloads/

 

Download Postman | Get Started for Free

Try Postman for free! Join 30 million developers who rely on Postman, the collaboration platform for API development. Create better APIs—faster.

www.postman.com

 

 

728x90
반응형