[Spring Security] 스프링 시큐리티 기초 -1

Junho Bae·2021년 2월 26일
5

Spring Security

목록 보기
1/2

인프런 백기선님의 스프링 시큐리티 강의를 듣고 제가 나중에 보려고 정리해 놓습니다.
인프런 백기선의 스프링 시큐리티

Srping Security

chapter 1-1. 폼 인증 예제

1) 프로젝트 기본

principal이 있다면 -> model.addAttribute
/dashboard는 로그인 페이지로 리다이렉트 -> WebSecurityConfig에서 설정

2) 설정파일 작성

http.fromLogin();
http.httpBasic();

chapter 1-2, 1-3. web application

@Controller
public class SampleController {

    @GetMapping("/")
    public String index(Model model, Principal principal) {

        if(principal == null) {
            model.addAttribute("message","Hello Spring Security"); //key-value
        }
        else  {
            model.addAttribute("message","Hello" + principal.getName()); //key-value
        }
        return "index";
    }

    @GetMapping("/info")
    public String info(Model model) {
        model.addAttribute("message","Info page"); //key-value
        return "info";
    }

    @GetMapping("/dashboard") //로그인을 한 사용자만 접근이 가능하기 때문에 Principal이 있어야함.
    public String dashboard(Model model, Principal principal) {
        model.addAttribute("message","Hello" + principal.getName()); //key-value
        return "dashboard";
    }


    @GetMapping("/admin")
    public String admin(Model model, Principal principal) {
        model.addAttribute("message","Hello Admin" + principal.getName()); //key-value
        return "admin";
    }
 

chapter 1-4. Spring Security 연동하기

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.1.6.RELEASE</version>
        </dependency>
  • 문제 1. 인증을 할 방법이 없었다 : 해결
  • 문제2. 인증을 할 방법이 없으니 사용자를 알 수 없다: 해결
  • 스프링 시큐리티의 자동설정 : id user , pw는 콘솔에 확인
  • 어플리케이션에 스프링 시큐리티 만 연동해서 쓸 수 있느냐?
    - 유저가 한명뿐
    - 패스워드도 맨날 바뀜
    - 패스워드가 로그에 남는다? 미침? 절대로 로그에는 보안 관련된 정보를 남기면 안됨.

정리
스프링 부트의 도움을 받아 의존성을 추가할 수 있다.

  • 그러면 모든 요청은 인증을 필요로함 : 원래계획읜 root, info는 누누가
  • 기본 유저가 생성됨 : user, pw는 콘솔에 생성
    해결된 문제 : 인증을 할 수 있고 현재 사용자 정보를 알 수 있다
    새로운 문제 : 인증없이 접근 가능한 url을 설정하고 싶음. 이 어플리케이션을 사용할 수 있는 유저 계정이 그럼 하나 뿐? 비밀번호가 로그에 남음?

chapter 1-5. 스프링 시큐리티 설정하기

@Configuration
@EnableWebSecurity //이게 나중에 뭣들인지 나중에 설명해드릴게유
public class SecurityCionfig extends WebSecurityConfigurerAdapter {


    @Override //이런 식으로 오버라이딩 해서 설정함.
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .mvcMatchers("/","info").permitAll()
                .mvcMatchers("/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and().formLogin() // and로 메서드 체이닝을 안해도됨.
                .and()
                .httpBasic();
        /*
        and에서 끊고
        http.formLogin();
        http.httpBasic();
        해도 됨.
         */

    }
}
  • 이런 식으로 info, root는 아무나 들어갈 수 있게.
  • admin은 “ADMIN”이라는 롤이 있어야 들어갈 수 있게
  • 다른 모든 요청은 인증을 해야 들어갈 수 있게

1-5. 인메모리 유저 추가하기

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                //쉽게 이 값들이 디비에 들어가는 값. 디비에 드어갈 때는 암호화를 하잖아? 그떄 이 앞에 어떤 방법으로 암호화를 하는지 적어놓는게 noop. 여기서 noops는 암호화 하지 않았다는 의미.
                .withUser("junho").password("{noop}123").roles("USER").and() //noop는 스프링 시큐리티 5부터 사용되는 암호화 인코딩 방식. 입력받은 값을 prefix에 따라서 인코딩한다.
                .withUser("admin").password("{noop}!@#").roles("ADMIN");
        //선언한 방식으로 인코딩을 해서 확인을 해줄 수 있음.
    } 
    
  • 유저를 추가하면 auth를 받는 configure를 오버라이딩
  • 여기서 auth.하면서 만들면 됨.

1-6. jpa 연동 - 인메모리에서 유저를 관리하지 않고, 디비에서 관리를 하는 방법. 그중 jpa를 사용해서 연동하는 방법.

1) 의존성, h2 database 추가
2)엔티티 생성.
@Entity
public class Account {

    @Id @GeneratedValue
    private Integer id;

    @Column(unique = true)
    private String username;


    private  String password;

    private String role;
	
	/*
	getter, setter
	setter가 있어야 @ModelAttribute 사용 가능
	*/
	
	/*
	스프링 시큐리티에는 비밀번호에 정해진 포멧이 있음. 패스워드 인코더를 빈으로 등록해서 아래와 같이 사용. {bcrypt}비밀번호 이런 식으로 사용됨. 디폴트가 비크립트 방식. sha256도 사용 가능함.
	*/

    public void encodePassword(PasswordEncoder passwordEncoder) {
        //이것도 패스워드 인코더가 이미 있음.
        this.password = passwordEncoder.encode(this.password);
    }
}
3) 레퍼지토리 생성
/*
스프링 jpa가 해주는, 인터페이스만 만들어도 구현체를 빈으로 등록해줌
리퍼지토리만 만들었지만, 어카운트 리펕지토리라는 빈이 있고 구현체가 등록되어 있는 것 처럼 사용
*/

