각진 세상에 둥근 춤을 추자

[Spring Boot] Spring Security 처리 과정 + 로그인 예제 (인증, 인가) 본문

Spring

[Spring Boot] Spring Security 처리 과정 + 로그인 예제 (인증, 인가)

circle.j 2023. 1. 12. 19:36
인증(Authentication)과 인가(Authorization)

 

  • 인증(Authentication): 해당 사용자가 본인이 맞는지 확인
  • 인가(Authorization): 해당 사용자가 요청하는 자원을 실행할 수 있는 권한이 있는가 확인

 

 

 

  • Principal(접근 주체) : 보호받는 Resource에 접근하는 대상 → 아이디
  • Credential(비밀번호) : Resource에 접근하는 대상의 비밀번호 → 비밀번호

 

 

 


Spring Security

 

  • Spring Security는 인증과 인가 관련 기능 구현을 손쉽게 처리 해주는 라이브러리
  • Spring Security의 인증을 처리 방식의 기본은 HttpSession 방식
  • Spring Security는 서블릿 필터(Servlet Filter) 기반으로 동작하고, '인증'과 '권한' 등 다양한 기능을 Filter로 제공

 

 

 


Spring Security 동작 방식

 

 

 

 

 

  1. Client가 로그인 시도 (Httprequest)
  2. AuthenticationFilter -> UserDetails로 사용자가 입력한 정보가 맞는지 확인 
  3. AuthorizationFilter로 권한 부여 

 

 

 


1. 프로젝트 생성하기

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

 

 


2. application.properties

프로젝트 - 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

 

 


 

3. 정적 페이지 생성

 

프로젝트 - 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>

 

 


4. VO

 

프로젝트 - 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;
}

 

 


5. Repository

 

프로젝트 - 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);
}

 

 


6. Service

 

프로젝트 - 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);
	}
	
}

 

 


7. Controller 

 

프로젝트 - 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";
	}
	
}

 

 


8. Security

 

프로젝트 - 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;
	}
	
}

 

 


9. 실행

 

[메인 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 Boot] Spring Security 회원 등록 (인증, 인가) + BCryptPasswordEncoder

이전 글에 이어서 [Spring Boot] Spring Security 처리 과정 + 로그인 예제 (인증, 인가) [Spring Boot] Spring Security 처리 과정 + 로그인 예제 (인증, 인가) 인증(Authentication)과 인가(Authorization) 인증(Authentication):

this-circle-jeong.tistory.com