D+62::스프링부트_시큐리티/인터페이스(Interface)

Am.Vinch·2022년 9월 26일
0

20220923_FRI

어제 내용 복습 정리

  • _1. porm.xml파일에 spring security dependecy 추가
    -> 프로그램의 모든 요청에 인증과 인가(권한)에 대한 조건을 판단한다.
    모든 요청(페이지이동)에 있어서 반드시 인증을 받도록 설정을 했다.
  • 2. 설정파일을 생성하고 코드를 구현
    : 해당페이지마다 인증 및 인가의 여부와 어떻게 인증할것인지에 대해
  • config 파일은 해당 클래스파일이 시큐리티 설정파일이라고 할 수 있다.
    -> 해당 파일이 시큐리티파일임을 인지하도록 어노테이션으로 표시해야한다.
    • @Configuration : 해당클래스가 설정파일임을 스프링에게 인지시키는 어노테이션
    • @EnableWebSecurity : 해당 클래스로부터 만들어진 객체가 security 설정 파일임을 인지시켜주는 어노테이션
  • 3. 실제 DB와 시큐리티를 연동
    • 전제 조건(로그인과 로그아웃 기능은 우리가 실제로 만들지 않는다.)
    • 시큐리티에서 제공하는 로그인 실행 코드를 작성해야한다.
    • UserDetailsService라는 인터페이스를 제공해야 한다.
      : 인터페이스는 단독 사용 불가하기 때문에 클래스 만들어야한다. ->UserDetailsServiceImpl
    • 이 인터페이스 안에 구현해야 하는 메소드가 딱 하나 존재한다.
           @Override
    		public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      			MemberVO loginInfo = memberService.login(username);
      			return null;
                }
            }
    • 해당 메소드loadUserByUsername가 실행이 되면, 로그인 기능을 수행한다.
    • 이 메소드는 리턴타입이 UserDetails 이기때문에 우리가 평소에 리턴받는 로그인 객체(loginInfo)를 사용할 수 없다
    • 그래서 UserDetails 인터페이스를 구현하는 User라는 클래스를 제공한다.
      이 User클래스에 우리가 조회한 데이터를 전달해서 리턴시킬 수 있다.

프로젝트 RUN

  • 먼저, 프로젝트 run 하기 전, DB실행을 위해 이전 porm.xml파일에 필요한 dependency 태그추가한다.
<!-- log4jdbc : 쿼리실행은 콘솔에 출력하기 위해 추가 -->
		<dependency>
			<groupId>org.bgee.log4jdbc-log4j2</groupId>
			<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
			<version>1.16</version>
		</dependency>
		<!-- 타임리프의 fragment 기능 사용을 위한 추가 -->
		<dependency>
			<groupId>nz.net.ultraq.thymeleaf</groupId>
			<artifactId>thymeleaf-layout-dialect</artifactId>
			<version>3.1.0</version>
		</dependency>
		<!--  validation처리 추가 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
        	<!-- html에서 시큐리티 정보를 가져올수 있는 태그 사용 (2개) -->
      <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-taglibs</artifactId>
          <version>5.0.7.RELEASE</version>
      </dependency>
      <dependency>
          <groupId>org.thymeleaf.extras</groupId>
          <artifactId>thymeleaf-extras-springsecurity5</artifactId>
          <version>3.0.4.RELEASE</version>
      </dependency>

  • porm.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>Kh.study</groupId>
	<artifactId>SecurityTest</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>SecurityTest</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>
		<dependency>
			<groupId>com.oracle.database.jdbc</groupId>
			<artifactId>ojdbc8</artifactId>
			<scope>runtime</scope>
		</dependency>
		<!-- log4jdbc : 쿼리실행은 콘솔에 출력하기 위해 추가 -->
		<dependency>
			<groupId>org.bgee.log4jdbc-log4j2</groupId>
			<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
			<version>1.16</version>
		</dependency>

		<!-- 타임리프의 fragment 기능 사용을 위한 추가 -->
		<dependency>
			<groupId>nz.net.ultraq.thymeleaf</groupId>
			<artifactId>thymeleaf-layout-dialect</artifactId>
			<version>3.1.0</version>
		</dependency>
		<!--  validation처리 추가 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
        	<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity5 -->
      <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-taglibs</artifactId>
          <version>5.0.7.RELEASE</version>
      </dependency>
      <dependency>
          <groupId>org.thymeleaf.extras</groupId>
          <artifactId>thymeleaf-extras-springsecurity5</artifactId>
          <version>3.0.4.RELEASE</version>
      </dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>
  • securityConfig 파일
package kh.study.security.config;

import java.util.ArrayList;
import java.util.List;

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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;


//해당클래스가 설정파일임을 스프링에게 인지시키는 어노테이션
@Configuration
//해당 클래스로부터 만들어진 객체가 security 설정 파일임을 인지시켜주는 어노테이션
@EnableWebSecurity
public class SecurityConfig   {
	
	//------------- bean 객체 어노테이션 --------------------------------------------------//
	//매개변수로 들어온 httpSecurity 객체를 사용해서 
	//인증 및 인가에 대한 제어를 구성할 수 있다.
	//@Bean//(객체) : 자동으로 객체만들어주는 어노테이션
	//SecurityFilterChain securityFilterChain = ""; 필요할 때마다 자동으로 호출해준다.
	//-------------------------------------------------------------------------------------//

	//--------- @bean 만들기 전 예시-------------------------------------------------------//
	// @Bean// 객체만들어주세요
	//  String string = "java"; -> 자동으로 만들어준다. 
	// 메소드를 만들면, 스프링이 필요할때 알아서 호출해준다.
	// 단, 리턴되는 자료형에서 소문자로 만들어 만들어준다.String - > string
	//	public String aaa(String name) {
	//		return name;
	//	}
	//	public void bbb() {
	//	String name	= aaa("java");
	//	System.out.println(name);
	//	}
	//-------------------------------------------------------------------------------------//
	