public interface AccountRepository extends JpaRepository<Account, Integer> {

    Account findByUsername(String username);
}
4) account controller 작성
@RestController //레스트 컨트롤러니까 제이슨으로 볼 수 있겠지.
public class AccountController {

    @Autowired
    AccountService accountService;

    @GetMapping("/account/{role}/{username}/{password}")
    //url 에 있는 세개의 키워드를 받아서 account 객체로 바운딩
    public Account creasteAccount(@ModelAttribute Account account) {
        return accountService.createNew(account);
        //바인딩 된 값을 저장.
        //return accountRepository.save(account);
    }
}
  • 기능들 service에서 구현
5) account service 작성
/*
account 관련 비즈니스 로직을 처리하는 구현체
그중 하나가 유저디테일서비스의 역할을 하게 될 것입니다.
하나의 역할만 있으면 단일책임 원칙에 의해서, 하지만 이 일도 어카운트 서비스
일부라고 생각하기 때문에.
*/


/*
UserDetailsService는 authentication을 관리할 때, DAO 인터페이스를 통해서 저장소에 들어있는 유저정보를 가져오고 인증할 때 사용하는 인터페이스

물론 이 구현체를 쓴다고 해서 모든 데이터가 관계형 디비에 들어있어야 한다거나 하는 건 아님. 구현을 어떻게 할 것인지는 순전히 우리의 책임

*/

@Service
public class AccountService implements UserDetailsService {

    @Autowired
    PasswordEncoder passwordEncoder;

    @Autowired AccountRepository accountRepository;

    //유저네임을 받아와서 유저네임에 해당하는 유저 정보를 데이터에서 가져와서 유저 디테일즈라는 타입으로 리턴해줘야 함.
    @Override
    public UserDetails loadUserByUsername(String usernamae) throws UsernameNotFoundException {

        //스프링 시큐리티는 양식이 필요함.

        //일단 꺼내오고
        Account account = accountRepository.findByUsername(usernamae);

        if(account == null) {
            throw new UsernameNotFoundException(usernamae);
        }

        //스프링 시큐리는 유저라는 클래스를 제공, 유저 클래스의 빌드를 이용하면 유저 디테일이라는 타입의 객체를 만들 수 있음.
        //이게 없던 시절에는 유저 디테일즈 타입의 클래스를 구현해서, Account <-> 유저디테일서비스 의 어뎁터를 하는 클래스를 구현하면 됨.

        return User.builder()
                .username(account.getUsername())
                .password(account.getPassword())
                .roles(account.getRole())
                .build();

    }

    public Account createNew(Account account) {
        account.encodePassword(passwordEncoder);
        return this.accountRepository.save(account);
    }
}
  • 그러니까 쉽게 생각하면, 맞는지 아닌지는 모르겠지만, 스프링 시큐리티에서 login()을 하기 위해서는 loadUserByUsername()이라는 메서드를 호출하고 그 결과 반환되는 UserDetails타입의 객체가 필요한듯 -> spring 아키텍쳐에서 확인할 수 있듯이, UsernamePasswordAuthenticationFileter에서 AuthenticationManager의 구현체인 ProviderManager에서 이 메서드가 쓰여서 이걸로 auth가 만들어지고 이제 SecurityHolder에 들어가는거였음.
  • 그래서 loadUserByUsername을 오버라이딩 해서, 우리의 디비 리퍼지토리에 접근하고, 거기서 가져온 다음에 User.builder()를 통해 UserDetails 타입의 객체로 반환하는듯. 이건 맞음
  • 따라서 필요한 경우에는 UserDetailsImpl을 만들어서 반환하거나, User Class를 상속해서 만들면 되는 듯.
6) SecurityConfig 수정
@Configuration
@EnableWebSecurity //이게 나중에 뭣들인지 나중에 설명해드릴게유
public class SecurityCionfig extends WebSecurityConfigurerAdapter {

    @Autowired
    AccountService accountService;

    @Override //이런 식으로 오버라이딩 해서 설정함.
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .mvcMatchers("/","/info","/account/**").permitAll()
                .mvcMatchers("/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and().formLogin() // and로 메서드 체이닝을 안해도됨.
                .and()
                .httpBasic();
        /*
        and에서 끊고
        http.formLogin();
        http.httpBasic();
        해도 됨.
         */

    }

    /*
    인메모리는 필요없음.
     */

    @Override
    //명시적으로 알려주는 거기는 한데 안해도 되기는 함.
    //하지만 유저 디테일 서비스의 구현체가 빈으로 등록되어 있기만 해도, 알아서 써줌.
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //여기다가 우리가 사용하는 유저 디테일 서ㅏ비스가 익서다. 어카운트 서비스도 유저 디테일 서비스의 구현체니까.
        auth.userDetailsService(accountService);
    }
}
  • account/ 이후의 오는 모든 타입의 유알엘 허용
    -AuthenticationManagerBuilder 이 친구가 이제 인증을 관리하는 친구 auth.
  • auth야, 우리 이제 유저 디테일 서비스로는 우리가 확장한 accountService를 쓸거야! 라고 명시적으로 말 해줘도 되고, 안해줘도됨
7) 마지막, 암호화
@SpringBootApplication
public class DemoApplication {

    //이걸 이렇게 바꿔주기만 하면 패스워드 인코딩을 할 수 있음. 서비스에서 인코딩할 때 쓰면 됨.
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
        //이게 다양한 패스워드 인코딩을 지원함.
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}
  • 패스워드 인코더를 빈으로 등록해서 사용하기.

1-8. 테스트하기

스프링 시큐리티 테스트.

profile
SKKU Humanities & Computer Science

0개의 댓글