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 하기 전, 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>
<?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>
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 페이지로 이동시, 로그인은 가능하지만(인증) 접근권한이 없어서 오류페이지가 뜬다.
//----------------------------------------------------------------------------------------------------------------//
}
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;
}
//---------------------------------------------------------------------------------------//
}
<!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>
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');
//------------------------- 스프링 암호화 ------------------------------- //
// 암호화 기능을 갖는 객체 생성
// @Bean 어노테이션
// : 미리 객체 만들어준다 -> PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Bean //PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
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";
}
<!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>
<insert id="join">
INSERT INTO SECURITY_MEMBER (
MEMBER_ID
,MEMBER_PW
,MEMBER_NAME
,ROLE
) VALUES(
#{memberId}
,#{memberPw}
,#{memberName}
,#{role}
)
</insert>
package kh.study.security.service;
import kh.study.security.vo.MemberVO;
public interface MemberService {
//로그인
MemberVO login(String memberId);
void join(MemberVO memberVO);
}
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);
}
}
로그인한 사용자의 정보를 가져오는 방법
- 자바에서 데이터를 가져오는 방법
- html에서 데이터를 가져오는 방법
로그인 유저 정보를 추출
- 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";
}
<!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)
- 화면 출력결과
- 콘솔 출력결과 : 아이디,권한,비밀번호 조회
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 페이지로 이동시, 로그인은 가능하지만(인증) 접근권한이 없어서 오류페이지가 뜬다.
//----------------------------------------------------------------------------------------------------------------//
}
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";
}
}
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());
}
}
}
package kh.study.security.service;
import kh.study.security.vo.MemberVO;
public interface MemberService {
//로그인
MemberVO login(String memberId);
void join(MemberVO memberVO);
}
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);
}
}
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;
}
//---------------------------------------------------------------------------------------//
}
<?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>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
index페이지입니다
</body>
</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>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
manager 페이지입니다.
</body>
</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>
<!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>
<!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>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
accessDenied
</body>
</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
- 인터페이스 정의하는 방법
- 인터페이스 구현하는 방법
- 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
- 인터페이스 상속
- 인터페이스의 기본 메소드(Default Method) , 자바 8
- 인터페이스의 static 메소드 , 자바 8
- 인터페이스의 private 메소드 , 자바 9
자바8 이전
: 상수 , 추상 메소드만 가능
=> 강제성이 강함을 유추자바8 이후
: default , static , private method가 추가됨
=> 강제성 안에 유연성을 추가함
public interface 인터페이스명{
//상수
타입 상수명 = 값;
=> 절대적 , 제공해주는 값만 사용해라
//추상 메소드
리턴타입 메소드명();
=> 강제적 , 해당 메소드를 무조건 오버라이딩해서 재구현한후 사용해라
//디폴트 메소드
default 리턴타입 메소등명(){
구현
}
=> 선택적, 기본적으로 제공해주니 , 오버라이딩을 해서 사용해도 되고 아니면 그냥 디폴트 메소드로 사용해라
//static 메소드
static 리턴타입 메소드명(){
//구현
}
=> 강제적 , 오버라이딩 할수없고 인터페이스에서 제공되는 형태로만 사용가능
}
- 추상 클래스는 일반 메소드와 추상 메소드만 가질수 있다.
But,인터페이스는 추상 메소드만 가질수 있다. ( 자바8 이후부턴 , static , default method 사용 가능 )
- 추상 클래스와 달리 인터페이스에선 변수와 메소드에 기본적으로 선언되는것이 있다. ( default , static 메소드 제외 )
변수 : public static final
메소드 : public abstract
컴파일러가 자동 수행
<구현방식>
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();
}
}
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();
}
}
인터페이스는 인터페이스간에 상속을 할수있다.
클래스와 달리 다중상속이 가능
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");
}
}
자바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(){
}
}
interface에서도 메소드 구현 가능!!
메소드 signatures뿐만 아니라 implementation(구현)을 할수있게 됨
static 메소드이기 때문에 클래스명으로 호출해야함
오버라이딩 불가능
해당 인터페이스와 관련된 유틸 , 공통 메소드를 제공하기 위한 용도로 사용static 리턴타입 메소드명() { }
- private interface method는 abstract가 될수없다.
- private method드는 인터페이스 내부에서만 사용 가능하다.
- private static , non-static method 둘다 가능
- private static 메소드는 인터페이스내의 static , non-static method에서 사용가능
- 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