	@Bean
	public SecurityFilterChain filterChain(HttpSecurity security) throws Exception {
		//-------------------------------------------------------------------------//
		// throws Exception :                                                      //
		// 해석) 해당 메소드 예외발생시 예외처리안하고 전가시킨다.                 //
		//-------------------------------------------------------------------------//
		
		//-------------------------------<방법 1_기본 >----------------------------//
		// ① security.authorizeRequests().antMatchers("/index").permitAll();      //
		// 해석) 로그인 없이도 누구에게나 허용하도록하겠다.                        //
		// authorizeHttpRequests() : 권한(인증,인가) 에 대한 설정을 시작하겠다.    //
		// antMatchers("/index") : /index 요청에 대한 설정을 하겠다.               //
		// permitAll() : 인증,인가 절차없이 누구에게나 허용하겠다.                 //
		//-------------------------------------------------------------------------//
		//-------------------------------------------------------------------------//
		// ② security.authorizeRequests().antMatchers("/member").authenticated(); //
		//  해석) /member 요청경로도 인증을 받아야만 접근가능하다.                 //  
		//-------------------------------------------------------------------------//
		//-------------------------------------------------------------------------//
		// ③ security.authorizeRequests().anyRequest().authenticated();           //
		// 해석) 모든 요청은 로그인(인증)을 하지 않으면 접근 거부한다.             //
		// anyRequest() : 어떤 요청이 들어와도                                     //
		// authenticated() : 인증(로그인)을 받아야 접근가능하다.                   //
		//-------------------------------------------------------------------------//
		//----------------------------------------------------------------------------//
		// ④ security.authorizeRequests().antMatchers("/manager").hasRole("MANAGER") //
		// ⑤ security.authorizeRequests().antMatchers("/admin").hasRole("ADMIN")     //
		// hasRole() :                                                                //
		// 해석)                                                                      //
		// /manager 라는 요청으로 MANAGER라는 인가가 있어야 한다.                     //
		// /admin 라는 요청으로 ADMIN라는 인가가 있어야 한다.                         //
		//----------------------------------------------------------------------------//
		//-------------------------------<방법 1_기본 >-------------------------------//

		//-------------------------------<방법 2_요약> -------------------------------//
		// 한줄로 연결하여 요약이 가능한 이유)
		// : 리턴타입이 동일하기 때문에 연결하여 사용 가능하다
//		security.authorizeRequests()
//				.antMatchers("/index").permitAll()
//				.antMatchers("/login").permitAll()
//				.antMatchers("/member").authenticated()
//				.antMatchers("/manager").hasRole("MANAGER")
//				.antMatchers("/admin").hasRole("ADMIN")
//				//.anyRequest().authenticated()
//				.and().formLogin().loginPage("/login")
//									.defaultSuccessUrl("/index")
//									.failureUrl("/loginFail");
		//----------------------------------------------------------------------------------------------//
        // 1) 인증이 되지 않았을 경우 스프링이 기본 제공하는 login 페이지로 이동										
		//							.and().formLogin();
		//----------------------------------------------------------------------------------------------//
		// 2) 인증이 되지 않았을 경우 /login 페이지로 이동
		// (주의사항) 단, 로그인 페이지를 가는데 전체허용 permitAll()을 하지않은채로 페이지이동하면,
		// 로그인하러가는 페이지이동을 하는데에도 로그인인증을 받아야하는게 무한반복 호출하여 오류가 발생한다.
		//							.and().formLogin().loginPage("/login");
		//----------------------------------------------------------------------------------------------//
	
		//-------------------------------------------------------------------------//
		// security.formLogin();                                                   //
		// 해석) 인증이 되지 않을 경우, 자동으로 로그인 페이지로 이동한다.         //     
		// 로그인 인증방법)                                                        //
		// 콘솔창 가장 마지막 비밀번호 값을 입력하고 아이디도 user입력 시 가능하다.//
		//-------------------------------------------------------------------------//
		//.defaultSuccessUrl("/index") : 로그인 성공시 이동경로 
		//.failureUrl("/loginFail"); : 로그인 실패시 이동경로
		//-------------------------------<방법 2_요약> -------------------------------------------------//
		
		//------------------ <  manager 와 admin 로그인 할 때, 비교 >-----------------------------------------------------------//
		// 공통점) 둘 다 페이지 이동시, 로그인 인증을 해야 페이지 창이 뜬다.
		// 차이점) admin 은 접근권한을 가진 인가와 인증 둘다 가능하고, manager는 인증만 가능하다.
		//         admin으로 로그인 후, manager 로그인 가능하지만,
		//         manager 로그인 후, admin 페이지로 이동시, 로그인은 가능하지만(인증) 접근권한이 없어서 오류페이지가 뜬다.
		//------------------------------------------------------------------------------------------------------------------//
		
		
		//---------------------------------------------------------------------------//
		//security.exceptionHandling().accessDeniedPage("/accessDenied");
		// 위의 manager 페이지에서 admin 페이지 이동시,
		// 권한 문제로  접근거부 될 때 기본적으로 이동할 페이지를 지정 할 수 있다.
		// exceptionHandling().accessDeniedPage("/페이지명") 
		// : 페이지명으로 이동하면서 오류페이지가 아닌 접근페이지로 이동 가능하다.
		//---------------------------------------------------------------------------//
		
		//---------------------------------------------------------------------------//
		//security.logout().invalidateHttpSession(true)
		//				 .logoutSuccessUrl("/index");
		// 로그아웃하면 세션에 저장된 데이터를 자동 삭제한다.
		// 로그아웃하면 자동으로 /index 페이지로 이동한다.
		//---------------------------------------------------------------------------//
		
		//---------------------------------------------------------------------------//
		//security.csrf().disable();
		// 사이트 간의 요청 위조를 방지하는 시큐리티의 기본기능 비활성화해준다.
		// 스프링 보안공격 방지
		// : 이를 하지 않으면, 스프링보안정책문제로 페이지 오류발생으로 
		//---------------------------------------------------------------------------//
		
		
/////////////////////////////////////////////// ------------ 정리 ----------- /////////////////////////////////////////////////////////////////////////		
		//--------------  접근 허용 범위 설정 -----------------------------//
		security.csrf().disable()// scrf 공격을 막기위한 기본 설정을 해제
					.authorizeRequests() //인증,인가에 대해 설정을 시작하겠다.
					.antMatchers("/index","/login").permitAll() // /index,/login : 해당 요청은 누구나 접근 가능하도록 하겠다. 
					.antMatchers("/manager").hasAnyRole("MANAGER","ADMIN") // /manager 요청에   MANAGER , ADMIN 둘 중 하나의 권한만 있어도 가능하다.
					.antMatchers("/admin").hasRole("ADMIN") // /admin 요청은 ADMIN 권한 필요
					.anyRequest().permitAll() // 위의 요청들 제외 나머지 모두 아무나 접근 허용
				//-------------로그인 관련 설정------------------------------------------------------//
				.and()
					.formLogin() // 인증과 인가를 안받은 사람이 요청 접근을 하면 로그인 페이지를 띄우겠다.
					.loginPage("/login") //커스터마이징한 로그인 페이지 설정
					                     // 기본로그인 페이지사용하지않고  커스터마이징한 로그인 페이지(/login )로 이동하겠다. 
					.defaultSuccessUrl("/index") //로그인 성공시
					.failureUrl("/loginFail") // 로그인 실패시 
				//---------------- 로그아웃 설정 ----------------------------------------------------//	
				.and()
					.logout()
					.invalidateHttpSession(true)//로그아웃하면 세션 데이터 삭제하겠다.
					.logoutSuccessUrl("/index")
				//-----------------접근 거부 설정 ---------------------------------------------------//	
				.and()
					.exceptionHandling()// 인증은 받았지만 권한이 없는 경우 오류가 발생하면 처리한다.
					.accessDeniedPage("/accessDenied") // 접근 거부된 페이지 커스터마이징 설정
		; 
		return security.build();
	}
/////////////////////////////////////////////// ------------ 정리 ----------- /////////////////////////////////////////////////////////////////////////		
	
	
	
////////////// [ 인증 및 인가 테스트를 위한 계정 생성 메소드 ] ///////////////////////////////////////////
//	@Bean
//	public InMemoryUserDetailsManager userDetailService() {
//		List<UserDetails> userList = new ArrayList<>();
	
//---------------------------------------------------------------//	
//		.password("{noop}manager123") :{noop} 암호화과련 코드    //
//---------------------------------------------------------------//	
//		//----------리스트 데이터 추가하기 : 방법 .add()-------------//
//		
//		userList.add(User.withUsername("manager")
//				.password("{noop}manager123") 
//				.roles("MANAGER")
//				.build());
//		
//		userList.add(User.withUsername("admin")
//				.password("{noop}admin123")
//				.roles("ADMIN")
//				.build());
//		//------------------------------------------------------------//
//
//		//-------------------------------------------------------------------------//
//		// Q. manager 도 로그인 후 admin 페이지 접근가능하도록 하려면? ------------//
//		// "메소드 오버로딩" 사용하면 가능하다.
//		// 메소드명이 동일하면 중복되어 사용 불가능하지만,
//		// 매소드명이 다르거나 매개변수가 다르면 사용가능하다.
//		// roles(" "," ",...," ") : 무한으로 매개변수가 들어갈 수 있다.
//		//-------------------------------------------------------------------------//
//		// userList.add(User.withUsername("admin")
//		//		.password("{noop}admin123")
//		//		.roles("ADMIN","MANAGER")
//		//		.build());
//		//-------------------------------------------------------------------------//
//		
//		return new InMemoryUserDetailsManager(userList);
//}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////		
		
//------------------ 위의 manager 와 admin 로그인의 비교 ---------------------------------------------------------//
// 공통점) 둘 다 페이지 이동시, 로그인 인증을 해야 페이지 창이 뜬다.
// 차이점) admin 은 접근권한을 가진 인가와 인증 둘다 가능하고, manager는 인증만 가능하다.
//         admin으로 로그인 후, manager 로그인 가능하지만,
//         manager 로그인 후, admin 페이지로 이동시, 로그인은 가능하지만(인증) 접근권한이 없어서 오류페이지가 뜬다.
//----------------------------------------------------------------------------------------------------------------//
}
  • UserDtailsServiceImpl
package kh.study.security.service;

