본문 바로가기
국비학원/수업기록

국비 지원 개발자 과정_Day93

by 루팽 2023. 4. 12.

서블릿 컨테이너

1. 사용자 request

→ 2. 필터체인 - 인코딩, 세션, 로그아웃 관련 필터 등등

→ 3. DispatchServlet / 또 다른 서블릿도 존재할 수 있음 → Controller / 업무마다 하나씩 존재

→ 4. 메소드 실행

모든 리퀘스트는 필터체인을 거쳐야 서블릿에 도착한다

 

최초 경유 필터 Filter0 → FilterProxyFilter(인터셉트, 인증인가에따라 다른 서비스를 적용) → 또 다른 Filter1 → Servlet

필터체인은 한 개 이상, URL패턴에따라 다르게 설정 가능

 

<스프링 시큐리티 유저와 어드민 테스트 - SecurityConfig.java>

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity(debug=true) // 요청이 지나가는 필터정보 확인 가능
@EnableGlobalMethodSecurity(securedEnabled=true ,prePostEnabled=true) // 권한을 체크하겠다는 설정 추가
public class SecurityConfig {
	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
		AuthenticationManagerBuilder auth = 
				http.getSharedObject(AuthenticationManagerBuilder.class);
		auth.inMemoryAuthentication()
		// 아래 메소드가 데프리케이트 대상인 이유는 테스트용도로만 사용하라는 경고의 의미
		// 보안상 안전하지 않으니 쓰지말것을 당부 -  그러나 지원은 끊지 않음
		.withUser(User.withDefaultPasswordEncoder()
				.username("user")
				.password("123")
				.roles("USER")
		).withUser(User.withDefaultPasswordEncoder()
				.username("admin")
				.password("1234")
				.roles("ADMIN")
		);
		http.csrf().disable();
		http.authorizeRequests()
		// localhost:5000/user로 요청하면 403발동 - 403접근권한이 없을때
		.antMatchers("/user/**").authenticated() // 인증만 되면 들어갈 수 있는 주소
		// manager나 admin 권한이 있는 사람만 접근가능
		.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
		// admin 권한이 있는 사람만 접근가능
		.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
		.anyRequest().permitAll() // 위 세가지가 아닌 경우는 모두 허용함
		.and()
		.formLogin()
		.loginPage("/loginForm") // 2차 단위테스트 - 권한에따라서 페이지 제어
		.loginProcessingUrl("/login")
		.defaultSuccessUrl("/");
		
		return http.build();
	}// end of filterChain
}

/*
	테스트 시나리오
	localhost:5000 요청은 권한과 상관없이 열림
	localhost:5000/user
	localhost:5000/admin
	localhost:5000/manager
	로그인 페이지로 이동함
	
	첫번째 - user롤일때
	localhost:5000/user 출력되고
	localhost:5000/admin 403발동
	localhost:5000/manager 403발동
	
	두번째 - admin 로그인했을때
	lcalhost:5000/user 출력
	localhost:5000/admin 출력
	localhost:5000/manager 출력
	
	두번째 - manager 로그인했을때
	lcalhost:5000/user 출력
	localhost:5000/admin 403발동
	localhost:5000/manager 출력
*/

 

<스프링 시큐리티 유저와 어드민 테스트 - HomeController.java>

package com.example.demo.controller;

import javax.servlet.http.HttpServletRequest;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@Controller
public class HomeController {
	Logger logger = LogManager.getLogger(HomeController.class);
	
	@GetMapping("/")
	public String index(HttpServletRequest req) {
		logger.info("index");
		logger.info("admin 호출: " + req.isUserInRole("ROLE_ADMIN"));
		logger.info("user 호출: " + req.isUserInRole("ROLE_USER"));
		logger.info("manager 호출: " + req.isUserInRole("ROLE_MANAGER"));
		if(req.isUserInRole("ROLE_ADMIN")) {
			return "forward:admin-index.jsp";			
		}
		else if(req.isUserInRole("ROLE_USER")) {
			return "forward:user-index.jsp";			
		}
		else {
			return "forward:index.jsp";			
		}
	}
	
	@GetMapping("/loginForm")
	public String loginForm() {
		logger.info("loginForm");
		return "redirect:/auth/loginForm.jsp";
	}
	
	/*
	/login이 요청되면 스프링 시큐리티가 인터셉트해서 대신 로그인 진행해줌
	@GetMapping("/login")
	public @ResponseBody String login() {
		logger.info("login");
		return "로그인 후 페이지";
	}
	*/
	
