본문 바로가기

SpringBoot

SpringBoot(20) - Security 예제(Thymeleaf), 복습 예제(회원가입)

728x90
반응형

1) SpringBoot

   1-1) Security 예제

      1-1-1) Thymeleaf

   1-2) 복습 예제

      1-2-1) 회원가입

 

 

 

 

 

1) SpringBoot

1-1) Security 예제

1-1-1) Thymeleaf

(1) [src/main/java] - [net.datasa.test] 안에 HomeController.java 파일 생성 후 아래와 같이 작성

package net.datasa.test;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class HomeController {
	// 메인화면
	@GetMapping({"", "/"})
	public String home() {
		return "home";
	}
	
	@GetMapping("view1")
	public String view1() {
		return "view1";
	}
	
	@GetMapping("view2")
	public String view2(@AuthenticationPrincipal AuthenticatedUser user) {
		
		log.debug("사용자 아이디 : {}", user.getId());
		
		return "view2";
	}
	
	// 로그인 form으로 이동
	@GetMapping("loginForm")
	public String loginForm() {
		return "loginForm";
	}
	
	@GetMapping("thymeleaf")
	public String thymeleaf() {
		return "thymeleaf";
	}
}

 

 

(2) [src/main/resources] - [templates] 안에 home.html 파일 생성 후 아래와 같이 작성

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://thymeleaf.org"
		xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
	<meta charset="UTF-8">
	<title>test_security</title>
</head>
<body>
	<h1>[ test_security ]</h1>
	
	<p>
		<a href="view1">view1</a>
	</p>
	<p sec:authorize="isAuthenticated()">
		<a href="view2">view2</a>
	</p>
	
	<p sec:authorize="not isAuthenticated()">
		<a href="loginForm">로그인</a>
	</p>
	<p sec:authorize="isAuthenticated()">
		<a href="logout">로그아웃</a>
	</p>
	
	<p>
		<a href="thymeleaf">타임리프 테스트</a>
	</p>
	
</body>
</html>

 

 

(3) [src/main/resources] - [templates] 안에 thymeleaf.html 파일 생성 후 아래와 같이 작성

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://thymeleaf.org"
		xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
	<meta charset="UTF-8">
	<title>Thymeleaf</title>
</head>
<body>
	<h1>[ Thymeleaf의 Security 관련 속성, 객체 ]</h1>
	
	<!-- 로그인하지 않은 경우 -->
	<div sec:authorize="not isAuthenticated()">
		<p>로그인하지 않음</p>
		<p th:text="${#authentication}"></p>
	</div>
	
	<!-- 로그인한 경우 -->
	<div sec:authorize="isAuthenticated()">
		<p>로그인함</p>
		
		<p>* ROLE_USER 권한을 가지고 있는지</p>
		<p sec:authorize="hasRole('USER')">일반 회원입니다.</p>
		<p>* ROLE_ADMIN 권한을 가지고 있는지</p>
		<p sec:authorize="hasRole('ADMIN')">관리자입니다.</p>
		
		<p>* 로그인한 아이디</p>
		<p sec:authentication="name"></p>
		<p>* 로그인 정보</p>
		<p sec:authentication="principal"></p>
		<p>* 권한</p>
		<p sec:authentication="principal.authorities"></p>
		<p>* 이름</p>
		<p sec:authentication="principal.name"></p>
		
		<p>* 인증 정보</p>
		<p th:text="${#authentication}"></p>
		<p th:text="${#authentication.name}"></p>
		<p th:text="${#authentication.principal}"></p>
		<p th:text="${#authentication.principal.name}"></p>
		<p th:text="${#authentication.principal.enabled}"></p>
		
		<p>* 로그인 아이디 확인 예</p>
		<p th:if="${#authentication.name == 'abc'}">ID는 abc입니다.</p>
		
		<p sec:authorize="isAuthenticated()"
			th:if="${#authentication.name == 'abc'} or ${#authorization.expression('hasRole(''ADMIN'')')}">삭제</p>
		
	</div>
	
</body>
</html>

 

 

(4) [src/main/java] - [net.datasa.test] 안에 WebSecurityConfig.java 파일 생성 후 아래와 같이 작성

package net.datasa.test;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    //로그인 없이 접근 가능한 경로
    private static final String[] PUBLIC_URLS = {
            "/"                 //메인화면
            , "/view1"          //로그인 없이 접근할 수 있는 페이지
            , "/thymeleaf"
            , "/images/**"
            , "/css/**"
            , "/js/**"
    };

    // Bean : 메모리에 만들어진 객체(즉, 객체를 생성해서 메모리에 로드시켜 놓는 역할을 한다!)
    @Bean
    protected SecurityFilterChain config(HttpSecurity http) throws Exception {
        http
            //요청에 대한 권한 설정
            .authorizeHttpRequests(author -> author
                .requestMatchers(PUBLIC_URLS).permitAll()   //모두 접근 허용
                // 모든 요청은 인증이 필요하다는 코드이며, 바로 위의 코드만 인증 없이 모두 접근 가능하다!
                .anyRequest().authenticated()               //그 외의 모든 요청은 인증 필요
            )
            //HTTP Basic 인증을 사용하도록 설정
            .httpBasic(Customizer.withDefaults())
            //폼 로그인 설정
            .formLogin(formLogin -> formLogin
                    .loginPage("/loginForm")                //로그인폼 페이지 경로
                    .usernameParameter("id")                //폼의 ID 파라미터 이름
                    .passwordParameter("password")          //폼의 비밀번호 파라미터 이름
                    .loginProcessingUrl("/login")           //로그인폼 제출하여 처리할 경로
                    .defaultSuccessUrl("/")                 //로그인 성공 시 이동할 경로
                    .permitAll()                            //로그인 페이지는 모두 접근 허용
            )
            //로그아웃 설정
            .logout(logout -> logout
                    .logoutUrl("/logout")                   //로그아웃 처리 경로
                    .logoutSuccessUrl("/")                  //로그아웃 성공 시 이동할 경로
            );

        http
            .cors(AbstractHttpConfigurer::disable)
            .csrf(AbstractHttpConfigurer::disable);

        return http.build();
    }

    //비밀번호 암호화를 위한 인코더를 빈으로 등록
    @Bean
    public BCryptPasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

 

 