import javax.annotation.Resource;

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import kh.study.security.vo.MemberVO;
@Service("userDetailsService")
public class UserDtailsServiceImpl implements UserDetailsService  {

	//--------------------------------------------//
	// UserDetailsService                         //
	// : 로그인 메소드가 정의되어 있는 인터페이스 //
	// (스프링에서 만들어준 서비스)               //
	//--------------------------------------------//
	@Resource(name = "memberService")
	private MemberService memberService;
	
	
	//---------------------  해당 메소드가 로그인 시 자동으로 실행된다.---------------------------//
	// 자동실행되면 컨트롤러 코드 작성할 필요가 없다.
	// 대신, 로그인 버튼 클릭하면 form태그 action값 "/login" 요청 대시 해당 메소드를 실행한다.
	
	@Override
	public UserDetails loadUserByUsername(String username)  {
		//---------- 우리가 만든 로그인쿼리 실행 -----------//
		// 로그인 실패했을 시, loginInfo 값 null 들어온다.
		// 아이디를 잘못적었을 시에만 null이 들어온다.
		// 왜? 매퍼에서 where절에 memberId값만 있기때문에
		MemberVO loginInfo = memberService.login(username); //
		
		
		//-------------loginInfo null값인 경우 ------------------------------//
		// 단, null인 객체에서는 메소드 사용 불가능하다.(메소드호출불가)
		// : 그럼 아래 메소드 사용불가해서 오류뜬다.
		// throw new UsernameNotFoundException()
		// :아이디잘못적어서 loginInfo가 null값인 경우
		// :강제로 예외를 발생시킨다.
		if(loginInfo == null) {
			System.out.println(username + "이라는 회원은 존재하지 않습니다.");
			throw new UsernameNotFoundException("오류");
		}
		
		
		
		//--------securityConfig파일에서 가져오기 ---------------------//
		// 결국 user가 조회된 데이터를 모두 담아 가지고 있는다.
		// 일단 암호화하기 전 비밀번호는 앞에 "{noop}" 를 넣어줘야한다.
		// 최종적으로는 암호화할 때만 "{noop}"제거한다
		UserDetails userDetails = User
									.withUsername(loginInfo.getMemberId())
									.password("{noop}"+loginInfo.getMemberPw()) 
									.roles(loginInfo.getRole())
									.build();
								
		
		//--------------------------------------------------------------//
		// UserDetails : 리턴값 자료형_인터페이스 이기때문에 
		// 리턴값을 자료형이 MemberVO인 loginInfo 으로 할 수 없다!!!
		// 자료형이 UserDetails인 userDetails 를 리턴시킨다.
		return userDetails;
		
	}
	//---------------------------------------------------------------------------------------//
}
  • login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-------------- 로그인 기능------------------------------------------------->
<!-------------- [ 스프링 시큐리티 로그인 할 때 ] --------------------------->
<!--  : form태그 name값 아이디/비밀번호  표기법                            -->
<!--  아이디   : name="username"                                           -->
<!--  비밀번호 : name="password"                                           -->
<!-- 시큐리티를 활용해서 로그인 진행하려면
     post방식으로 /login으로 요청을 보내주기만 하면된다.
     (단, action값이 /login 이어야만 한다. 변경하면 실행 안된다!!)
     컨트롤러에 굳이 /login 요청을 받는 post방식 메소드를 만들 필요가 없다.
     대신, 매개변수로 받아오는 memebrId값은 
     반드시 input태그의 name 값인 username 값을 매개변수로 데이터 들고 가져간다.
     이와 다를시, 가져가서 사용하지 못한다.                                --->
<!-------------- ------------------------------------------------------------->
<form action="/login" method="post">
	id :<input type="text" name="username"><br>
	pw :<input type="password" name="password"><br>
	<div th:text="${failMsg}"></div>
	<input type="submit" value="로그인">
</form>
</body>
</html>

DB

  • DB 에서 만든 테이블 ROLE 값 수정하여 DELETE 후 INSERT 해준다.
DELETE security_member;
INSERT INTO SECURITY_MEMBER VALUES('member','member123','일반회원','MEMBER');
INSERT INTO SECURITY_MEMBER VALUES('manager','manager123','매니저회원','MANAGER');
INSERT INTO SECURITY_MEMBER VALUES('admin','admin123','관리자회원','ADMIN');
  • 결과

스프링 암호화

  • config
//------------------------- 스프링 암호화 ------------------------------- //
	// 암호화 기능을 갖는 객체 생성
	// @Bean 어노테이션
	// : 미리 객체 만들어준다 -> PasswordEncoder passwordEncoder =  new BCryptPasswordEncoder();
	@Bean //PasswordEncoder passwordEncoder =  new BCryptPasswordEncoder();
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
  • MemberVO
package kh.study.security.vo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Setter
@Getter
@ToString
public class MemberVO  {
	private String	memberId;
	private String	memberPw;
	private String	memberName;
	private String	role;
	
	// ----------------- 회원가입시,본인 비밀번호와 권한 설정하기 --------------------------------- //
	public void setRoleAndPw(PasswordEncoder passwordEncoder) {
		// -----------------------비밀번호 암호화---------------------------------------//
		// 그냥 하면 비밀번호값이 암호화되지 않기때문에
		// 해당 클래스 내에서 각각의 데이터 가져오기 때문에 
		// 객체명생략하고 컬럼명만 가져오면 된다.
		String pw =  passwordEncoder.encode(getMemberPw());
		setMemberPw(pw);
		
		
		// 만약 권한 체크박스로 둘다 체크되어있다면?
		// "manager","admin" 둘다 연결되어 들어온다.
		// 하지만, 관리자, 매니저 둘다 아닐 시 Role 데이터 값 null이 뜨기때문에 //
		if(getRole() == null) {
			setRole("MEMBER");
		}
		else {// MEMBER,MANAGER,ADMIN 모두 가져온다.
			setRole("MEMBER," + getRole());
		}
		
	}
}
  • 컨트롤러
	@GetMapping("/join")
	public String joinPage() {
		return "join";
	}
	
	
	
	
	@PostMapping("/join")
	public String join(MemberVO memberVO) {
		//---------MemberVO에서 만든 메소드사용---------//
		memberVO.setRoleAndPw(passwordEncoder);
		//----------------------------------------------//
		memberService.join(memberVO);
		return "login";
	}
	
	
	
	
	//---------------------------------  암호화 -------------------------------------//
	@GetMapping("/test1")
	public String testEncoder() {
		//-------------------------------------------------------------------------//
		//  passwordEncoder.encode("java") : "java"라는 글자를 암호화 
		// 단, 암호화된 글자는 암호를 풀수있는 기능을 제공하지 않는다.(복호화 x)
		String p1 = passwordEncoder.encode("java");
		String p2 = passwordEncoder.encode("java");
		
		System.out.println("p1 = " + p1);
		System.out.println("p2 = " + p2);
		
		//콘솔 출력결과 
		//: java 문자열을 암호화시켜서 나온다.
		//: 똑같은 java라는 글자를 둘다 암호화시켰지만 다르게 나온다!
		//p1 = $2a$10$85wbgrxZkQyDOD.PKadvROmZi0yUuc.F.q4dX3xgxvDAoEQyDHD3O
		//p2 = $2a$10$y/op3s4rvkQVP4XcVBwSOOaqSxc0m4C5vnNougLl.CsR.ml2v4rIu
		
		
		//------- 암호화된 글자가 같은지 match 하기 ---------//
		// 단, 리턴값은 boolean. T/F로 나오기때문에
		// 맞으면 true 틀리면 false가 나온다.
		boolean b1 = passwordEncoder.matches("java", p1);
		boolean b2 = passwordEncoder.matches("java", p2);
		
		System.out.println("b1 + " + b1);
		System.out.println("b2 + " + b2);
		
		return "index";
	}
	
  • join.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="/join" method="post">
	id :<input type="text" name="memberId"><br>
	pw :<input type="password" name="memberPw"><br>
	name :<input type="text" name="memberName"><br>
	role :
	<input type="checkbox" name="role" value="MANAGER" >매니저
	<input type="checkbox" name="role" value="ADMIN">관리자
	<br>
	<input type="submit" value="회원가입">
