본문 바로가기
개발/교육

(Spring) 57일차

by kakk789 2022. 6. 22.

스프링 시큐리티 로그인 과정

1. 로그인 페이지 요청 ( login.jsp )
1.1. WebSecurityConfigurerAdapter를 구현한  configure() 함수 호출 하여 권한 별 페이지 출력

2. id/password 입력 후 로그인 버튼 클릭하여 해당 정보를 갖는 서비스를 찾음

3. post 방식은 스프링 시큐리티가 알아서 해줌
3.1 스프링 시큐리티는 UserDetailsService를 구현한 loadUserByUsername() 함수를 호출하여
     알아서 값 비교하여 알아서 로그인을 해줌(스프링 시큐리티 마법....)
더보기

login 페이지

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>로그인</h2>
	<hr>
	<form action="login" method="post">
		<input type="hidden" name="${_csrf.parameterName }" value="${_csrf.token }">
		아이디 	: <input type="text" name="username"><br>
		비밀번호  : <input type="password" name="password"><br>
		<input type="submit" value="로그인">
	</form>
</body>
</html>

SecurityConfig

  • configure() 함수
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// TODO Auto-generated method stub
		
		//아래의 mavMAtchers에 따라 승인
		http.authorizeRequests() 
		
		// 해당 서비스는 모두 사용 가능 (로그인 필요 X)
		.mvcMatchers("/", "/hello","/all/**", "/join").permitAll() 
		
		// 해당 서비스는 admin만 사용 가능
		.mvcMatchers("/admin/**").hasRole("admin") 
		
		// 해당 서비스는 로그인 하면 사용 가능
		.anyRequest().authenticated(); 
		
		//로그인
		//로그인 폼 명 /login
		http.formLogin().loginPage("/login")
									.permitAll()
								.defaultSuccessUrl("/service1");

		//로그아웃
		//invalidateHttpSession = 로그 아웃 후 세션 파기하는 속성
		http.logout().logoutRequestMatcher(
				new AntPathRequestMatcher("/logout"))
					.invalidateHttpSession(true);
		
		http.httpBasic();
	}
}

controller

@Controller
@Setter
public class MemberController {

	@Autowired
	private MemberDAO dao;
	
	@Autowired
	private PasswordEncoder passwordEncoder;
	
	//GET 만 정의하면 됌.
	//POST는 Spring Security가 알아서 해주기때문에 
	//POST는 VIEW 페이지에서만 정의하면 됨
	@RequestMapping(value="/login", method=RequestMethod.GET)
	public void login() {
		
	}
	
	@GetMapping("/join")
	public void joinForm() {
		
	}
	
	@PostMapping("/join")
	public ModelAndView joinSubmit(MemberVO m) {
		ModelAndView mav = new ModelAndView("redirect:/login");
		
		//사용자가 입력한 암호를 가져와서 해당 암호를 암호화하는 작업
		m.setPwd(passwordEncoder.encode(m.getPwd()));
		
		m.setRole("user");
		dao.insert(m);
		return mav;
	}
}

로그인 서비스 페이지(스프링 마법)

  • loadUserByUsername()
@Service
@Setter
public class MemberService implements UserDetailsService {
	
	@Autowired
	private MemberDAO dao;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// TODO Auto-generated method stub
		System.out.println("사용자 로그인 처리");
		System.out.println(username);
		MemberVO m = dao.findById(username);
		if(m == null) {
			throw new UsernameNotFoundException(username);
		}
		
		UserDetails user = User.builder()
				.username(username)
				.password(m.getPwd())
				.roles(m.getRole()).build();
		return user;
	}
}

Mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="member">
  <select id="findById" resultType="memberVO">
  	select * from member100 where id=#{id}
  </select>
</mapper>

 

DAO / DBManaver 생략 했음.

로그인 회원 상태유지

  • 로그인 한 회원의 상태를 유지하기 위해서는 Controller 에서 시큐리티를 통해 로그인 회원 정보를 가져오면 됌
  • 로그인 한 회원의 id를 가져 왔으니, Model 이나 Session, Cookie에 상태 유지 하면 됌
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
String id = user.getUsername()

//model.addAttribute(id);                                    //model에 상태유지
//session.setAttribute("id", id);                           //session에 상태유지

DI 개념 (객체에 대한 이야기)

