[새싹] 현대IT&E 231206 기록 - Spring / Spring Boot

최정윤·2023년 12월 6일
0

새싹

목록 보기
38/67

31.3 커스텀 로그인 페이지

CommonController.java 일부

package net.developia.spring06.controller;

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.log;

import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import lombok.extern.log4j.Log4j;

@Controller
@Log4j
public class CommonController {
	@GetMapping("/accessError")
	public void accessDenied(Authentication auth, Model model) {
		log.info("acess Denied: " + auth);
		
		model.addAttribute("msg", "Access Denied");
	}
	
	@GetMapping("/customLogin")
	public void loginInput(String error, String logout, Model model) {
		log.info("error: " + error);
		log.info("logout: " + logout);
		
		if (error != null) {
			model.addAttribute("error", "Login Error Check Your Account");
		}
		
		if (logout != null) {
			model.addAttribute("logout", "Logout!!");
		}
	}
}

customLogin.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html14/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

	<h1>Custom Login Page</h1>
	<h2><c:out value="${error}"/></h2>
	<h2><c:out value="${logout}"/></h2>
	
	<form method='post' action="/login">
		<div>
			<input type='text' name='username' value='admin'>
		</div>
		<div>
			<input type='password' name='password' value='admin'>
		</div>
		<div>
			<input type='submit'>
		</div>
		<input type='hidden' name="${_csrf.parameterName}" value="${_csrf.token}"/>
	</form>
</body>
</html>

31.5 로그인 성공과 AuthenticationSuccessHandler

CustomLoginSuccessHandler.java

package net.developia.spring06.security;

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.log;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import lombok.extern.log4j.Log4j;

@Log4j
public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
		log.warn("Login Success");
		
		List<String> roleNames = new ArrayList<>();
		
		auth.getAuthorities().forEach(authority -> {
			roleNames.add(authority.getAuthority());
		});
		
		log.warn("ROLE NAMES: " + roleNames);
		
		if(roleNames.contains("ROLE_ADMIN")) {
			response.sendRedirect("/sample/admin");
			return;
		}
		
		response.sendRedirect("/");
	}
}

security-context.xml 일부

<security:http>
	<security:intercept-url pattern="/sample/all" access="permitAll"/>
	<security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')"/>
	<security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')"/>
	<security:intercept-url pattern="/sample/all" access="permitAll"/>
	<!-- <security:access-denied-handler error-page="/accessError"/> -->
	<security:access-denied-handler ref="customAccessDenied"/>
	<!-- <security:form-login/> -->
	<!-- <security:form-login login-page="/customLogin" /> -->
	<security:form-login login-page="/customLogin" authentication-success-handler-ref="customLoginSuccess" />
</security:http>

31.6 로그아웃의 처리와 LogoutSuccessHandler

security-context.xml 일부

<security:logout logout-url="/customLogout" invalidate-session="true"/>

CommonController.java 일부

@GetMapping("/customLogout")
	public void logoutGET() {
		log.info("custom logout");
	}

customLogout.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html14/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1> Logout Page </h1>
	
	<form action="/customLogout" method='post'>
		<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
		<button>로그아웃</button>
	</form>
</body>
</html>

admin.jsp 일부

<body>
	<h1>/sample/admin page</h1>
	
	<a href="/customLogout">Logout</a>
</body>

32. JDBC를 이용하는 간편 인증/권한 처리

32.1 JDBC를 이용하기 위한 테이블 설정

ace.sql

create table users(
      username varchar2(50) not null primary key,
      password varchar2(50) not null,
      enabled char(1) default '1');

      
 create table authorities (
      username varchar2(50) not null,
      authority varchar2(50) not null,
      constraint fk_authorities_users foreign key(username) references users(username));
      
 create unique index ix_auth_username on authorities (username,authority);


insert into users (username, password) values ('user00','pw00');
insert into users (username, password) values ('member00','pw00');
insert into users (username, password) values ('admin00','pw00');

insert into authorities (username, authority) values ('user00','ROLE_USER');
insert into authorities (username, authority) values ('member00','ROLE_MANAGER'); 
insert into authorities (username, authority) values ('admin00','ROLE_MANAGER'); 
insert into authorities (username, authority) values ('admin00','ROLE_ADMIN');
commit;


select * from users;

select * from authorities order by authority;

32.1.1 PasswordEncoder 문제 해결

CustomNoOpPasswordEncoder.java

package net.developia.spring06.security;

import org.springframework.security.crypto.password.PasswordEncoder;

import lombok.extern.log4j.Log4j;

@Log4j
public class CustomNoOpPasswordEncoder implements PasswordEncoder {@Override
	public String encode(CharSequence rawPassword) {
		
		log.warn("before encode :" + rawPassword);
	
		return rawPassword.toString();
	}

	
	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		
		log.warn("marches: " + rawPassword + ":" + encodedPassword);
		