</form>
</body>
</html>
  • mapper
	<insert id="join">
		INSERT INTO SECURITY_MEMBER (
			MEMBER_ID
			,MEMBER_PW
			,MEMBER_NAME
			,ROLE
		) VALUES(
			#{memberId}
			,#{memberPw}
			,#{memberName}
			,#{role}
		)
	</insert>
  • MemberService
package kh.study.security.service;

import kh.study.security.vo.MemberVO;

public interface MemberService {
	//로그인
	MemberVO login(String memberId);
	void join(MemberVO memberVO);
}
  • MemberServiceImpl
package kh.study.security.service;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import kh.study.security.vo.MemberVO;

@Service("memberService")
@Repository
public class MemberServiceImpl implements MemberService {
	@Autowired
	private SqlSessionTemplate sqlSession;
	
	//로그인
	@Override
	public MemberVO login(String memberId) {
		return sqlSession.selectOne("memberMapper.login",memberId);
	}
	//회원가입
	@Override
	public void join(MemberVO memberVO) {
		sqlSession.insert("memberMapper.join",memberVO);
	}

}

결과확인

  • 회원가입 페이지이동
  • input값 입력,체크박스확인 후 회원가입 버튼 클릭시 로그인 페이지 이동
  • 콘솔 결과, 암호화되어 입력된 비밀번호값 확인가능
  • DB 조회결과
  • member/manager/admin 에 따라 접근가능 페이지가 다르다.
    • member는 /manager,/admin 접근 거부
    • manager는 /admin 접근거부
    • admin은 모든요청 접근 허용

로그인한 사용자의 정보를 가져오는 방법

로그인한 사용자의 정보를 가져오는 방법

    1. 자바에서 데이터를 가져오는 방법
    1. html에서 데이터를 가져오는 방법

로그인 유저 정보를 추출

로그인 유저 정보를 추출

  • loginfo 요청 경로 페이지이동시, 로그인 정보출력
  • <콘솔결과>
    id = all
    pw = null
    ROLE_ADMIN
    ROLE_MANAGER
    ROLE_MEMBER
  • 위의 'ROLE_'의 뜻은 ~권한을 가진다. 라고볼수있다.
    list값에서 반복문으로 하나씩 데이터를 꺼낼 때,
    자료형은 리스트 LIST<>괄호안에 담겨있는 자료형이다.
  • Controller
	@GetMapping("/info")
	public String getInfo(Authentication authentication ) {//인터페이스 매개변수
		
		//로그인한 유저의 정보를 가져온다.
		User user = (User)authentication.getPrincipal();
		
		//로그인한 사람의 아이디,비밀번호,권한정보 데이터만 가져올수있다.
		System.out.println("id = " + user.getUsername());
		System.out.println("pw = " + user.getPassword());
		
		List<GrantedAuthority> authoList = new ArrayList<>(user.getAuthorities());
		
		for(GrantedAuthority authority : authoList) {
			System.out.println(authority.getAuthority());;
		}
		
		return "index";
	}
	
	@GetMapping("/tag")
	public String htmlTest() {
		return "test";
	}

시큐리티 태그 사용

  • tag.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>시큐리티 태그 사용</h1>
	
	<!--  로그인정보 이름값-->
	<p th:text="${#authentication.name}"></p>
	<!--  로그인정보 권한 값-->
	<p th:text="${#authentication.authorities}"></p>
	<!--  로그인정보 권한여부 (T/F)-->
	<p th:text="${#authentication.authenticated}"></p>


	<!-- 로그인 여부에 따라 로그인/로그아웃 버튼 표시 -->
		<!-- 인증되지 않은(로그인하지 않은) 사용자에게 보임 -->
		<button sec:authorize="isAnonymous()" type="button"
			onclick="location.href='/admin/loginView'">로그인</button>
		<!-- 인증된(로그인한) 사용자에게 보임 -->
		<button sec:authorize="isAuthenticated()" type="button"
			onclick="location.href='/admin/logout'">로그아웃</button>


	<!-- ROLE_ADMIN 권한을 가지고 있다면 보임 -->
	<div sec:authorize="hasRole('ROLE_ADMIN')">ROLE_ADMIN 권한이 있습니다.</div>
	<!-- ROLE_SUB_ADMIN 권한을 가지고 있다면 보임 -->
	<div sec:authorize="hasRole('ROLE_MANAGER')">ROLE_MANAGER 권한이
		있습니다.</div>
	<!-- ROLE_USER 권한을 가지고 있다면 보임 -->
	<div sec:authorize="hasRole('ROLE_MEMBER')">ROLE_MEMBER 권한이 있습니다.</div>
	<!-- ROLE_ADMIN 혹은 ROLE_SUB_ADMIN 권한을 가지고 있다면 보임 -->
	<div sec:authorize="hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')">ROLE_ADMIN
		혹은 ROLE_MANAGER 권한이 있습니다.</div>

	<br />
	<!--인증시 사용된 객체에 대한 정보-->
	<b>Authenticated DTO:</b>
	<div sec:authorize="isAuthenticated()" sec:authentication="principal"></div>

	<br />
	<!--인증시 사용된 객체의 Username (ID)-->
	<b>Authenticated username:</b>
	<div sec:authorize="isAuthenticated()" sec:authentication="name"></div>

	<br />
	<!--객체의 권한-->
	<b>Authenticated admin role:</b>
	<div sec:authorize="isAuthenticated()"
		sec:authentication="principal.authorities"></div>
</body>
</html>

화면 결과

  • 결과
    • DB 조회
      : MEMBER,ADMIN,MANAGER 모든 권한을 가지고 있는 아이디로 로그인 했을 때,
      (id:all,pw:all,name:all)
    • 화면 출력결과
    • 콘솔 출력결과 : 아이디,권한,비밀번호 조회


총정리

프로젝트_SecurityTest

업로드중..

package kh.study.security.config;
//해당클래스가 설정파일임을 스프링에게 인지시키는 어노테이션
@Configuration
//해당 클래스로부터 만들어진 객체가 security 설정 파일임을 인지시켜주는 어노테이션
@EnableWebSecurity
public class SecurityConfig   {
	
	//------------- bean 객체 어노테이션 --------------------------------------------------//
	//매개변수로 들어온 httpSecurity 객체를 사용해서 
	//인증 및 인가에 대한 제어를 구성할 수 있다.
	//@Bean//(객체) : 자동으로 객체만들어주는 어노테이션
	//SecurityFilterChain securityFilterChain = ""; 필요할 때마다 자동으로 호출해준다.
	//-------------------------------------------------------------------------------------//

	//--------- @bean 만들기 전 예시-------------------------------------------------------//
	// @Bean// 객체만들어주세요
	//  String string = "java"; -> 자동으로 만들어준다. 
	// 메소드를 만들면, 스프링이 필요할때 알아서 호출해준다.
	// 단, 리턴되는 자료형에서 소문자로 만들어 만들어준다.String - > string
	//	public String aaa(String name) {
	//		return name;
	//	}
	//	public void bbb() {
	//	String name	= aaa("java");
	//	System.out.println(name);
	//	}
	//-------------------------------------------------------------------------------------//
	