DI는 객체에 대한 이야기
  • 자바로 만들어진 소프트웨어는 수많은 클래스로 구성되고, 클래스와 클래스는 서로 관계에있음 has a or is a 관계이다.
  • 두 클래스의 관계가 '클래스 vs 클래스'이면서 new에 의해 객체를 생성하는 건 '강한 의존관계에 있다' 라고 표현
  • '강한 의존관계'를 '약간 의존관계'로 만드는게 DI의 개념이다.(의존관계에 대한 객체 주입을 Spring이 알아서 해줌)
    어떤식으로 Spring이 해주냐면 New로 객체를 생성하는 것이 아닌 별도의 환경설정 파일에서 객체를 제공해준다
  • 스프링 초기에는 XML로 환경설정을 하였지만 XML이 너무 비대해져서 그래서 어노테이션 기반으로 객체를 생성하는 개념이 나왔으나 이것 또한 불편해서 자동으로 의존관계 설정하는 개념이 등장
발전 변천사

XML로 객체 생성 -> 어노테이션 기반 생성 -> 현재는 오토스캔하여 완전 자동 생성으로 발전

 


 

Spring에서 AOP (Aspect Oriented Programming) 란?

여러 곳에서 사용되는 Method를 한 곳에서 Spring이 유지보수 가능하도록 도와줌

AOP는 메소드에 대한 이야기 (공통 관심사항'과 '핵심 관심사항'의 Bind를 스프링이 해줌)
  • 공통 관심사항, 핵심 관심사항 개념을 알아야함
    - 공통 관심사항(cross-cutting concern) - 공통으로 처리되는 사항 (포괄적 개념)
    - 핵심 관심사항(core concern) - 핵심으로 처리되는 사항 
  • 대표적인 사용 용도
    1. 패키지 내 메소드 Logging
    2. 패키지 내 메소드 로직 수행 시간 확인

주요 용어 설명

Aspect
- 여러 곳에서 사용되는  '공통 관심 사항' 들을(cross-cutting concern)를 모듈화
- @Aspect 
Target
- Aspect 가 적용되는 곳
- 어떤 대상에 부가 기능을 부여할 것인가

Advice
- Aspect 에서 실질적인 기능에 대한 구현체
- 어떤 부가 가능 인지 (Before, AfterReturning, AfterThrowing, After, Around)

Joint point
- Advice 가 Target 에 적용되는 시점메서드 진입할 때, 생성자 호출할 때, 필드에서 값을 꺼낼 때 등등
- 어디에 적용할 것인가? 메서드, 필드, 객체, 생성자 등
Pointcut
- Joint point 의 상세 스펙을 정의한 것
- 실제 Advice가 적용될 지점, Spring AOP에서는 advice가 적용될 메서드를 선정
- @Pointcut

Aspect

  • 여러 곳에서 사용되는  '공통 관심 사항' 들을(cross-cutting concern)를 모듈화

Advice 

  • '공통 관심 사항'을 위해 만들어 놓은 클래스
before
핵심 관심사항 처리 전  '공통 관심사항' 동작
- @Before

After Returning
- 핵심 관심사항이 성공적으로 마무리 되었을 경우 '공통 관심사항' 동작
- @AfterReturning
- 대상 객체가 리턴한 값을 사용하고자 할 때는 returning="ret" 에 담아서 사용

After Throwing
- '핵심 관심사항' 실패했을 때  '공통 관심사항' 동작 (예외)
- @AfterThrowing
- 예외에 접근하기 위해 throwing="ex" 에 담아서 사용

After 
- 핵심 관심사항 성공여부 관계 없이 무조건 공통 관심사항 동작

Around
- 핵심 관심사항이 처리되기 전 후 or 예외 발생 시 공통 관심사항 동작
- JProceedingJoinPoint 타입의 joinPoint를 반드시 매개변수로 가져야함

JoinPoint

  • '공통 관심 사항'이 호출되는 지점
  • 필요한 메소드가 호출되는 부분을 jointPoint 라고 부름
  • 대상 객체 및 호출되는 메서드의 정보를 담고 있음.
joinpoint.getSignature()
  • 호출되는 메서드의 정보를 담고 있음
@After("test()")
	public void afterError(JoinPoint joinPoint) {
		//toShortString() 메소드 이름 반환
		String methodName = joinPoint.getSignature().toShortString();
		System.out.println(methodName + "메소드가 완료되었습다.");
	}
Obejct getTarget()
  • 대상 객체를 반환
Object[] getArgs()
  • 파라미터 목록을 반환 (아래에 추가 설명 있음)

JoinPoint의 getArgs() 메소드

  • 언제 사용하냐면, 해당 서비스를 요청한 클라이언트의 정보를 받아서 사용할 수 있음
getArgs()는 타겟 메소드들의 매개변수들을 배열로 알려줌

PointCut

  • JoinPoint를 여러 개 묶어 놓은 것
  • 어디에 적용할 것 인지를 정한다(excution ..... )
  • PointCut이 적용된 메소드의 리턴 값을 void이여야 한다.
	@Pointcut("execution(public * com.example.demo.controller..*(..))")

속성1) execution

  • Advice를 적용할 메서드를 명시할 때 사용