		return rawPassword.toString().equals(encodedPassword);
	}

}

security-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:security="http://www.springframework.org/schema/security"
	xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
	<bean id="customAccessDenied" class="net.developia.spring06.security.CustomAccessDeniedHandler"></bean>
	<bean id="customLoginSuccess" class="net.developia.spring06.security.CustomLoginSuccessHandler"></bean>
	<bean id="customPasswordEncoder" class="net.developia.spring06.security.CustomNoOpPasswordEncoder"></bean>
	
	<!-- <security:http auto-config="true" use-expressions="false"> -->
	<security:http>
		<security:intercept-url pattern="/sample/all" access="permitAll"/>
		<security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')"/>
		<security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')"/>
		<security:intercept-url pattern="/sample/all" access="permitAll"/>
		<!-- <security:access-denied-handler error-page="/accessError"/> -->
		<security:access-denied-handler ref="customAccessDenied"/>
		<!-- <security:form-login/> -->
		<!-- <security:form-login login-page="/customLogin" /> -->
		<security:form-login login-page="/customLogin" authentication-success-handler-ref="customLoginSuccess" />
		<security:logout logout-url="/customLogout" invalidate-session="true"/>
	</security:http>
	
	<security:authentication-manager>
		<security:authentication-provider>
			<security:jdbc-user-service data-source-ref="dataSource"/>
			<security:password-encoder ref="customPasswordEncoder"/>
 			<!-- <security:user-service>
				<security:user name="member" password="{noop}member" authorities="ROLE_MEMBER"/>
				<security:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN"/>
			</security:user-service> -->
		</security:authentication-provider>
	</security:authentication-manager>
</beans>

32.2 기존의 테이블을 이용하는 경우

32.2.1 인증/권한을 위한 테이블 설계

ace.sql

create table tbl_member(
      userid varchar2(50) not null primary key,
      userpw varchar2(100) not null,
      username varchar2(100) not null,
      regdate date default sysdate, 
      updatedate date default sysdate,
      enabled char(1) default '1');


create table tbl_member_auth (
     userid varchar2(50) not null,
     auth varchar2(50) not null,
     constraint fk_member_auth foreign key(userid) references tbl_member(userid)
);

pom.xml 일부

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-test</artifactId>
		    <version>${org.springframework-version}</version>
		</dependency>

MemverTests.java

package net.developia.spring06.security;

import java.sql.Connection;
import java.sql.PreparedStatement;

import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
	"file:src/main/webapp/WEB-INF/spring/root-context.xml",
	"file:src//main/webapp/WEB-INF/spring/security-context.xml"
})
@Log4j
public class MemberTests {
	
	@Setter(onMethod_ = @Autowired)
	private PasswordEncoder pwencoder;
	
	@Setter(onMethod_ = @Autowired)
	private DataSource ds;
	
	@Test
	public void testInsertMemeber() {
		String sql = "insert into tbl_member(useid, userpw, username) values (?,?,?)";
		
		for(int i = 0; i < 100; i++) {
			Connection con = null;
			PreparedStatement pstmt = null;
			
			try {
				con = ds.getConnection();
				pstmt = con.prepareStatement(sql);
				
//				pstmt.setString(2, pwencoder.encode("pw" + i));
				
				if(i < 80) {
					pstmt.setString(1, "user"+i);
					pstmt.setString(2, "ROLE_USER");
				} else if (i < 90) {
					pstmt.setString(1,  "manager"+i);
					pstmt.setString(2, "ROLE_USER");
				} else {
					pstmt.setString(1,  "admin"+i);
					pstmt.setString(2, "ROLE_USER");
				}
				pstmt.executeUpdate();
			} catch(Exception e) {
				e.printStackTrace();
			} finally {
				if(pstmt != null) { try {pstmt.close(); } catch (Exception e) {} }
				if(con != null) {try { con.close(); } catch(Exception e) {} }
			}
		} //end for
	}

}

32.2.3 쿼리를 이용하는 인증

ace.sql

select
    userid username, userpw password, enabled
from
    tbl_member
where userid = 'admin90';


select
    userid username, auth authority
from
    tbl_member_auth
where userid = 'admin90';

security-context.xml 일부

<security:authentication-manager>
		<security:authentication-provider>
			<!-- <security:jdbc-user-service data-source-ref="dataSource"/> -->
			<security:jdbc-user-service
			data-source-ref="dataSource"
			users-by-username-query="select userid, userpw, enabled from tbl_member where userid = ? "
			authorities-by-username-query="select userid, auth from tbl_member_auth where userid = ? " />
			<!-- <security:password-encoder ref="customPasswordEncoder"/> -->
			<security:password-encoder ref="bcryptPasswordEncoder" />
 			<!-- <security:user-service>
				<security:user name="member" password="{noop}member" authorities="ROLE_MEMBER"/>
				<security:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN"/>
			</security:user-service> -->
		</security:authentication-provider>
	</security:authentication-manager>