	@Bean
	public SecurityFilterChain filterChain(HttpSecurity security) throws Exception {
		//-------------------------------------------------------------------------//
		// throws Exception :                                                      //
		// 해석) 해당 메소드 예외발생시 예외처리안하고 전가시킨다.                 //
		//-------------------------------------------------------------------------//
		
		//-------------------------------<방법 1_기본 >----------------------------//
		// ① security.authorizeRequests().antMatchers("/index").permitAll();      //
		// 해석) 로그인 없이도 누구에게나 허용하도록하겠다.                        //
		// authorizeHttpRequests() : 권한(인증,인가) 에 대한 설정을 시작하겠다.    //
		// antMatchers("/index") : /index 요청에 대한 설정을 하겠다.               //
		// permitAll() : 인증,인가 절차없이 누구에게나 허용하겠다.                 //
		//-------------------------------------------------------------------------//
		//-------------------------------------------------------------------------//
		// ② security.authorizeRequests().antMatchers("/member").authenticated(); //
		//  해석) /member 요청경로도 인증을 받아야만 접근가능하다.                 //  
		//-------------------------------------------------------------------------//
		//-------------------------------------------------------------------------//
		// ③ security.authorizeRequests().anyRequest().authenticated();           //
		// 해석) 모든 요청은 로그인(인증)을 하지 않으면 접근 거부한다.             //
		// anyRequest() : 어떤 요청이 들어와도                                     //
		// authenticated() : 인증(로그인)을 받아야 접근가능하다.                   //
		//-------------------------------------------------------------------------//
		//----------------------------------------------------------------------------//
		// ④ security.authorizeRequests().antMatchers("/manager").hasRole("MANAGER") //
		// ⑤ security.authorizeRequests().antMatchers("/admin").hasRole("ADMIN")     //
		// hasRole() :                                                                //
		// 해석)                                                                      //
		// /manager 라는 요청으로 MANAGER라는 인가가 있어야 한다.                     //
		// /admin 라는 요청으로 ADMIN라는 인가가 있어야 한다.                         //
		//----------------------------------------------------------------------------//
		//-------------------------------<방법 1_기본 >-------------------------------//

		//-------------------------------<방법 2_요약> -------------------------------//
		// 한줄로 연결하여 요약이 가능한 이유)
		// : 리턴타입이 동일하기 때문에 연결하여 사용 가능하다
//		security.authorizeRequests()
//				.antMatchers("/index").permitAll()
//				.antMatchers("/login").permitAll()
//				.antMatchers("/member").authenticated()
//				.antMatchers("/manager").hasRole("MANAGER")
//				.antMatchers("/admin").hasRole("ADMIN")
//				//.anyRequest().authenticated()
//				.and().formLogin().loginPage("/login")
//									.defaultSuccessUrl("/index")
//									.failureUrl("/loginFail");
		//----------------------------------------------------------------------------------------------//
        // 1) 인증이 되지 않았을 경우 스프링이 기본 제공하는 login 페이지로 이동										
		//							.and().formLogin();
		//----------------------------------------------------------------------------------------------//
		// 2) 인증이 되지 않았을 경우 /login 페이지로 이동
		// (주의사항) 단, 로그인 페이지를 가는데 전체허용 permitAll()을 하지않은채로 페이지이동하면,
		// 로그인하러가는 페이지이동을 하는데에도 로그인인증을 받아야하는게 무한반복 호출하여 오류가 발생한다.
		//							.and().formLogin().loginPage("/login");
		//----------------------------------------------------------------------------------------------//
	
		//-------------------------------------------------------------------------//
		// security.formLogin();                                                   //
		// 해석) 인증이 되지 않을 경우, 자동으로 로그인 페이지로 이동한다.         //     
		// 로그인 인증방법)                                                        //
		// 콘솔창 가장 마지막 비밀번호 값을 입력하고 아이디도 user입력 시 가능하다.//
		//-------------------------------------------------------------------------//
		//.defaultSuccessUrl("/index") : 로그인 성공시 이동경로 
		//.failureUrl("/loginFail"); : 로그인 실패시 이동경로
		//-------------------------------<방법 2_요약> -------------------------------------------------//
		
		//------------------ <  manager 와 admin 로그인 할 때, 비교 >-----------------------------------------------------------//
		// 공통점) 둘 다 페이지 이동시, 로그인 인증을 해야 페이지 창이 뜬다.
		// 차이점) admin 은 접근권한을 가진 인가와 인증 둘다 가능하고, manager는 인증만 가능하다.
		//         admin으로 로그인 후, manager 로그인 가능하지만,
		//         manager 로그인 후, admin 페이지로 이동시, 로그인은 가능하지만(인증) 접근권한이 없어서 오류페이지가 뜬다.
		//------------------------------------------------------------------------------------------------------------------//
		
		
		//---------------------------------------------------------------------------//
		//security.exceptionHandling().accessDeniedPage("/accessDenied");
		// 위의 manager 페이지에서 admin 페이지 이동시,
		// 권한 문제로  접근거부 될 때 기본적으로 이동할 페이지를 지정 할 수 있다.
		// exceptionHandling().accessDeniedPage("/페이지명") 
		// : 페이지명으로 이동하면서 오류페이지가 아닌 접근페이지로 이동 가능하다.
		//---------------------------------------------------------------------------//
		
		//---------------------------------------------------------------------------//
		//security.logout().invalidateHttpSession(true)
		//				 .logoutSuccessUrl("/index");
		// 로그아웃하면 세션에 저장된 데이터를 자동 삭제한다.
		// 로그아웃하면 자동으로 /index 페이지로 이동한다.
		//---------------------------------------------------------------------------//
		
		//---------------------------------------------------------------------------//
		//security.csrf().disable();
		// 사이트 간의 요청 위조를 방지하는 시큐리티의 기본기능 비활성화해준다.
		// 스프링 보안공격 방지
		// : 이를 하지 않으면, 스프링보안정책문제로 페이지 오류발생으로 
		//---------------------------------------------------------------------------//
		
		
/////////////////////////////////////////////// ------------ 정리 ----------- /////////////////////////////////////////////////////////////////////////		
		//--------------  접근 허용 범위 설정 -----------------------------//
		security.csrf().disable()// scrf 공격을 막기위한 기본 설정을 해제
					.authorizeRequests() //인증,인가에 대해 설정을 시작하겠다.
					.antMatchers("/index","/login").permitAll() // /index,/login : 해당 요청은 누구나 접근 가능하도록 하겠다. 
					.antMatchers("/manager").hasAnyRole("MANAGER","ADMIN") // /manager 요청에   MANAGER , ADMIN 둘 중 하나의 권한만 있어도 가능하다.
					.antMatchers("/admin").hasRole("ADMIN") // /admin 요청은 ADMIN 권한 필요
					.anyRequest().permitAll() // 위의 요청들 제외 나머지 모두 아무나 접근 허용
				//-------------로그인 관련 설정------------------------------------------------------//
				.and()
					.formLogin() // 인증과 인가를 안받은 사람이 요청 접근을 하면 로그인 페이지를 띄우겠다.
					.loginPage("/login") //커스터마이징한 로그인 페이지 설정
					                     // 기본로그인 페이지사용하지않고  커스터마이징한 로그인 페이지(/login )로 이동하겠다. 
					.defaultSuccessUrl("/index") //로그인 성공시
					.failureUrl("/loginFail") // 로그인 실패시 
				//---------------- 로그아웃 설정 ----------------------------------------------------//	
				.and()
					.logout()
					.invalidateHttpSession(true)//로그아웃하면 세션 데이터 삭제하겠다.
					.logoutSuccessUrl("/index")
				//-----------------접근 거부 설정 ---------------------------------------------------//	
				.and()
					.exceptionHandling()// 인증은 받았지만 권한이 없는 경우 오류가 발생하면 처리한다.
					.accessDeniedPage("/accessDenied") // 접근 거부된 페이지 커스터마이징 설정
		; 
		return security.build();
	}
	
