각진 세상에 둥근 춤을 추자
[Spring Boot] Spring Security 처리 과정 + 로그인 예제 (인증, 인가) 본문
- 인증(Authentication): 해당 사용자가 본인이 맞는지 확인
- 인가(Authorization): 해당 사용자가 요청하는 자원을 실행할 수 있는 권한이 있는가 확인
- Principal(접근 주체) : 보호받는 Resource에 접근하는 대상 → 아이디
- Credential(비밀번호) : Resource에 접근하는 대상의 비밀번호 → 비밀번호
- Spring Security는 인증과 인가 관련 기능 구현을 손쉽게 처리 해주는 라이브러리
- Spring Security의 인증을 처리 방식의 기본은 HttpSession 방식
- Spring Security는 서블릿 필터(Servlet Filter) 기반으로 동작하고, '인증'과 '권한' 등 다양한 기능을 Filter로 제공
- Client가 로그인 시도 (Httprequest)
- AuthenticationFilter -> UserDetails로 사용자가 입력한 정보가 맞는지 확인
- AuthorizationFilter로 권한 부여
New - Spring Starter Project
- Name: 프로젝트명
- Type: Maven
- Packaging: Jar
- Java Version: 11
- Language: Java
- Group, Package: kr.co.패키지명
- Spring Boot Version: 2.7.7
- Selected: Lombok, MyBatis FrameWork, MySQL Driver, Spring Boot DevTools, Spring Web, Spring Data JPA, Thymeleaf, Spring Security
프로젝트 - src/main/resources - application.properties
# 기본 개발설정
server.servlet.context-path=/Ch08
spring.thymeleaf.cache=false
# DataSource 설정
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/java2db
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# MyBatis Mapper 경로설정
mybatis.mapper-locations=classpath:mappers/**/*.xml
# JPA 설정
spring.jpa.database=mysql
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.hibernate.ddl-auto=update
프로젝트 - src/main/resources - templates - index.html 생성
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h3>Ch08.Spring Security 실습</h3>
<h4>인증</h4>
<a th:href="@{/user1/login}">HttpSession 인증 실습</a><br>
<a th:href="@{/user2/login}">Spring Security 인증 실습</a>
<h4>인가 테스트</h4>
<a th:href="@{/admin/success}">admin/success</a><br>
<a th:href="@{/member/success}">member/success</a><br>
<a th:href="@{/guest/success}">guest/success</a><br>
</body>
</html>
프로젝트 - src/main/resources - templates - user1 (폴더) 생성 - login.html, loginSuccess.html 생성 (인증 테스트)
[login.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>user1::login</title>
</head>
<body>
<h3>user1 login</h3>
<a th:href="@{/}">메인이동</a>
<form th:action="@{/user1/login}" method="post">
<table border="1">
<tr>
<td>아이디</td>
<td><input type="text" name="uid"/></td>
</tr>
<tr>
<td>비밀번호</td>
<td><input type="password" name="pass"/></td>
</tr>
<tr>
<td colspan="2" align="right"><input type="submit" value="로그인"/></td>
</tr>
</table>
</form>
</body>
</html>
[loginSuccess.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>user1::loginSuccess</title>
</head>
<body>
<h3>user1 loginSuccess</h3>
<p>
[[${session.sessUser.name}]]님 반갑습니다.<br>
<a th:href="@{/user1/logout}">로그아웃</a>
</p>
</body>
</html>
프로젝트 - src/main/resources - templates - user2 (폴더) 생성 - login.html, loginSuccess.html 생성 (인가 테스트)
[login.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>user2::login</title>
</head>
<body>
<h3>user2 login</h3>
<a th:href="@{/}">메인이동</a>
<form th:action="@{#}" method="post">
<table border="1">
<tr>
<td>아이디</td>
<td><input type="text" name="uid"/></td>
</tr>
<tr>
<td>비밀번호</td>
<td><input type="password" name="pass"/></td>
</tr>
<tr>
<td colspan="2" align="right"><input type="submit" value="로그인"/></td>
</tr>
</table>
</form>
</body>
</html>
[loginSuccess.html]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:th="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>user2::loginSuccess</title>
<sec:authentication property="principal" />
</head>
<body>
<h3>user2 loginSuccess</h3>
<p>
<span sec:authentication="name">홍길동</span> 님, 반갑습니다.<br>
<a th:href="@{/user2/logout}">로그아웃</a>
</p>
</body>
</html>
프로젝트 - src/main/resources - templates - admin (폴더) 생성 - success.html 생성 (인증 테스트)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>admin/success</title>
</head>
<body>
<h3>admin/success</h3>
<p>
접근확인
</p>
<a th:href="@{/}">메인이동</a>
</body>
</html>
프로젝트 - src/main/resources - templates - guest (폴더) 생성 - success.html 생성 (인증 테스트)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>guest/success</title>
</head>
<body>
<h3>guest/success</h3>
<p>
접근확인
</p>
<a th:href="@{/}">메인이동</a>
</body>
</html>
프로젝트 - src/main/resources - templates - member (폴더) 생성 - success.html 생성 (인증 테스트)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>member/success</title>
</head>
<body>
<h3>member/success</h3>
<p>
접근확인
</p>
<a th:href="@{/}">메인이동</a>
</body>
</html>
프로젝트 - src/main/java - kr.co.user - vo 패키지 생성 - User1VO.java 생성
package kr.co.ch08.vo;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Entity
@Table(name = "user1")
public class User1VO {
@Id
private String uid;
private String pass;
private String name;
private String hp;
private int age;
}
프로젝트 - src/main/java - kr.co.user - vo - User2VO.java 생성
package kr.co.ch08.vo;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Entity
@Table(name = "user2")
public class User2VO {
@Id
private String uid;
private String pass;
private String name;
private String hp;
private int age;
}
프로젝트 - src/main/java - kr.co.user - repository 패키지 생성 - User1Repo.java 생성
package kr.co.ch08.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import kr.co.ch08.vo.User1VO;
public interface User1Repo extends JpaRepository<User1VO, String> {
public User1VO findUser1VOByUidAndPass(String uid, String pass);
}
프로젝트 - src/main/java - kr.co.user - repository 패키지 - User2Repo.java 생성
package kr.co.ch08.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import kr.co.ch08.vo.User2VO;
public interface User2Repo extends JpaRepository<User2VO, String> {
public User2VO findUser2VOByUidAndPass(String uid, String pass);
}
프로젝트 - src/main/java - kr.co.user - service 패키지 생성 - User1Service.java 생성
package kr.co.ch08.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import kr.co.ch08.repository.User1Repo;
import kr.co.ch08.vo.User1VO;
@Service
public class User1Service {
@Autowired
private User1Repo repo;
public User1VO selectUser1(String uid, String pass) {
return repo.findUser1VOByUidAndPass(uid, pass);
}
}
프로젝트 - src/main/java - kr.co.user - service 패키지 - User2Service.java 생성
package kr.co.ch08.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import kr.co.ch08.repository.User2Repo;
import kr.co.ch08.vo.User2VO;
@Service
public class User2Service {
@Autowired
private User2Repo repo;
public User2VO selectUser2(String uid, String pass) {
return repo.findUser2VOByUidAndPass(uid, pass);
}
}
프로젝트 - src/main/java - kr.co.user - controller 패키지 생성 - MainController.java 생성
package kr.co.ch08.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MainController {
@GetMapping(value = {"/", "/index"})
public String index() {
return "/index";
}
@GetMapping("/admin/success")
public String adminSuccess() {
return "/admin/success";
}
@GetMapping("/member/success")
public String memberSuccess() {
return "/member/success";
}
@GetMapping("/guest/success")
public String guestSuccess() {
return "/guest/success";
}
}
프로젝트 - src/main/java - kr.co.user - controller 패키지 - User1Controller.java 생성
package kr.co.ch08.controller;
import javax.servlet.http.HttpSession;
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;
import kr.co.ch08.service.User1Service;
import kr.co.ch08.vo.User1VO;
@Controller
public class User1Controller {
@Autowired
private User1Service service;
@GetMapping("/user1/login")
public String login() {
return "/user1/login";
}
@PostMapping("/user1/login")
public String login(String uid, String pass, HttpSession session) {
User1VO user = service.selectUser1(uid, pass);
if(user != null) {
// 회원이 맞을 경우
session.setAttribute("sessUser", user);
return "redirect:/user1/loginSuccess";
}else {
// 회원이 아닌 경우
return "redirect:/user1/login?success=100";
}
}
@GetMapping("/user1/loginSuccess")
public String loginSuccess() {
return "/user1/loginSuccess";
}
@GetMapping("/user1/logout")
public String logout() {
return "redirect:/user1/login?success=200";
}
}
프로젝트 - src/main/java - kr.co.user - controller 패키지 - User2Controller.java 생성
package kr.co.ch08.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class User2Controller {
@GetMapping("/user2/login")
public String login() {
return "/user2/login";
}
@GetMapping("/user2/loginSuccess")
public String loginSuccess() {
return "/user2/loginSuccess";
}
}
프로젝트 - src/main/java - kr.co.user - security 패키지 생성 - SecurityConfig.java 생성
package kr.co.ch08.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 인가(접근권한) 설정 (모든 링크(사용자)에 대해 허용을 해 준 상태, 권한관리필터)
http.authorizeHttpRequests().antMatchers("/").permitAll();
// admin 하위의 모든 자원 -> "ADMIN"에게 부여
http.authorizeHttpRequests().antMatchers("/admin/**").hasRole("ADMIN");
// member 하위의 모든 자원 -> "ADMIN", "MEMBER" 에게 부여
http.authorizeHttpRequests().antMatchers("/member/**").hasAnyRole("ADMIN", "MEMBER");
// GUEST는 무권한 -> 생략
// 사이트 위변조 요청 방지
http.csrf().disable();
// 로그인 설정
http.formLogin()
.loginPage("/user2/login")
.defaultSuccessUrl("/user2/loginSuccess")
.failureUrl("/user2/login?success=100")
.usernameParameter("uid")
.passwordParameter("pass");
// 로그아웃 설정
http.logout()
.invalidateHttpSession(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/user2/logout"))
.logoutSuccessUrl("/user2/login?success=200");
}
@Autowired
private SecurityUserService service;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 로그인 인증처리 서비스 등록
auth.userDetailsService(service).passwordEncoder(new MessageDigestPasswordEncoder("SHA-256"));
}
}
프로젝트 - src/main/java - kr.co.user - security 패키지 - SecurityUserService.java 생성
package kr.co.ch08.security;
import org.springframework.beans.factory.annotation.Autowired;
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 kr.co.ch08.repository.User2Repo;
import kr.co.ch08.vo.User2VO;
@Service
public class SecurityUserService implements UserDetailsService{
@Autowired
private User2Repo repo;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 스프링 시큐리티 인증 동작방식은 아이디/패스워드를 한 번에 조회하는 방식이 아닌
// 아이디만 이용해서 사용자 정보를 로딩하고 이후 패스워드를 검증하는 방식이다.
// security: 사용자 계정 먼저 확인 (username)
User2VO user = repo.findById(username).get();
if(user == null) {
throw new UsernameNotFoundException(username);
}
// 인증 거치는 과정 (사용자가 폼에 입력한 정보가 맞는지 확인)
UserDetails userDts = User.builder()
.username(user.getUid())
.password(user.getPass())
.roles("MEMBER") //마지막에 권한 주기
.build();
return userDts;
}
}
[메인 index 페이지] http://localhost:8080/Ch08/
['HttpSession 인증 실습' 버튼 클릭 - 로그인] http://localhost:8080/Ch08/user1/login
['Spring Security 인증 실습' 버튼 클릭 - 로그인]
http://localhost:8080/Ch08/user2/login
http://localhost:8080/Ch08/user2/loginSuccess
현재 로그인 시 MEMBER 권한 부여 (SecurityUserService.java)
(권한: ADMIN > MEMBER > GUEST)
MEMBER 권한 로그인 상태로 각각 admin/success, member/success, guest/success 클릭 시
이어서 다음 글
[Spring Boot] Spring Security 회원 등록 (인증, 인가) + BCryptPasswordEncoder
'Spring' 카테고리의 다른 글
[Spring Boot] WebSecurityConfigurerAdapter 지원 중단 (0) | 2023.01.16 |
---|---|
[Spring Boot] Spring Security 회원 등록 (인증, 인가) + BCryptPasswordEncoder (1) | 2023.01.13 |
[Spring Boot] JPA 사용 - 간단 회원 정보 입력, 목록, 수정, 삭제 (0) | 2023.01.11 |
[Spring Boot] 스프링부트 DB 연동 + Lombok 설치 +간단 회원 정보 입력, 목록, 수정 (0) | 2023.01.10 |
[Spring Boot] MAC 스프링부트 설치 및 개발환경 구축 (1) | 2023.01.10 |