SpringBoot

00 개발 환경 구축하기

0.2 스프링 부트3 프로젝트 만들기

build.gradle

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

group = 'me.choijeongyun'
version = '1.0'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

SpringBootDeveloperApplication.java

package org.example.springbootdeveloper;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootDeveloperApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDeveloperApplication.class, args);
    }
}

01 자바 백엔드 개발자가 알아두면 좋은 지식

1.1 서버와 클라이언트

1.1.1 클라이언트란?

  • 클라이언트는 서버로 요청하는 프로그램을 모두 일컬어 말한다.
  • 웹 브라우저가 대표적인 클라이언트 중 하나이다.

1.1.2 서버란?

  • 서버는 클라이언트의 요청을 받아 처리하는 주체이다.
  • 클라이언트가 데이터를 요청했다면 데이터를, 서버 내에서 처리만 해달라는 요청을 했다면 해당 요청만 처리할 수도 있다.

1.2 데이터베이스

  • 데이터베이스는 여러 사람이 데이터를 한 군데에 모아놓고 여러 사람이 사용할 목적으로 관리하는 데이터 저장소이다.
  • MySQL, Oracle, postgreSQL 등은 데이터베이스가 아니라 데이터베이스를 관리하기 위한 시ㅌ스템의 이름이다.

1.2.1 RDB란?

  • 데이터베이스는 여러가지로 구분할 수 있는데 RDB, NoSQLm NewSQL 등 여러 종류가 있다.
  • 그 중 가장 많이 사용하는 DB는 RDB(관계형데이터베이스)이다.
  • 행과 열로 이루어진 테이블로 관리하며, 기본키를 사용해 각 행을 식별한다.

1.2.2 SQL이란?

  • 쿼리, 즉 데이터 검색을 하는 언어이다.
  • SQL도 하나만 있는 것이 아니라 ANSI 표준 SLQ이 있고, 각 RDB별로 방언이 있다.

1.2.3 NoSQL이란?

  • NoSQL의 뜻이 SQL을 안 쓴다는 의미로 사용된다.

02 스프링 부트3 시작하기

2.3 스프링 부트3 둘러보기

2.3.1 첫 번째 스프링 부트3 예제 만들기

TestController.java

package org.example.springbootdeveloper;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    @GetMapping("/test")
    public String test() {
        return "Hello, world!";
    }
}

http://localhost:8080/test 실행화면

2.3.3 자동 구성


03 스프링 부트3 구조 이해하기

3.1 스프링 부트3 구조 살펴보기

3.2 스프링 부트3 프로젝트 발전시키기

3.2.1 build.gradle에 의존성 추가하기

build.gradle

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

group = 'org.exmaple'
version = '1.0'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
//    implementation 'org.springframework.boot:spring-boot-starter-web'
//    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    implementation 'org.springframework.boot:sping-boot-starter-data-jap'
    runtimeOnly 'com.h2database:h2' // 인메모리 데이터베이스
    compileOnly 'org.projectlombok:lombok' // 롬복
    annotationProcessor 'org.projectlombok:lombok'
}

test {
    useJUnitPlatform()
}

3.2.2 프레젠테이션, 서비스, 퍼시스턴스 계층 만들기

TestController.java

package org.example.springbootdeveloper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Member;

@RestController
public class TestController {
    @Autowired // TestService 빈 주입
    TestService testService;

    @GetMapping("/test")
    public List<Member> getAllMembers() {
        List<Member> members = testService.getAllMembers();
        return members;
    }
}

TestService.java

package org.example.springbootdeveloper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class TestService {
    @Autowired
    MemberRepository memberRepository;

    public List<Member> getAllMembers() {
        return memberRepository.findAll();
    }
}

Member.java

package org.example.springbootdeveloper;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
@Entity
public class Member {
    @Id
    @GenratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", updatable = false)
    private Long id; // DB 테이블의 'id' 컬럼과 매칭

    @Column(name = "name", nullable = false)
    private String name; // DB 테이블의 'name' 컬럼과 매칭
}

MemberRepository.java

package org.example.springbootdeveloper;

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
}

3.2.4 작동 확인하기

data.sql

INSERT INTO member (id, name) VALUES (1, 'name 1')
INSERT INTO member (id, name) VALUES (2, 'name 2')
INSERT INTO member (id, name) VALUES (3, 'name 3')
profile
개발 기록장

0개의 댓글