	//------------------------- 스프링 암호화 ------------------------------- //
	// 암호화 기능을 갖는 객체 생성
	// @Bean 어노테이션
	// : 미리 객체 만들어준다 -> PasswordEncoder passwordEncoder =  new BCryptPasswordEncoder();
	@Bean //PasswordEncoder passwordEncoder =  new BCryptPasswordEncoder();
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
/////////////////////////////////////////////// ------------ 정리 ----------- /////////////////////////////////////////////////////////////////////////		
	
	
	
////////////// [ 인증 및 인가 테스트를 위한 계정 생성 메소드 ] ///////////////////////////////////////////
//	@Bean
//	public InMemoryUserDetailsManager userDetailService() {
//		List<UserDetails> userList = new ArrayList<>();
	
//---------------------------------------------------------------//	
//		.password("{noop}manager123") :{noop} 암호화과련 코드    //
//---------------------------------------------------------------//	
//		//----------리스트 데이터 추가하기 : 방법 .add()-------------//
//		
//		userList.add(User.withUsername("manager")
//				.password("{noop}manager123") 
//				.roles("MANAGER")
//				.build());
//		
//		userList.add(User.withUsername("admin")
//				.password("{noop}admin123")
//				.roles("ADMIN")
//				.build());
//		//------------------------------------------------------------//
//
//		//-------------------------------------------------------------------------//
//		// Q. manager 도 로그인 후 admin 페이지 접근가능하도록 하려면? ------------//
//		// "메소드 오버로딩" 사용하면 가능하다.
//		// 메소드명이 동일하면 중복되어 사용 불가능하지만,
//		// 매소드명이 다르거나 매개변수가 다르면 사용가능하다.
//		// roles(" "," ",...," ") : 무한으로 매개변수가 들어갈 수 있다.
//		//-------------------------------------------------------------------------//
//		// userList.add(User.withUsername("admin")
//		//		.password("{noop}admin123")
//		//		.roles("ADMIN","MANAGER")
//		//		.build());
//		//-------------------------------------------------------------------------//
//		
//		return new InMemoryUserDetailsManager(userList);
//}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////		
		
//------------------ 위의 manager 와 admin 로그인의 비교 ---------------------------------------------------------//
// 공통점) 둘 다 페이지 이동시, 로그인 인증을 해야 페이지 창이 뜬다.
// 차이점) admin 은 접근권한을 가진 인가와 인증 둘다 가능하고, manager는 인증만 가능하다.
//         admin으로 로그인 후, manager 로그인 가능하지만,
//         manager 로그인 후, admin 페이지로 이동시, 로그인은 가능하지만(인증) 접근권한이 없어서 오류페이지가 뜬다.
//----------------------------------------------------------------------------------------------------------------//
}

config

  • securityConfig
package kh.study.security.controller;

@Controller
public class SecurityController {
	//-------- @Autowired 어노테이션 ------------------------------------//
	// @Autowired : 만들어진 객체를 가져와서 넣어준다.
	// : 단, 가져올 때 자료형을 기준으로 가져온다.      
	// 해셕) securityConfig 클래스 에서  만들어진 암호화 객체를 가져온다.
	@Autowired
	private PasswordEncoder passwordEncoder;
	//-------------------------------------------------------------------//
	
	@Resource(name = "memberService")
	private MemberService memberService;
	
	@GetMapping("/index")
	public String index() {
		return "index";
	}
	
	@GetMapping("/home")
	public String home() {
		return "home";
	}
	@GetMapping("/manager")
	public String manager() {
		return "manager";
	}
	@GetMapping("/admin")
	public String admin() {
		return "admin";
	}
	@GetMapping("/accessDenied")
	public String accessDenied() {
		return "accessDenied";
	}
	@GetMapping("/login")
	public String login() {
		return "login";
	}
	@GetMapping("/loginFail")
	public String loginFail(Model model) {
		//---------------------------------------------------------------------------//
		model.addAttribute("failMsg", "아이디 혹은 비밀번호가 잘못 입력되었습니다.");
		// login 페이지에, 뒤의 문자열로 failMsg값을 던져준다.
		// 로그인 실패시, div영역으로 failMsg값이 화면에 나타난다
		//---------------------------------------------------------------------------//
		return "login";
	}
	// ---------------------------회원가입 페이지 이동---------------------------//
	@GetMapping("/join")
	public String joinPage() {
		return "join";
	}
	
	//----------------------------------회원가입----------------------------------//
	@PostMapping("/join")
	public String join(MemberVO memberVO) {
		//---------MemberVO에서 만든 메소드사용---------//
		memberVO.setRoleAndPw(passwordEncoder);
		//----------------------------------------------//
		memberService.join(memberVO);
		return "login";
	}
	
	//---------------------------------  암호화 -------------------------------------//
	@GetMapping("/test1")
	public String testEncoder() {
		//-------------------------------------------------------------------------//
		//  passwordEncoder.encode("java") : "java"라는 글자를 암호화 
		// 단, 암호화된 글자는 암호를 풀수있는 기능을 제공하지 않는다.(복호화 x)
		String p1 = passwordEncoder.encode("java");
		String p2 = passwordEncoder.encode("java");
		
		System.out.println("p1 = " + p1);
		System.out.println("p2 = " + p2);
		
		//콘솔 출력결과 
		//: java 문자열을 암호화시켜서 나온다.
		//: 똑같은 java라는 글자를 둘다 암호화시켰지만 다르게 나온다!
		//p1 = $2a$10$85wbgrxZkQyDOD.PKadvROmZi0yUuc.F.q4dX3xgxvDAoEQyDHD3O
		//p2 = $2a$10$y/op3s4rvkQVP4XcVBwSOOaqSxc0m4C5vnNougLl.CsR.ml2v4rIu
		
		
		//------- 암호화된 글자가 같은지 match 하기 ---------//
		// 단, 리턴값은 boolean. T/F로 나오기때문에
		// 맞으면 true 틀리면 false가 나온다.
		boolean b1 = passwordEncoder.matches("java", p1);
		boolean b2 = passwordEncoder.matches("java", p2);
		
		System.out.println("b1 + " + b1);
		System.out.println("b2 + " + b2);
		
		return "index";
	}
	
	//----------------로그인 유저 정보를 추출---------------------------//
	// /loginfo 요청 경로 페이지이동시, 로그인 정보출력
	//
	// <콘솔결과>
	//  id = all
	//	pw = null
	//	ROLE_ADMIN
	//	ROLE_MANAGER
	//	ROLE_MEMBER
	//
	// 위의 'ROLE_'의 뜻은 ~권한을 가진다. 라고볼수있다.
	// list값에서 반복문으로 하나씩 데이터를 꺼낼 때,
	// 자료형은 리스트 LIST<>괄호안에 담겨있는 자료형이다.
	
	@GetMapping("/info")
	public String getInfo(Authentication authentication ) {//인터페이스 매개변수
		
		//로그인한 유저의 정보를 가져온다.
		User user = (User)authentication.getPrincipal();
		
		//로그인한 사람의 아이디,비밀번호,권한정보 데이터만 가져올수있다.
		System.out.println("id = " + user.getUsername());
		System.out.println("pw = " + user.getPassword());
		
		List<GrantedAuthority> authoList = new ArrayList<>(user.getAuthorities());
		
		for(GrantedAuthority authority : authoList) {
			System.out.println(authority.getAuthority());;
		}
		
		return "index";
	}
	
	@GetMapping("/tag")
	public String htmlTest() {
		return "test";
	}
}

VO

  • MemverVO
package kh.study.security.vo;

@Setter
@Getter
@ToString
public class MemberVO  {
	private String	memberId;
	private String	memberPw;
	private String	memberName;
	private String	role;
	
	// ----------------- 회원가입시,본인 비밀번호와 권한 설정하기 --------------------------------- //
	public void setRoleAndPw(PasswordEncoder passwordEncoder) {
		// -----------------------비밀번호 암호화---------------------------------------//
		// 그냥 하면 비밀번호값이 암호화되지 않기때문에
		// 해당 클래스 내에서 각각의 데이터 가져오기 때문에 
		// 객체명생략하고 컬럼명만 가져오면 된다.
		String pw =  passwordEncoder.encode(getMemberPw());
		setMemberPw(pw);
		
		
		// 만약 권한 체크박스로 둘다 체크되어있다면?
		// "manager","admin" 둘다 연결되어 들어온다.
		// 하지만, 관리자, 매니저 둘다 아닐 시 Role 데이터 값 null이 뜨기때문에 //
		if(getRole() == null) {
			setRole("MEMBER");
		}
		else {// MEMBER,MANAGER,ADMIN 모두 가져온다.
			setRole("MEMBER," + getRole());
		}
		
	}
}

service

  • MemberService
package kh.study.security.service;

import kh.study.security.vo.MemberVO;

public interface MemberService {
	//로그인
	MemberVO login(String memberId);
	void join(MemberVO memberVO);
}
  • MemberServiceImpl
package kh.study.security.service;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import kh.study.security.vo.MemberVO;

@Service("memberService")
@Repository
public class MemberServiceImpl implements MemberService {
	@Autowired
	private SqlSessionTemplate sqlSession;
	