	@GetMapping("/user")
	public @ResponseBody String user() {
		logger.info("user");
		return "user";
	}
	
	@GetMapping("/manager")
	public @ResponseBody String manager() {
		logger.info("manager");
		return "manager";
	}
	
	@GetMapping("/admin")
	public @ResponseBody String admin() {
		logger.info("admin");
		return "admin";
	}
	
	@GetMapping("/auth")
	public @ResponseBody Authentication auth() {
		return SecurityContextHolder.getContext().getAuthentication();
	}
}

 

<스프링 시큐리티 유저와 어드민 테스트 - application.yml>

server:
  port: 5000
  servlet:
    context-path: /
    encoding:
      charset: UTF-8
      enabled: true
      force: true  
spring:
  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
      
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Seoul
    username: root
    password: 1234
#ddl-auto: create이면 매번 테이블을 만들어준다
#create전략이면 테이블을 삭제하고 새로 만들어 준다
#그래서 맨 처음 테이블이 만들어질 때만 생성하고 다음에 실행할 때는 update로 바꾸어 주어야함    
#show-sql을 true로 주면 콘솔창에 쿼리문이 출력됨
#hibernate.format_sql: true로 주면 쿼리문이 정렬이 되어서 출력됨
#physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 이것은
#Entity를 만들때(즉 테이블을 만들때) 변수명 그대로 테이블 컬럼으로 만들어준다는 의미임

#open-in-view:true 로 주면 Lazy로드가 가능해짐. false로 하면 영속성 컨텍스트가 service계층에서 종료됨
#영속성을 프리젠테이션 계층까지 가져간다. 트랜잭션은 Service계층에서 종료된다
#Transaction이 종료된 후에도 Controller의 Session이 close 되지 않았기 때문에 영속 객체는
#Persistence 상태를 유지할 수 있으며, 따라서 프록시 객체에 대한 Lazy Loading을 수행할 수 있게 된다
#버전 2.0부터 스프링 부트는 기본적으로 OSIV가 활성화되어 있을때 경고를 발생하므로 프로덕션 시스템에
#영향을 주기 전에 이 문제를 발견할 수 있다
#서블릿 필터에서 Session을 오픈하고 트랜잭션을 시작하던 전통적인 방식의 OPEN SESSION IN VIEW 패턴과 달리
#SpringMVC에서 제공하는 OpenSessionInViewFilter 는 필터 내에서 Session은 오픈하지만 
#트랜잭션은 시작하지 않는다. 따라서 서블릿 필터 안에서는 커넥션 풀로부터 JDBC커넥션을 얻을 필요가 없다
#https://getinthere.tistory.com/27 참고하세요
  jpa:
    open-in-view: true
    hibernate:
      ddl-auto: create #create로하면 model추가시 테이블 자동생성됨. 매번생성되니 테이블생성되면 update로 바꿀것
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
      use-new-id-generator-mappings: false
    show-sql: true
    properties:
      hibernate.format_sql: true
  security:
    user:
      name: user
      password: 123
      roles: USER #인증과 인가[권한](role -> ROLE_ADMIN, ROLE_USER, ROLE_MANAGER)

 

<스프링 시큐리티 유저 MySql 입력 - User.java>

package com.example.demo.model;

import java.sql.Timestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.hibernate.annotations.CreationTimestamp;

import lombok.Builder;
import lombok.Data;

@Entity // jpa사용시 테이블 생성해줌 - 확인할것. application.yml에 create모드인지 체크
@Data // getter, setter생성해줌
public class User {
	@Id // primary key
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int id;
	private String username;
	private String password;
	private String email;
	private String role; // ROLE_USER, ROLE_MANAGER, ROLE_ADMIN
	@CreationTimestamp
	private Timestamp createDate;
	// 회원가입에 사용할 생성자 추가
	@Builder
	public User(String username, String password, String email
			, String role, Timestamp createDate) {
		this.username = username;
		this.password = password;
		this.email = email;
		this.role = role;
		this.createDate = createDate;
	}
}

 

<스프링 시큐리티 유저 MySql 입력 - UserRepository.java>

package com.example.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.demo.model.User;

// CRUD메소드는 JpaRepository가 제공함
public interface UserRepository extends JpaRepository<User, Integer> {
	public User findByUsername(String username);
}

 

<스프링 시큐리티 유저 MySql 입력 - HomeController.java>

package com.example.demo.controller;

import javax.servlet.http.HttpServletRequest;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;

@Controller
public class HomeController {
	Logger logger = LogManager.getLogger(HomeController.class);
	
	@Autowired
	private UserRepository userRepository;
	
