Spring_boot/Project

[Spring] 웹 보안

달의요정루나 2021. 8. 26. 15:24

이번에 설계한 것은 로그인 페이지를 통해서

아이디와 비번을 입력했을때 사용자를 인증하는 프로젝트 입니다.

프로젝트 생성시 들어가는 라이브러리

 

applicaton.properties 설계

spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver

spring.datasource.url = jdbc:mysql://localhost/jpa_ex?characterEncoding=utf8&serverTimezone=Asia/Seoul

spring.datasource.username = root

spring.datasource.password = root

 

 

spring.jpa.hibernate.ddl-auto=create

spring.jpa.generate-ddl=false

spring.jpa.show-sql=true

spring.jpa.database=mysql

logging.level.org.hibernate=info

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect


spring.thymeleaf.cache=false

html 설계

(src/main/resources/templates)

1) index.html

<html xmlns:th="http://www.thymeleaf.org">

<head>
<title>Thymeleaf3</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
	<h1>Index Page for Everyine!!</h1>
</body>
</html>

2) guest.html

<html xmlns:th="http://www.thymeleaf.org">

<head>
<title>Thymeleaf3</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
	<h1>Guest Page for Everyine!!</h1>
</body>
</html>

3) admin.html

<html xmlns:th="http://www.thymeleaf.org">

<head>
<title>Thymeleaf3</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
	<h1>Administer Page for Everyine!!</h1>
</body>
</html>

4) manager.html

<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<body>
	<h1>Login Success ^^ !!!</h1>
</body>
</html>

5) success.html

<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<body>
	<h1>Login Success ^^ !!!</h1>
</body>
</html>

6) login.html

<html xmlns:th="http://www.thymeleaf.org">

<head>
<title>Thymeleaf3</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>

	<h2>CUSTOM LOGIN PAGE</h2>
	
	<div th:if="${param.error != null}">
		<h2>Invalid Username or password</h2>
		<h2 th:value="${param.error}"></h2>
	</div>
	
	<div th:if="${param.logout !=null}">
		<h2>You have been logged out.</h2>
	</div>
	
	<form method="post">
		<p>
			<label for="username">Username</label> <input type="text"
			id="username" name="username"/>
		</p>
		<p>
			<label for="password">Password</label> <input type="password"
			id="password" name="password"/>
		</p>
		<input type="hidden" th:name="${_csrf.parameterName}"
		th:value="${_csrf.token}"/>
		<button type="submit" class="btn">Log in</button>
	</form>
	
</body>
</html>

7) logout.html

<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<body>
	<h2>CUSTOM LOGOUT PAGE</h2>
	<form method="post">
		<h3>Logout</h3>
		<input type="hidden" th:name="${_csrf.parameterName}"
		th:value="${_csrf.token}" />
		<button type="submit" class="btn">LogOut</button>
	</form>
</body>
</html>

8) accessDenied.html

<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>AccessDenied</title>
</head>
<body>
	<div class="panel panel-default">
		<div class="panel-body">
			<div class="alert alert-danger" role="alert">
			
			<h2>Sorry, you do not have permission to view this page.</h2>
			
			Click Login at <a th:href="@{/login}">here</a>
			
			</div>
		</div>
	</div>
</body>
</html>

9) adminSecret.html

<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Guest Test</title>
</head>
<body>
	<h1>Administer Secret</h1>
</body>
</html>

10) managerSecret.html

<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Guest Test</title>
</head>
<body>
	<h1>Manager Secret</h1>
</body>
</html>

SecurityConfig 설계

(org.zerock.security.SecurityConfig)

- 로그인 할때 입력해야 하는 아이디, 암호 그리고 입력시 어느 html이 작동되는지를 설계합니다.

package org.zerock.security;

import org.springframework.context.annotation.Bean;
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.PasswordEncoder;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		//아이디, 암호가 입력시 조건에 따라 이동될 html 파일들이 결정된다.
		http.authorizeRequests().antMatchers("/guest/**").permitAll()
			.and()
			.authorizeRequests().antMatchers("/manager/**").hasRole("MANAGER")
			.and()
			.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN")
			.and()
			.formLogin().loginPage("/login").defaultSuccessUrl("/success").permitAll()
			.and()
			.exceptionHandling().accessDeniedPage("/accessDenied")
			.and()
			.logout().logoutUrl("/logout").invalidateHttpSession(true);
			
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() { //입력한 암호가 맞는지 검사를 해줍니다.
		
		return new PasswordEncoder() {
			
			@Override
			public boolean matches(CharSequence rawPassword, String encodedPassword) {
				// TODO Auto-generated method stub
				return rawPassword.equals(encodedPassword);
			}
			
			@Override
			public String encode(CharSequence rawPassword) {
				// TODO Auto-generated method stub
				return rawPassword.toString();
			}
		};
	}
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    	// 페이지에 입력될 아이디와 암호들
		auth.inMemoryAuthentication()
			.withUser("manager").password("1111").roles("MANAGER")
			.and()
			.withUser("guest").password("2222").roles("BASIC")
			.and()
			.withUser("admin").password("3333").roles("ADMIN");
	}
}