	//로그인
	@Override
	public MemberVO login(String memberId) {
		return sqlSession.selectOne("memberMapper.login",memberId);
	}
	//회원가입
	@Override
	public void join(MemberVO memberVO) {
		sqlSession.insert("memberMapper.join",memberVO);
	}

}
  • UserDetailsService
package kh.study.security.service;
@Service("userDetailsService")
public class UserDtailsServiceImpl implements UserDetailsService  {

	//--------------------------------------------//
	// UserDetailsService                         //
	// : 로그인 메소드가 정의되어 있는 인터페이스 //
	// (스프링에서 만들어준 서비스)               //
	//--------------------------------------------//
	@Resource(name = "memberService")
	private MemberService memberService;
	
	
	//---------------------  해당 메소드가 로그인 시 자동으로 실행된다.---------------------------//
	// 자동실행되면 컨트롤러 코드 작성할 필요가 없다.
	// 대신, 로그인 버튼 클릭하면 form태그 action값 "/login" 요청 대시 해당 메소드를 실행한다.
	
	@Override
	public UserDetails loadUserByUsername(String username)  {
		//---------- 우리가 만든 로그인쿼리 실행 -----------//
		// 로그인 실패했을 시, loginInfo 값 null 들어온다.
		// 아이디를 잘못적었을 시에만 null이 들어온다.
		// 왜? 매퍼에서 where절에 memberId값만 있기때문에
		MemberVO loginInfo = memberService.login(username); //
		
		
		//-------------loginInfo null값인 경우 ------------------------------//
		// 단, null인 객체에서는 메소드 사용 불가능하다.(메소드호출불가)
		// : 그럼 아래 메소드 사용불가해서 오류뜬다.
		// throw new UsernameNotFoundException()
		// :아이디잘못적어서 loginInfo가 null값인 경우
		// :강제로 예외를 발생시킨다.
		if(loginInfo == null) {
			System.out.println(username + "이라는 회원은 존재하지 않습니다.");
			throw new UsernameNotFoundException("오류");
		}
		
		
		
		//--------securityConfig파일에서 가져오기 ------------------------------------//
		// 결국 user가 조회된 데이터를 모두 담아 가지고 있는다.
		//.password("{noop}"+loginInfo.getMemberPw()) 
		// 일단 암호화하기 전 비밀번호는 앞에 "{noop}" 를 넣어줘야한다.
		// 최종적으로는 암호화할 때만 "{noop}"제거한다
		//.roles(loginInfo.getRole()) //"MEMBER,MANAGER,ADMIN" (X)  문제발생
		// 그래서 .split(",")으로 하나하나 잘라줘야한다.
		// 총 문자열 3개로 분리되어 나온다.
		// -> "MEMBER","MANAGER","ADMIN" (O)
		UserDetails userDetails = User
									.withUsername(loginInfo.getMemberId())
									.password(loginInfo.getMemberPw()) 
									.roles(loginInfo.getRole().split(","))
									.build();
								
		
		//--------------------------------------------------------------//
		// UserDetails : 리턴값 자료형_인터페이스 이기때문에 
		// 리턴값을 자료형이 MemberVO인 loginInfo 으로 할 수 없다!!!
		// 자료형이 UserDetails인 userDetails 를 리턴시킨다.
		return userDetails;
		
	}
	//---------------------------------------------------------------------------------------//
}

mapper

  • member-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="memberMapper">
	<resultMap type="kh.study.security.vo.MemberVO" id="member">
		<id column="MEMBER_ID" property="memberId"/>
		<result column="MEMBER_PW" property="memberPw"/>
		<result column="MEMBER_NAME" property="memberName"/>
		<result column="ROLE" property="role"/>
	</resultMap>
<!--                 < 스프링 시큐리티 로그인 조회 >                                -->

<!--    스프링 시큐리티에서는 조건절에 비밀번호 사용하는 것을 허용하지 않는다!, 비밀번호도 같이 조회해야 한다.
		비밀번호가 조회가 되지만, 암호화 된채로 비밀번호 데이터를 가져온다 
		그래서 암호화된 비밀번호를 디코딩(복호화) 한 뒤, 
		조회된 비밀번호를 비교하여 맞으면 로그인을 시켜준다.                   
		조회시, name값이나 다른 tell,...등 값을 조회하려해도 
		오류는 안나지만 사용은 불가능하다!!!!
		그래서," 아이디,권한,비밀번호 "까지 총 3가지만을 필수로 조회한다.
		비밀번호는 조회한 아이디와 인풋태그아이디 비교 후 비밀번호 데이터 버린다.   -->

	<select id="login" resultMap="member">
		SELECT MEMBER_ID
			, ROLE
			, MEMBER_PW
		FROM SECURITY_MEMBER
		WHERE MEMBER_ID = #{memberId}
	</select>

	<insert id="join">
		INSERT INTO SECURITY_MEMBER (
			MEMBER_ID
			,MEMBER_PW
			,MEMBER_NAME
			,ROLE
		) VALUES(
			#{memberId}
			,#{memberPw}
			,#{memberName}
			,#{role}
		)
	</insert>

</mapper>

templates

  • index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
index페이지입니다
</body>
</html>
  • home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>홈 화면</h1>
<!-------------- 로그아웃 기능------------------------------------------------->
<!-- 시큐리티를 활용해서 로그아웃을 진행하려면
     post방식으로 /logout 으로 요청을 보내주기만 하면된다.
     컨트롤러에 굳이 /logout 요청을 받는 post 방식메소드를 만들 필요가 없다.(자동생성)-->
<form action="/logout" method="post">
	<input type="submit" value="로그아웃">
</form>
<!----------------------------------------------------------------------------->

</body>
</html>
  • manager.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
manager 페이지입니다.
</body>

</html>
  • admin.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
admin 페이지입니다.
</body>

</html>
  • join.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="/join" method="post">
	id :<input type="text" name="memberId"><br>
	pw :<input type="password" name="memberPw"><br>
	name :<input type="text" name="memberName"><br>
	role :
	<input type="checkbox" name="role" value="MANAGER" >매니저
	<input type="checkbox" name="role" value="ADMIN">관리자
	<br>
	<input type="submit" value="회원가입">
</form>
</body>
</html>
  • login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-------------- 로그인 기능------------------------------------------------->
<!-------------- [ 스프링 시큐리티 로그인 할 때 ] --------------------------->
<!--  : form태그 name값 아이디/비밀번호  표기법                            -->
<!--  아이디   : name="username"                                           -->
<!--  비밀번호 : name="password"                                           -->
<!-- 시큐리티를 활용해서 로그인 진행하려면
     post방식으로 /login으로 요청을 보내주기만 하면된다.
     (단, action값이 /login 이어야만 한다. 변경하면 실행 안된다!!)
     컨트롤러에 굳이 /login 요청을 받는 post방식 메소드를 만들 필요가 없다.
     대신, 매개변수로 받아오는 memebrId값은 
     반드시 input태그의 name 값인 username 값을 매개변수로 데이터 들고 가져간다.
     이와 다를시, 가져가서 사용하지 못한다.                                --->
<!-------------- ------------------------------------------------------------->
<form action="/login" method="post">
	id :<input type="text" name="username"><br>
	pw :<input type="password" name="password"><br>
	<div th:text="${failMsg}"></div>
	<input type="submit" value="로그인">
</form>
</body>
</html>
  • accessDenied.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