	@GetMapping("/")
	public String index(HttpServletRequest req) {
		logger.info("index");
		logger.info("admin 호출: " + req.isUserInRole("ROLE_ADMIN"));
		logger.info("user 호출: " + req.isUserInRole("ROLE_USER"));
		logger.info("manager 호출: " + req.isUserInRole("ROLE_MANAGER"));
		if(req.isUserInRole("ROLE_ADMIN")) {
			return "forward:admin-index.jsp";			
		}
		else if(req.isUserInRole("ROLE_USER")) {
			return "forward:user-index.jsp";			
		}
		else {
			return "forward:index.jsp";			
		}
	}
	
	// 로그인 화면
	@GetMapping("/loginForm")
	public String loginForm() {
		logger.info("loginForm");
		return "redirect:/auth/loginForm.jsp";
	}
	
	/*
	/login이 요청되면 스프링 시큐리티가 인터셉트해서 대신 로그인 진행해줌
	@GetMapping("/login")
	public @ResponseBody String login() {
		logger.info("login");
		return "로그인 후 페이지";
	}
	*/
	
	// 회원가입 화면 부르기
	@GetMapping("/joinForm")
	public String joinForm() {
		logger.info("joinForm");
		return "redirect:/auth/joinForm.jsp";
	}
	
	// 회원가입
	@PostMapping("/join")
	public String join(User user) {
		logger.info("user");
		user.setRole("ROLE_USER");
		userRepository.save(user);
		return "redirect:/auth/loginForm.jsp";
	}
	
	@GetMapping("/user")
	public @ResponseBody String user() {
		logger.info("user");
		return "user";
	}
	
	@GetMapping("/manager")
	public @ResponseBody String manager() {
		logger.info("manager");
		return "manager";
	}
	
	@GetMapping("/admin")
	public @ResponseBody String admin() {
		logger.info("admin");
		return "admin";
	}
	
	@GetMapping("/auth")
	public @ResponseBody Authentication auth() {
		return SecurityContextHolder.getContext().getAuthentication();
	}
}

 

<스프링 시큐리티 유저 MySql 입력 - application.yml>

server:
  port: 5000
  servlet:
    context-path: /
    encoding:
      charset: UTF-8
      enabled: true
      force: true  
spring:
  mvc:
    view:
      prefix: /WEB-INF/views/
      suffix: .jsp
      
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Seoul
    username: root
    password: 1234
#ddl-auto: create이면 매번 테이블을 만들어준다
#create전략이면 테이블을 삭제하고 새로 만들어 준다
#그래서 맨 처음 테이블이 만들어질 때만 생성하고 다음에 실행할 때는 update로 바꾸어 주어야함    
#show-sql을 true로 주면 콘솔창에 쿼리문이 출력됨
#hibernate.format_sql: true로 주면 쿼리문이 정렬이 되어서 출력됨
#physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 이것은
#Entity를 만들때(즉 테이블을 만들때) 변수명 그대로 테이블 컬럼으로 만들어준다는 의미임

#open-in-view:true 로 주면 Lazy로드가 가능해짐. false로 하면 영속성 컨텍스트가 service계층에서 종료됨
#영속성을 프리젠테이션 계층까지 가져간다. 트랜잭션은 Service계층에서 종료된다
#Transaction이 종료된 후에도 Controller의 Session이 close 되지 않았기 때문에 영속 객체는
#Persistence 상태를 유지할 수 있으며, 따라서 프록시 객체에 대한 Lazy Loading을 수행할 수 있게 된다
#버전 2.0부터 스프링 부트는 기본적으로 OSIV가 활성화되어 있을때 경고를 발생하므로 프로덕션 시스템에
#영향을 주기 전에 이 문제를 발견할 수 있다
#서블릿 필터에서 Session을 오픈하고 트랜잭션을 시작하던 전통적인 방식의 OPEN SESSION IN VIEW 패턴과 달리
#SpringMVC에서 제공하는 OpenSessionInViewFilter 는 필터 내에서 Session은 오픈하지만 
#트랜잭션은 시작하지 않는다. 따라서 서블릿 필터 안에서는 커넥션 풀로부터 JDBC커넥션을 얻을 필요가 없다
#https://getinthere.tistory.com/27 참고하세요
  jpa:
    open-in-view: true
    hibernate:
      ddl-auto: create #create로하면 model추가시 테이블 자동생성됨. 매번생성되니 테이블생성되면 update로 바꿀것
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
      use-new-id-generator-mappings: false
    show-sql: true
    properties:
      hibernate.format_sql: true

댓글