execution([수식어] 리턴타입 [클래스이름].이름(파라미터)

execution
(* aaa.bbb.ccc.*.*())
- aaa.bbb.ccc 패키지의 파라미터가 없는 모든 메서드 호출

execution(* aaa.bbb.ccc..*.*(..))
- aaa.bbb.ccc패키지 및 하위 패키지에 있는 파라미터가 0개 이상인 메서드

execution(Integer aaa.bbb.ccc..WriteArticleService.write(..))
- 리턴 타입이 Integer인 WriteArticleService 인터페이스의 write 메서드 호출

execution(* get*(*))
- 이름이 get으로 시작하고 1개의 파라미터를 갖는 메서드 호출

execution(* get*(*,*))
- 이름이 get으로 시작하고 2개의 파라미터를 갖는 메서드 호출

execution(* read*(Integer, ..))
- 메서드 이름이 read로 시작하고 첫 번째 파라미터 타입이 Integer이며 1개 이상의 파라미터를 갖는 메서드 호출

속성2) within

  • 메서드가 아닌 특정 타입에 속하는 메서드를 Pointcut으로 설정 할 때 사용
within(aaa.bbb.ccc.board.service.WriteArticleService)
- WriteArticleService 인터페이스의 모든 메서드 호출

within(aaa.bbb.ccc.board.service.*)
- aaa.bbb.ccc.board.service 패키지에 있는 모드 메서드호출

within(aaa.bbb.ccc.board..*)
- aaa.bbb.ccc.board 패키지 및 하위 패키지에 있는 모든 메서드 호출

속성3) bean

bean 명시자

bean(beanname)
 - 이름이 beanname인 빈의 모든 메서드
 
bean(bean*)
 - 빈의 이름이 bean으로 시작하는 빈의 모든 메서드

Weaving

  • join point에 Advice를 적용하는 방법 (3가지가 있는데 스프링 에서는 프록시 방식을 사용한다)

Spring AOP Dependency  설정

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.7.0</version>
</dependency>

참고 블로그

https://chung-develop.tistory.com/67

filter

  • 웹 어플리케이션에서 사용자의 요청을 가로채어 먼저 동작하여 흐름을 제어하기 위한 용도로 사용
  • 예시 : 어떤 흐름이냐면 어떤 서비스를 이용할 때 Login을 꼭 진행하도록 함
  • 인증 / 인가(role)를 나눌 수 있음 (Spring Security를 수동으로 하는 느낌)

doFilter() 함수에서 흐름제어를 해야함 (LoginFilter.java)

- 현재 세션을 getSession()으로 가져와야 하는데 ServletRequest에는 getSession()이 없으므로
HttpServletRequest로 Type Casting 후 getSession() 사용하여 현재 로그인 한 id 세션 가져와 흐름제어 할 것임
- 23번째에 줄에 namespace에서 실수가 많으니 주의할 것 (서비스 요청 할 때 '프로젝트 이름/서비스do'
//admin에 있는 모든 요청
@WebFilter("/admin/*")
public class LoginFilter extends HttpFilter implements Filter {
						// ...
						// ...
						// ...
	public void doFilter(ServletRequest request, 
						ServletResponse response, 
						FilterChain chain
						)throws IOException, ServletException {
		
		System.out.println("필터가 동작함");
		
		//세션에 로그인 정보를 받아와서 로그인하지 않았으면 로그인 한데로 보내고
		// 로그인 했으면 조건에 부합하는 곳으로 보낸다
		HttpSession session = ((HttpServletRequest)request).getSession();
		String id = (String)session.getAttribute("id");
		if(id!=null) {
			//로그인 성공 시 doFilter 실핼
			chain.doFilter(request, response);
		}
		else {
			((HttpServletResponse)response).sendRedirect("/boardTest/login.do");

		}
	}
						// ...
						// ...
						// ...
}

스프링의 주요 특징

1. DI

  • 클래스들의 의존관계를 스프링 컨테이너가 제공함으로써 좀 더 느슨하게 설정하여 수정에 대한 요구에 유연하게 대처할 수 있음

2. AOP

  • 핵심코드에 공통모듈을 포장하여 실행하는 방식
  • 특정 요청에 따른 '핵심관심사항'과 '공통관심사항'이 있음
  • '공통관심사항'에 대하여 빈번한 수정에 대한 요구를 유연하게 대처하기 위하여 AOP를 적용하여 사용
반응형

'개발 > 교육' 카테고리의 다른 글

(JPA) 59일차  (0) 2022.06.24
(Thymeleaf/JPA) 58일차  (0) 2022.06.23
(Spring) 41일차  (0) 2022.05.27
(JSP)36일차  (0) 2022.05.20
(JSP)35일차  (0) 2022.05.19

댓글