권한 A, B, C 가 있고 이중 권한이 A < B < C 로 진행 하고 싶은경우 계층권한을 사용하면 된다.
(사용방법 으로는 SecurityConfig 파일 내부에 🔽)
// 계층 권한 설정
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_C > ROLE_B > ROLE_A"); // 권한 설정 순위 셋팅
return hierarchy;
}
⇒ 해당 코드를 추가 해주고
// 이제 내부에 어떤 경로에 요청이 왔을때에 어떤 응답을 해줄지 설정 해주면 된다.
// 작성방식은 람다식으로 해주면 된다.
http.authorizeHttpRequests((auth) -> auth
.requestMatchers("/","/login").permitAll()
.requestMatchers("/my/**").hasAnyRole("A") // <- A,B,C 모두 들어감
.requestMatchers("/admin").hasAnyRole("C") // <- C만 들어갈 수 있음
.anyRequest().authenticated()
);
CSRF (Cross Site Request Forgery) 로 요청을 위조하여 사용자가 원하지 않아도 서버측으로 특정 요청을 강제로 보내는 방식
개발 환경에서 Security Config 클래스를 통해 csrf 설정을 disable 설정을 했지만 실제 서비스 환경에서는 csrf 공격을 방지하기위해 csrf disable 설정을 제거하고 추가적인 설정을 진행해야 한다.
(기존 개발 환경에서 🔽)
// 로그인을 하게 되면 csrf 라는 토큰이 필요한데 지금 과정에서는 disable 상태로 두고 개발함
http.csrf((auth) -> auth.disable());
(배포할 시에 수정 코드 🔽)
// 로그인을 하게 되면 csrf 라는 토큰이 필요한데 지금 과정에서는 disable 상태로 두고 개발함
// http.csrf((auth) -> auth.disable());
이렇게 csrf 코드를 주석 처리 해주면 로그인 기능을 사용 할 수 없게 된다.
해당 코드를 주석 처리 하게 되면 enable 처리가 된어 csrf 설정이 진행된다.
enable 처리가 되면 스프링 시큐리티는 CsrfFilter를 통해
POST, PUT, DELETE 요청에 대해 토큰 검증을 진행한다.
(그래서 csrf 토큰을 관리하는 시스템을 구축하여야 한다. 🔽)
(mustache- post 요청에서 설정 기준)
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Login Page</title>
</head>
<body>
login page
<hr>
<form action="/loginProc" method="post" name="loginForm">
<input id="username" type="text" name="username" placeholder="id"/>
<input id="password" type="password" name="password" placeholder="password"/>
<input type="hidden" name="_csrf" value="{{_csrf.token}}"/>
<!-- 위의 코드를 집어 넣어 post 요청 코드에 같이 보낸다. -->
<input type="submit" value="login"/>
</form>
</body>
</html>
사용자가 로그인을 하게 되면 사용자의 정보는 SecurityContextHolder에 의해 서버 세션에 관리된다. → 이번 시간에는 세션의 소멸 시간, 아이디당 세션 생성 개수 설정하는것을 배울 예정이다.
세션 소멸 시간 설정
세션 타임 아웃 설정 → 특정 요청을 수행한 뒤 설정 시간 만큼만 유지
(application.properties 파일에서 🔽)
server.servlet.session.timeout=3000
⇒ 위와 같이 작성하면 3000 초간 로그인이 유지된다.
다중 로그인 설정
동일한 아이디로 다중 로그인을 진행할 경우 설정 방법
공식 문서의 sesstion Management 를 통해 구현 방법이 나와있다.
(SecurityConfig.java 파일에서 🔽)
// 다중 로그인 설정
http.sessionManagement((auth) -> auth
.maximumSessions(1) // <- 하나의 아이디에 동시접속 수
.maxSessionsPreventsLogin(true) // <- true: 해당 값을 초과할 경우 새 로그인 차단
// false: 기존 로그인 아웃 새로그인 진행
);
세션 고정 보호 (해커의 admin 세션 탈취를 보호)
sesstionManagement((auth) → auth.sessionFixation().none()); : 로그인 시 세션 정보 변경 안함
sesstionManagement((auth) → auth.sessionFixation().newSession()); : 로그인 시 세션 새로 생성
sesstionManagement((auth) → auth.sessionFixation().changeSessionId()); : 로그인 시 동일한 세션에 대한 id 변경 ⇒ 주로 이걸 사용하여 보호한다.
// 세션 고정 보호
http.sessionManagement((auth) -> auth.sessionFixation().changeSessionId());
회원정보를 통해 인증 인가 작업을 진행하기 때문에 사용자로 부터 회원 가입을 진행한 뒤 DB에 회원 정보가 존재 해야한다.
DTO, Entity, Repository 3개의 관계 ??
Entity (엔티티)
DB 의 특정 table 에 매핑 되는 자바 객체
package com.example.studysecurity240402.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Entity
@Setter // <-lombok 기능
@Getter // <-lombok 기능
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
// id 가 자동 생성 되고 자동 증가 되게
private int id;
private String username;
private String password;
private String role;
}
Repository (레포지토리)
레포지토리는 DB 조작 추상화 하는 인터페이스 입니다.
Entity 와 관련된 DB 작업을 정의 하고 실행하는 메서드(CRUD)가 제공됩니다.
Repository 는 JpaRepository , CrudRepository 같은 것을 상속 받아 사용해야 한다.
package com.example.studysecurity240402.repository;
import com.example.studysecurity240402.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
// JpaRepository 를 상속 받고 DB와 연동되는 Entity, id의 타입
}
DTO (디티오)
Entity 와 클라이언트(프론트) 간의 데이터 전송에 사용되는 객체
DTO 는 주로 데이터 전송의 목적에 따라 필요한 필드만 포함하며, 데이터베이스 조작과 관련된 메서드는 포함하지 않습니다.
package com.example.studysecurity240402.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class JoinDTO {
private String username;
private String password;
}
// 필요한 필드만 넣어서 사용 DB에 필요한 정보인 id 같은 정보 없이 클라이언트에서 넣어준 정보만
시큐리티를 사용하여 회원가입 하기
Controller 부분
package com.example.studysecurity240402.controller;
import com.example.studysecurity240402.dto.JoinDTO;
import com.example.studysecurity240402.service.JoinService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class JoinController {
@Autowired
private JoinService joinService; // <- 서비스 코드 주입 받기
@GetMapping("/join")
public String joinP() {
return "join";
}
// 회원 가입 요청이 들어오는 부분
@PostMapping("/joinProc")
public String joinProcess(JoinDTO joinDTO) { //<- 프론트에서 전달한 데이터를 받기
System.out.println(joinDTO.getUsername());
joinService.joinProcess(joinDTO); // <- service 의 해당 기능에 데이터 전달
return "redirect:/login"; // <- 잘 작동하면 해당 경로로 리다이렉트
}
}
service 부분
package com.example.studysecurity240402.service;
import com.example.studysecurity240402.dto.JoinDTO;
import com.example.studysecurity240402.entity.UserEntity;
import com.example.studysecurity240402.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class JoinService {
@Autowired
private UserRepository userRepository; // <- 레포지토리 주입 받기
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
public void joinProcess(JoinDTO joinDTO) {
UserEntity data = new UserEntity(); // <-회원 객체를 새로 생성
data.setUsername(joinDTO.getUsername()); // <- 회원 객체에 id 넣기
// 아래가 엔코더를 이용하여 해쉬화
data.setPassword(bCryptPasswordEncoder.encode(joinDTO.getPassword()));
// BCrypt를 사용한 해쉬화된 password 를 넣기
data.setRole("ROLE_USER");
// 회원 티입을 강제로 우리가 넣어준것
userRepository.save(data);
// 레포지토리의 save 기능을 사용하여 data 객체를 넣어주기
}
}
⇒ 회원 가입을 해보면
⇒ password 부분은 해쉬화 되어 들어가 있고 나머지는 입력해준 대로 잘 들어가 있는 것을 볼 수 있다.