63일: spring security 코드 분해

Jiwontwopunch·2022년 2월 14일
0

국비기록

목록 보기
63/121
post-thumbnail

2022.02.14.Mon.

✍ 복습

spring security : MVC 모드

  • 인증 authentication : 로그인
    @PreAuthorize("isAnonymous()") 회원가입 페이지
    @PreAuthorize("isAuthenticated()") 글쓰기
  • 인가 authorization : 권한, 인증된 다음
    @Secured("ROLE_USER")
    @Secured("ROLE_ADMIN")
    @Secured({"ROLE_UESR", "ROLE_ADMIN"})
  • 페이지 위조 방지 : csrf
    서버에서 페이지에 csrf token을 추가해서 사용자에게 보낸다.
    사용자는 csrf token이 불일치하거나 수명이 지났으면 403(권한없음) 처리.
    사용자는 csrf token을 되돌린다. <input..............name={"_csrf" value=$(_csrf.token}">
  • 인증이나 인가별로 메뉴를 다르게 구성

springSecurityConfig.java

package com.example.demo;

import org.springframework.beans.factory.annotation.*;
import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.method.configuration.*;
import org.springframework.security.config.annotation.web.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;
import org.springframework.security.crypto.password.*;

@Configuration
@EnableWebSecurity
// @PreAuthorize, @PostAuthorize, @Secured 어노테이션은 기본 비활성화되어 있다
@EnableGlobalMethodSecurity(prePostEnabled=true, securedEnabled=true)
// WebSecurityConfigurerAdapter : 스프링 시큐리티 설정을 기본 구현한 중간 단계 추상 클래스
public class SpringSecurityConfig2 extends WebSecurityConfigurerAdapter {
	@Autowired
	private PasswordEncoder passwordEncoder;
	
	// 스프링 시큐리티가 동작하지 않을 경로나 파일등을 설정 -> css, image, js 등의 경로를 지정
	// 부트 프로젝트는 /resource/static에 정적 파일이 들어간다고 정해져있기 때문에 불필요한 설정
	// @Override
	// public void configure(WebSecurity web) throws Exception {
	// 	super.configure(web);
	// }
	
	// 스프링 시큐리티에 대한 설정
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.formLogin().loginPage("/sample2/login").loginProcessingUrl("/sample2/login")
				.usernameParameter("username").passwordParameter("password")
				.defaultSuccessUrl("/").failureUrl("/sample2/login?error")
				
				// 에러페이지를 지정했을 때 404나 405가 뜨면 csrf를 의심하자
				// (csrf 오류로 403이 뜨고 그 403을 처리할 에러페이지 지정 오류로 다시 404나 405가 발생할 수 있다) 
			.and().exceptionHandling().accessDeniedPage("/sample2/error")
			.and().logout().logoutUrl("/sample2/logout").logoutSuccessUrl("/");
	}
	
	// 인증 매니저에 대한 설정
	// 스프링 시큐리티에 DB 작업용 UserDetailsService 인터페이스가 있어서 인터페이스를 구현하면 그걸 기반으로 인증 매니저가 생성된다
	// 아직 DB를 사용하지 않기 때문에 테스트용 유저를 담은 인증 매니저를 직접 만들겠다
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
			.withUser("spring").password(passwordEncoder.encode("1234")).roles("USER")
			.and()
			.withUser("system").password(passwordEncoder.encode("1234")).roles("ADMIN")
			.and()
			.withUser("admin").password(passwordEncoder.encode("1234")).roles("USER","ADMIN");
		// 스프링 시큐리티에서 권한은 단순 문자열
		// 따라서 "ROLE_ADMIN이 ROLE_USER를 포함한다"와 같은 권한 중첩의 개념이 없다
		// 즉 사용자와 권한은 1:다 관계이다
	}
}
  • @EnableWebSecurity
    스프링시큐리티 사용을 위한 어노테이션 선언
  • @EnableGlobalMethodSecurity(prePostEnabled=true, securedEnabled=true)
    스프링 시큐리티의 메소드 어노테이션 기반 시큐리티를 활성화 하기 위해서 필요
    securedEnabled = true 로 설정하면 @Secured 어노테이션 사용 가능
  • WebSecurityConfigurerAdapter : 스프링 시큐리티 설정을 기본 구현한 중간 단계 추상 클래스
  • public class SpringSecurityConfig2 extends WebSecurityConfigurerAdapter
    WebSecurityConfigurerAdapter을 상속
  • protected void configure(HttpSecurity http) throws Exception {
    http
    WebSecurityconfigurerAdapter가 제공, 사용자 인증이 된 요청에 대해서만 요청을 허용, 사용자는 폼기반 로그인으로 인증할 수 있다. 사용자는 HTTP 기반 인증으로 인증할 수 있다.
  • .FormLogin()
    화면을 보여주고 아이디와 비밀번호를 입력하는 전통적인 로그인화면 - csrf 자동 활성화
  • .loginPage()
    로그인 페이지를 보여줄 주소(get)
  • .loginProcessingUrl()
    로그인을 처리할 주소(post)
  • .usernameParameter()
    login.html에서 아이디를 입력받을 name
  • .passwordParameter()
    login.html에서 비밀번호를 입력받을 name
  • .defaultSuccessUrl()
    로그인에 성공하면 어느 페이지로 이동?
  • .failureUrl()
    로그인에 실패하면 어느 페이지로 이동?
  • http.exceptionHandling().accessDeniedPage("/sample2/error");
    권한 오류 403에 대한 설정으로 403이 발생하면 /sample/error로 이동해라
  • .logout()
    logout 관련 설정을 진행할 수 있도록 돕는 LogoutConfigurer<> class를 반환
  • .logoutUrl()
    client에서 SpringSecurity에게 logout을 요청하기 위한 url을 설정하는 메소드
  • .logoutSuccessUrl()
    로그아웃 성공 시 사용자가 redirect될 url을 지정하는 메소드
  • AuthenticationManagerBuilder
    AuthenticationManagerBuilder를 통해 인증 객체를 만들 수 있도록 제공 즉, 사용자 아이디, 비밀번호, 권한등을 관리
  • 테스트에 사용할 사용자를 생성 user, sys, admin 각 사용자별로 권한을 설정.

springSecurityController.java

package com.example.demo.controller;

import org.springframework.security.access.annotation.*;
import org.springframework.security.access.prepost.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

@Controller
public class SampleController2 {
	// 로그인 처리는 스프링 시큐리티가 담당
	// 로그인 페이지도 /login 경로로 스프링 시큐리티가 제공한다
	// 로그인 여부로 메소드에 접근할 수 있는 지 설정 : @PreAuthorize, @PostAuthorize
	// 권한으로 메소드에 접근할 수 있는 지 설정 : @Secured
	// 위 어노테이션들은 메소드에 지정할 수도 있고 클래스에 지정할 수도 있다
	@PreAuthorize("isAnonymous()")
	@GetMapping("/sample2/login")
	public void login() {
	}
	
	// 누구나 접근 가능
	@GetMapping({"/", "/sample2/list"})
	public String list() {
		return "sample2/list";
	}
	
	@Secured("ROLE_USER")
	@GetMapping("/sample2/user")
	public void user() {
	}
	
	@Secured("ROLE_ADMIN")
	@GetMapping("/sample2/admin")
	public void admin() {
	}
	
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/sample2/authenticated")
	public void authenticated() {
	}
	
	// 어노테이션은 클래스의 일종. 클래스처럼 어노테이션도 필드를 가질 수 있다
	// 모든 어노테이션은 value 필드를 가진다
	@PreAuthorize("isAnonymous()")
	@GetMapping(value="/sample2/anonymous")
	public void anonymous() {
	}
	
	// 시큐리티 설정에 의해 403(forbidden, 권한없음)일 경우 /sample2/erro로 이동 -> 아래 메소드가 없으면 404
	@GetMapping(value="/sample2/error")
	public void error403() {
	}
}
  • @PreAuthorize(),@Secured()
    권한 설정이 필요한 위치에 어노테이션을 추가해 주면 권한 별로 접근을 통제할 수 있다.

login.html 로그인 폼

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
<title>Insert title here</title>
</head>
<body>
	<!--
		로그인 폼 
		- 로그인을 처리하는 주소 : /sample/login
		- 아이디 : username이란 이름으로 spring
		- 비밀번호 : password란 이름으로 1234
	-->
	
	<!--
		스프링 시큐리티 MVC 방식에서 모든 post 요청은 csrf를 이용해 위변조를 확인하다
		서버가 보낸 csrf 토큰을 클라가 되돌리면 서버는 토큰의 유효성(값, 수명)을 검사한다  
	-->
	<div th:text="${_csrf.token}"></div>
	<form action="/sample/login" method="post">
		아이디:<input type="text" name="username" value="spring">
		비밀번호:<input type="password" name="password" value="1234">
		<input type="hidden" name="_csrf" th:value="${_csrf.token}">
		<button>로그인</button>
	</form>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<title>Insert title here</title>
<script>
	$(function() {
		$('#login').click(function() {
			location.href = "/sample2/login";
		});
		$('#logout').click(function() {
			const $form = $('<form>').attr('action','/sample2/logout').attr('method','post').appendTo($('body'));
			
			// 폼 로그인은 csrf를 활성화한다 -> MVC 방식에서 화면 위변조를 잡아내기 위해 사용하는 기술
			// 서버가 보내준 csrf 문자열 토큰을 서버로 재전송하면 서버에서 확인
			// get 방식은 csrf 개념이 없다(get은 서버상태 변경 X, post는 서버상태 변경)
			
			// 아래처럼 작성하면 '${_csrf.token}'를 문자열로 취급한다
			// $('<input>').attr('type','hidden').attr('name','_csrf').val('${_csrf.token}').appendTo($form);
			$('<input>').attr('type','hidden').attr('name','_csrf').val($('#csrf').text()).appendTo($form);
			$form.submit();
		});
	});
</script>
</head>
<body>
	<span th:text='${_csrf.token}' id="csrf"></span>
	
	<!-- 로그인했으면 로그아웃 버튼 표시, 안했으면 로그인 버튼 출력 -->
	<!-- 타임리프에서 스프링 시큐리티 표현식을 사용하기 위해서 라이브러리를 추가 -->
	<button sec:authorize="isAnonymous()" id="login">로그인</button>
	<button sec:authorize="isAuthenticated()" id="logout">로그아웃</button>
	
	<!-- 권한 표시 -->
	<div sec:authorize="hasRole('ADMIN')">권한 : 어드민 유저</div>
	<div sec:authorize="hasRole('USER')">권한 : 일반 유저</div>
</body>
</html>

0개의 댓글