accessDenied
</body>
</html>
  • test.html
    • 로그인 정보 데이터 가져오는 방법 정리된 파일

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>시큐리티 태그 사용</h1>
	
	<!--  로그인정보 이름값-->
	<p th:text="${#authentication.name}"></p>
	<!--  로그인정보 권한 값-->
	<p th:text="${#authentication.authorities}"></p>
	<!--  로그인정보 권한여부 (T/F)-->
	<p th:text="${#authentication.authenticated}"></p>


	<!-- 로그인 여부에 따라 로그인/로그아웃 버튼 표시 -->
		<!-- 인증되지 않은(로그인하지 않은) 사용자에게 보임 -->
		<button sec:authorize="isAnonymous()" type="button"
			onclick="location.href='/admin/loginView'">로그인</button>
		<!-- 인증된(로그인한) 사용자에게 보임 -->
		<button sec:authorize="isAuthenticated()" type="button"
			onclick="location.href='/admin/logout'">로그아웃</button>


	<!-- ROLE_ADMIN 권한을 가지고 있다면 보임 -->
	<div sec:authorize="hasRole('ROLE_ADMIN')">ROLE_ADMIN 권한이 있습니다.</div>
	<!-- ROLE_SUB_ADMIN 권한을 가지고 있다면 보임 -->
	<div sec:authorize="hasRole('ROLE_MANAGER')">ROLE_MANAGER 권한이
		있습니다.</div>
	<!-- ROLE_USER 권한을 가지고 있다면 보임 -->
	<div sec:authorize="hasRole('ROLE_MEMBER')">ROLE_MEMBER 권한이 있습니다.</div>
	<!-- ROLE_ADMIN 혹은 ROLE_SUB_ADMIN 권한을 가지고 있다면 보임 -->
	<div sec:authorize="hasAnyRole('ROLE_ADMIN', 'ROLE_MANAGER')">ROLE_ADMIN
		혹은 ROLE_MANAGER 권한이 있습니다.</div>

	<br />
	<!--인증시 사용된 객체에 대한 정보-->
	<b>Authenticated DTO:</b>
	<div sec:authorize="isAuthenticated()" sec:authentication="principal"></div>

	<br />
	<!--인증시 사용된 객체의 Username (ID)-->
	<b>Authenticated username:</b>
	<div sec:authorize="isAuthenticated()" sec:authentication="name"></div>

	<br />
	<!--객체의 권한-->
	<b>Authenticated admin role:</b>
	<div sec:authorize="isAuthenticated()"
		sec:authentication="principal.authorities"></div>
</body>
</html>


20220923FRI
하이퍼
링크) https://castleone.tistory.com/11?category=895526

목표

  1. 인터페이스 정의하는 방법
  2. 인터페이스 구현하는 방법
  3. 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
  4. 인터페이스 상속
  5. 인터페이스의 기본 메소드(Default Method) , 자바 8
  6. 인터페이스의 static 메소드 , 자바 8
  7. 인터페이스의 private 메소드 , 자바 9

1. 인터페이스 정의하는 방법

자바8 이전
: 상수 , 추상 메소드만 가능
=> 강제성이 강함을 유추

자바8 이후
: default , static , private method가 추가됨
=> 강제성 안에 유연성을 추가함

public interface 인터페이스명{

           //상수
           타입 상수명 =;
           => 절대적 , 제공해주는 값만 사용해라

           //추상 메소드
           리턴타입 메소드명();
           => 강제적 , 해당 메소드를 무조건 오버라이딩해서 재구현한후 사용해라

           //디폴트 메소드
           default 리턴타입 메소등명(){
            구현
           }
           => 선택적, 기본적으로 제공해주니 , 오버라이딩을 해서 사용해도 되고 아니면 그냥 디폴트 메소드로 사용해라

           //static 메소드
           static 리턴타입 메소드명(){
            //구현
           }
           => 강제적 , 오버라이딩 할수없고 인터페이스에서 제공되는 형태로만 사용가능
 }

※ 추상 클래스와 인터페이스 차이점

    1. 추상 클래스는 일반 메소드와 추상 메소드만 가질수 있다.
      But,인터페이스는 추상 메소드만 가질수 있다. ( 자바8 이후부턴 , static , default method 사용 가능 )
    1. 추상 클래스와 달리 인터페이스에선 변수와 메소드에 기본적으로 선언되는것이 있다. ( default , static 메소드 제외 )
      변수 : public static final
      메소드 : public abstract
      컴파일러가 자동 수행

2. 인터페이스 구현하는 방법

  • 인터페이스는 클래스에서 implements 키워드를 통해 구현 할 수 있다.
  • 구현 클래스 : 인터페이스를 구현한 클래스

<구현방식>
public class A implements 인터페이스명{}

<테스트 코드>

public interface Car {
        void go();
        void stop();
    }

    public class Sonata implements Car{
        @Override
        public void go() {
            System.out.println("소나타 간다");
        }

        @Override
        public void stop() {
            System.out.println("소나타 멈춘다.");
        }
    }

    public class TestMain{
        public static void main(String[] args) {
            Car car = new Car() {
                @Override
                public void go() {
                    System.out.println("간다");
                }

                @Override
                public void stop() {
                    System.out.println("멈춘다.");
                }
            };
            //익명 구현 객체
            //일회성의 구현 객체를 만들기 위해 사용하는 방법

            Car car1 = new Sonata();
            car1.go();
            car1.stop();
            System.out.println("=======================");
            car.go();
            car.stop();
        }
    }

3. 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

	public interface Car {
        void go();
        void stop();
    }


    public class Sonata implements Car{
        @Override
        public void go() {
            System.out.println("소나타 간다");
        }

        @Override
        public void stop() {
            System.out.println("소나타 멈춘다.");
        }

        public void sonataFunction(){
            System.out.println("sonataFunction");
        }
    }

    public class TestMain{
        public static void main(String[] args) {

            //클래스 타입 레퍼런스는 해당 클래스에 정의된 메소드 호출 가능
            Sonata sonata = new Sonata();
            sonata.go();
            sonata.stop();
            sonata.sonataFunction();

            System.out.println("====================");

            //인스턴스 레퍼런스는 해당 인터페이스를 구현한 캘래스 인스턴스를 가리킬 수 있고 해당 인터페이스에 선언된 메서드만 호출 가능
            Car car1 = new Sonata();
            car1.go();
            car1.stop();
        }
    }

4.인터페이스 상속

인터페이스는 인터페이스간에 상속을 할수있다.
클래스와 달리 다중상속이 가능
extends 키워드 사용
인터페이스를 구현한 클래스는 모든 상위 인터페이스를 오버라이딩 해야함

public interface ObjectCar {
    void carTypePrint();
}

public interface Car extends ObjectCar{
    void go();
    void stop();
}


public class Sonata implements Car{

    @Override
    public void go() {
      System.out.println("소나타 간다");
    }

    @Override
    public void stop() {
      System.out.println("소나타 멈춘다.");
    }

    public void sonataFunction(){
      System.out.println("sonataFunction");
    }

    @Override
    public void carTypePrint() {
      System.out.println("Real Car");
    }
}

5.인터페이스의 기본 메소드(Default Method), 자바 8

자바8부터 생긴 기능
interface에서도 메소드 구현 가능!!
메소드 signatures뿐만 아니라 implementation(구현)을 할수있게 됨
구현체가 모르게 추가된 기능으로 그만큼의 리스크가 존재

public default 리턴타입 메소드명(){
    선언
}

오버라이딩을 강제 시키지 않는다.


public interface Car{
  void go();
  void stop();
  default void autoGo(){
  	System.out.println("autoGo");
  }
}

public Acar implements Car{

  @Override
  public void go(){

  }

  @Override
  public void stop(){

  }

}

6.인터페이스의 static 메소드 , 자바8

interface에서도 메소드 구현 가능!!
메소드 signatures뿐만 아니라 implementation(구현)을 할수있게 됨
static 메소드이기 때문에 클래스명으로 호출해야함
오버라이딩 불가능
해당 인터페이스와 관련된 유틸 , 공통 메소드를 제공하기 위한 용도로 사용

static 리턴타입 메소드명() {
}

7.(Optional) 인터페이스의 private 메소드 , 자바 9

    1. private interface method는 abstract가 될수없다.
    1. private method드는 인터페이스 내부에서만 사용 가능하다.
    1. private static , non-static method 둘다 가능
    1. private static 메소드는 인터페이스내의 static , non-static method에서 사용가능
    1. private non-static 메소드는 private static method에서 사용 불가능

Q. 왜 만들었을까?
A. 특정 기능을 처리하는 내부 method임에도 불구하고 외부에 공개 될수있는 public method로 만들어야 했음.
=> 코드 중복을 피하고 인터페이스에 대한 캡슐화 가능

참고) https://howtodoinjava.com/java9/java9-private-interface-methods/
https://wookcode.tistory.com/19?category=979588

profile
Dev.Vinch

0개의 댓글