MemberRepository 설계

(org.zerock.persistence.MemberRepository.java)

package org.zerock.persistence;

import org.springframework.data.jpa.repository.JpaRepository;
import org.zerock.domain.Member;

public interface MemberRepository extends JpaRepository<Member, String>{

}

 

SampleController 설계

(org.zerock.controller.SampleController.java)

페이지를 생성하고 나중에 보안을 적용해서 접근을 제어한다.

package org.zerock.controller;

import java.util.Arrays;

import javax.annotation.PostConstruct;

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.RequestMapping;
import org.zerock.domain.Member;
import org.zerock.domain.MemberRole;
import org.zerock.persistence.MemberRepository;

@Controller
public class SampleController {

	@Autowired
	private MemberRepository repo;
	
	@GetMapping("/")
	public String index() {
		return "index";
	}
	
	@RequestMapping("/guest")
	public String forGuest() {
		return "guest";
	}
	
	@RequestMapping("/manager")
	public String forManager() {
		return "manager";
	}
	
	@RequestMapping("/admin")
	public String forAdmin() {
		return "admin";
	}
	
	@PostConstruct
	public void init() { //101명의 사용자를 생성
		for (int i = 0; i <= 100; i++) {
			
			Member member = new Member();
			member.setUid(i);
			member.setUpw("pw"+i);
			member.setUname("사용자"+i);
			
			MemberRole role = new MemberRole();
			if (i<=80) { //user0~user80까지는 BASIC 권한 가짐
				role.setRoleName("BASIC");
			} else if(i<=90) { //user90까지는 MANAGER 권한 가짐
				role.setRoleName("MANAGER");
			} else { //나머지 10명은 ADMIN 권한 가짐
				role.setRoleName("ADMIN");
			}
			member.setRoles(Arrays.asList(role));
			
			repo.save(member);
		}
	}
}

Member 클래스 설계

(org.zerock.domain.Member.java)

package org.zerock.domain;

import java.time.LocalDateTime;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

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

@Getter
@Setter
@Entity
@Table(name="tbl_members")
@EqualsAndHashCode(of="uid")
@ToString
public class Member {

	@Id
	private long uid; //회원 아이디
	private String upw; //user password
	private String uname; //user name
	
	@CreationTimestamp
	private LocalDateTime regdate;
	
	@UpdateTimestamp
	private LocalDateTime updatedate;
	
	@OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
	@JoinColumn(name="member")
	private List<MemberRole> roles; //MemberRole과 연관관계 설정
}

회원의 아이디를 의미하는 uid와 upw, uname 등의 속성을 가지도록 설계한다.

 

MemberRole 설계

(org.zerock.domain.MemberRole.java)

package org.zerock.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

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

@Getter
@Setter
@Entity
@Table(name = "tbl_member_roles")
@EqualsAndHashCode(of="fno")
@ToString
public class MemberRole {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long fno;
	
	private String roleName;
}

회원이 가지는 권한에 대한 이름을 가지는 구조

 

LoginController 설계

(org.zerock.controller.LoginController.java)

package org.zerock.controller;

import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {

	@Secured({"ADMIN", "MANAGER"})
	@GetMapping("/login")
	public String login() {
		return "login";
	}
	
	@GetMapping("/accessDenied")
	public String accessDenied() {
		return "accessDenied";
	}
	
	@GetMapping("/logout")
	public String logout() {
		return "logout";
	}
	
	@GetMapping("/success")
	public String success() {
		return "success";
	}
	
	@Secured({"ADMIN", "MANAGER"})
	@RequestMapping("/managerSecret")
	public String managerSecret() {
		return "managerSecret";
	}
	
	@Secured({"ADMIN"})
	@RequestMapping("/adminSecret")
	public String adminSecret() {
		return "adminSecret";
	}
	
}

 

결과

로그인 하기(아이디: manager, 비밀번호: 1111)

SecurityConfig에 입력해 놓은 아이디와 비밀번호를 입력하면 위처럼 로그인에 성공한다.

 

그러나 다른 아이디나 암호를 치면 위처럼 Invalid Username or password가 뜨면서 로그인에 실패한다.

 

위 두 사진은 LoginController에서 Secured에 따라 표시된다.

managerSecret은 관리자와 매니저상태에서 들어갈 수 있고 adminSecret은 관리자만 들어갈 수 있다.

adminSecret에 매니저 상태로 들어가면 에러가 생긴다.