(5) 결과 화면

첫 접속 화면

 

 

 

"타임리프 테스트" 문구 클릭 시 화면

- "로그인" 전 화면

 

- "로그인" 후 화면

 

 

 

 

1-2) 복습 예제

[web5] 안에 build.gradle 파일 내용 아래와 같이 작성

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.3.2'
	id 'io.spring.dependency-management' version '1.1.6'
}

group = 'net.datasa'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	// implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	// implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.mysql:mysql-connector-j'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	// testImplementation 'org.springframework.security:spring-security-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

 

[src/main/resources] - [templates] 안에 application.properties 파일 내용 아래와 같이 작성

spring.application.name=web5

#접속 포트번호
server.port=8888
#Context Path
server.servlet.context-path=/

#Logback 사용. 전체를 대상으로 로깅 레벨 지정
#error>warn>info>debug>trace
logging.level.root=info
#특정 패키지를 대상으로 로깅 레벨 지정
logging.level.net.datasa.web5=debug

#세션의 유지 시간 (기본값은 30분 = 1800초)
#단위 : s (seconds), m (minutes), h (hours), d (days)
server.servlet.session.timeout=60m

# MySQL 데이터베이스 설정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Asia/Seoul&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root

# JPA 설정
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.type.descriptor.sql=trace

spring.jackson.time-zone=Asia/Seoul

 

 

[web5] 안에 web5.txt 파일 생성 후 아래와 같이 내용 정리

새 프로젝트 : web5
기본 패키지 : net.datasa.web5
의존성 추가 : 기본 4개 + JPA + MySQL 드라이버 + spring Security
경로 : http://localhost:8888

패키지
net.datasa.web5.controller
net.datasa.web5.domain
net.datasa.web5.domain.entity
net.datasa.web5.domain.dto
net.datasa.web5.repository
net.datasa.web5.service
net.datasa.web5.security
net.datasa.web5.util

* 각 기능별 경로
/member/joinForm : 가입폼
/member/join : 가입처리
/member/loginForm : 로그인폼
/member/login : 로그인 처리
/member/info : 개인정보 수정
/board/write : 글쓰기
/board/delete : 글삭제
... 생략

 

 

 

1-2-1) 회원가입

(1) [src/main/resources] - [templates] 안에 home.html 파일 생성 후 아래와 같이 작성

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://thymeleaf.org"
		xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
	<meta charset="UTF-8">
	<title>web5</title>
</head>
<body>
	<h1>[ web5 ]</h1>
	
	<p>
		<a href="member/joinForm">회원가입</a>
	</p>
	<p>
		<a href="">로그인</a>
	</p>
	<p>
		<a href="">로그아웃</a>
	</p>
	<p>
		<a href="">개인정보 수정</a>
	</p>
	<p>
		<a href="">게시판</a>
	</p>
	
</body>
</html>

 

 

(2) 회원 정보 저장(회원가입)을 위한 DB 구조 설계

-- DB 구조
-- 회원정보(테이블명 : web5_member)
create table web5_member(
--  컬럼명              자료형            제약조건           						설명
	member_id         varchar(30)    primary key,    						-- 회원을 구분하는 아이디  
	member_password   varchar(100)   not null,       						-- 비밀번호(암호화)      
	member_name       varchar(30)    not null,       						-- 회원 이름      
	email             varchar(50),                   						-- 이메일  
	phone             varchar(30),                   						-- 전화번호
	address           varchar(200),                  						-- 주소
	enabled           tinyint(1)     default(1) check(enabled in (1, 0)),   -- 계정상태(1: 사용가능, 0: 사용 불가능)	
	rolename          varchar(30)    default('ROLE_USER') check(rolename in ('ROLE_USER', 'ROLE_ADMIN'))   -- 사용자 구분('ROLE_USER', 'ROLE_ADMIN' 중 하나)
);

select * from web5_member;

drop table web5_member;

commit;

 

 

(3) [src/main/java] - [net.datasa.web5.domain.entity] 안에 MemberEntity.java 파일 생성 후 아래와 같이 작성

package net.datasa.web5.domain.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name="web5_member")
public class MemberEntity {
	@Id
	@Column(name="member_id", nullable = false, length = 30)
	private String memberId;
	
	@Column(name="member_password", nullable = false, length = 100)
	private String memberPassword;
	
	@Column(name="member_name", nullable = false, length = 30)
	private String memberName;
	
	@Column(name="email", length = 50)
	private String email;
	
	@Column(name="phone", length = 30)
	private String phone;
	
	@Column(name="address", length = 200)
	private String address;
	
	@Column(name="enabled", columnDefinition = "tinyint(1) default(1) check(enabled in (1, 0))")
	private Boolean enabled;
	
	@Column(name="rolename", length = 30, columnDefinition = "varchar(30) default('ROLE_USER') check(rolename in ('ROLE_USER', 'ROLE_ADMIN'))")
	private String rolename;
}