save함수의 else칸을 update를 위해 비워놓았다.
3가지 단계가 있었는데 uuid는 이미 만들어져있으니 url만 만들고 생성자에 넣어보자.
else {
// 기본키가 있을때 : update
// todo 1-2) 다운로드 url 생성 -> 자바함수를 이용 ※여기서 다운로드란 jsp이 spring에서 이미지를 다운받아 가져오는 것.
String fileDownload = ServletUriComponentsBuilder
.fromCurrentContextPath()// 스프링 서버 기본 주소 : localhost:8000
.path("/advanced/fileDb/") // 추가 경로 넣기 : /advanced/fileDb
.path(uuid) // uuid를 url 제일 마지막에 넣어주기
.toUriString(); // 위의 url을 하나로 합쳐주는 함수 http://localhost:8000/advanced/fileDb/xxxx 가 된다.
// todo 1-3) 생성자에 만든 url넣어주기
FileDb fileDb = new FileDb(uuid,
fileTitle,
fileContent,
file.getOriginalFilename(),
file.getBytes(),
fileDownload);
fileDb2 = fileDbRepository.save(fileDb);
}
}catch (Exception e){
log.debug(e.getMessage());
}
return fileDb2;
insert에 만들어줬던 url만든 코드를 그대로 가져와서 tempUuid만 uuid로 바꿔주었다.uuid가 이미 존재하고 if문이 끝났기에 tmepUuid는 사용할 수 없기 때문이다.
나머지는 save기능을 만들때와 똑같이 해주면된다.
// 업데이트 함수
@GetMapping("/fileDb/edition/{uuid}")
public String editionFileDb(Model model, @PathVariable String uuid){
Optional<FileDb> fileDb = fileDbService.findById(uuid);
model.addAttribute("fileDb" , fileDb.get());
return "/advanced/fileDb/edit_fileDb.jsp";
}
@PutMapping("/fileDb/edit/{uuid}")
public RedirectView updateFileDb(@PathVariable String uuid,
@RequestParam(defaultValue = "") String fileTitle,
@RequestParam(defaultValue = "") String fileContent,
@RequestParam MultipartFile image){
try{
fileDbService.save(uuid, fileTitle, fileContent ,image);
}catch (Exception e){
log.debug(e.getMessage());
}
return new RedirectView("/advanced/fileDb");
}
<%--todo : 본문--%>
<div class="container">
<div>
<%-- todo 파일 업로드 : 전송형태 : multipart/form-data 전송--%>
<form action="/advanced/fileDb/edit/${fileDb.uuid}" method="post"
enctype="multipart/form-data"><%--파일전송은 이 enctype속성을 또 추가해주어야한다.--%>
<input type="hidden" name="_method" value="put">
<%--todo : 제목(fileTitle)--%>
<div class="mb-3">
<label for="fileTitle" class="form-label">File Title</label>
<input type="text" class="form-control" id="fileTitle" placeholder="제목입력" name="fileTitle" required
value="${fileDb.fileTitle}">
</div>
<%--todo : 내용(fileContent)--%>
<div class="mb-3">
<label for="fileContent" class="form-label">File Content</label>
<input type="text" class="form-control" id="fileContent" placeholder="내용입력" name="fileContent" required
value="${fileDb.fileContent}">
</div>
<%--todo : 기존에 업로드 된 이미지 확인 --%>
<div class="mb-3 col-12 " style="width: 18rem;">
<img src="${fileDb.fileUrl}" class="card-img-top" alt="강의">
</div>
<%--todo : 파일 업로드 버튼--%>
<div class="input-group">
<%--todo : 파일 선택 상자 : 백엔드 전송--%>
<input type="file" class="form-control" name="image" required >
<%--todo : 업로든 버튼--%>
<button class="btn btn-outline-secondary" type="submit">업로드</button>
</div>
</form>
</div>
</div>
사실상 save의 jsp와 크게 다를 것은 없다.
파일 사이즈 제한 은 기본적으로 1mb이다. 그런데 이걸 바꿀수 있다.
application.properties에서
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
두 속성을 걸어주면 되고 저기서 크기를 조정해주면서 제한을 줄 수 있다.

-- TODO: 인증관련 테이블 정의
-- 공통코드 테이블은 시퀀스는 사용하지 않음
-- 공통코드 테이블의 등록된 코드는 향후에 않쓰이더라도 삭제/수정하지 않음 : 데이터가 많지않아 오버헤드가 없음
DROP TABLE TB_CODE_CATEGORY CASCADE CONSTRAINT;
DROP TABLE TB_CODE CASCADE CONSTRAINT;
-- 코드성 테이블 : 공통 코드 유형 테이블
CREATE TABLE TB_CODE_CATEGORY
(
CATEGORY_ID NUMBER NOT NULL PRIMARY KEY,
CATEGORY_NAME VARCHAR2(255)
);
-- 코드성 테이블 : 공통 코드 테이블
CREATE TABLE TB_CODE
(
CODE_ID NUMBER NOT NULL PRIMARY KEY,
CODE_NAME VARCHAR2(255),
CATEGORY_ID NUMBER NOT NULL
CONSTRAINT FK_CODE_CATEGORY_CODE REFERENCES TB_CODE_CATEGORY (CATEGORY_ID),
USE_YN VARCHAR(1) DEFAULT 'Y'
);
-- TODO: 인증관련 테이블 정의
-- 유저 테이블
-- login table ddl
DROP TABLE TB_MEMBER CASCADE CONSTRAINTS;
CREATE TABLE TB_MEMBER
(
EMAIL VARCHAR2(1000) NOT NULL PRIMARY KEY, -- id (email)
PASSWORD VARCHAR2(1000), -- 암호
NAME VARCHAR2(1000), -- 유저명
CODE_NAME VARCHAR2(1000), -- 권한코드명(ROLE_USER, ROLE_ADMIN)
DELETE_YN VARCHAR2(1) DEFAULT 'N',
INSERT_TIME VARCHAR2(255),
UPDATE_TIME VARCHAR2(255),
DELETE_TIME VARCHAR2(255)
);

원래 쓰던 api에서 새로운 기능인 security를 추가했다. security는 설치를 하자마자 보안모드로 들어간다.
security는 인증된 사람만 프로그램에 접근할 수 있게 해주는 기능이다. 시큐리티를 적용하면 접근하기가 어려워 질 수 있다. 그래서 프로그램이 어느정도 만들어진 후에 접근 하는 방법도 있다.
여기서 jsp와 login툴만 수동으로 설치해주면된다.
dependencies {
// jsp 라이브러리 추가
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper' // 추가
implementation 'jakarta.servlet:jakarta.servlet-api' //스프링부트 3.0 이상
implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api' //스프링부트 3.0 이상
implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl' //스프링부트 3.0 이상
// Todo: jsp taglib 설정 : spring security용
implementation 'org.springframework.security:spring-security-taglibs'
// sql 출력 결과를 보기위한 라이브러리 추가
implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
// 오라클 driver (과거: 11버전) -> 19버전용 라이브러리 추가 설치
// todo: 오라클 추가 라이브러리( 19c )
implementation 'com.oracle.database.jdbc:ucp:19.14.0.0'
implementation 'com.oracle.database.security:oraclepki:19.14.0.0'
implementation 'com.oracle.database.security:osdt_cert:19.14.0.0'
implementation 'com.oracle.database.security:osdt_core:19.14.0.0'
// jpa 라이브러리
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// spring security 라이브러리 : 설치만 해도 보안모드 즉시 적용됨
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.oracle.database.jdbc:ojdbc11'
annotationProcessor 'org.projectlombok:lombok'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
위 라이브 러리를 가져와야한다. 새로운 security라이브러리도 추가된 것을 확인할 수 있다. 또한 security에 필요한 jsp 기능 라이브러리도 추가했다.
# 서버 포트 : 기본포트(8080) -> 8000(변경)
server.port=8000
# jsp 파일 경로 지정 : spring 에 jsp 위치 알려주는 설정
spring.mvc.view.prefix=/WEB-INF/views/
# 자바 소스가 수정되면 서버 자동 재시작을 함
spring.devtools.restart.enabled=true
# todo: PUT , DELETE 방식도 form 태그에서 사용할 수 있게 만들어줌
spring.mvc.hiddenmethod.filter.enabled=true
# TODO: DB 라이브러리 설정 : build.gradle 오라클 db 라이브러리 없으면 에러발생
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
# TODO: DB 접속 설정 : 도커 오라클 , 계정/암호, db명(서비스이름)(xepdb1)
# todo: spring.datasource.url=jdbc:log4jdbc:oracle:thin:@ip주소:db포트번호/db이름
spring.datasource.url=jdbc:log4jdbc:oracle:thin:@localhost:1521/xepdb1
# 계정 정보
spring.datasource.username=scott
# 암호
spring.datasource.password=!Ds1234567890
# TODO: JPA 접속 설정
# JPA : sql 를 자동 생성해주는 프레임워크 :
# => JPA 기본함수만 실행하면 해당되는 sql 문을 만들어줌(자동화기능)
# => JPA 복잡한 sql 문은 개발자 직접 작성하는 기능이 있음 => Querydsl 라이브러리
# vs Mybatis : 모든 sql 을 개발자 직접 작성하는 방식, 대신에 작성시 가독성 높여서 작성가능
# JPA : sql 자동작성 기능 : 1) ddl (테이블생성, 시퀀스 생성등) 생성 : x
# 2) dml (CRUD:insert/select/update/delete) 생성 : O
# ddl 자동생성 기능 켜기 옵션 : none(끄기), create(켜기), update(없는것만 만들기)
spring.jpa.hibernate.ddl-auto=none
# todo: db 제품 연결 ( oracle 12이상 : Oracle12cDialect )
spring.jpa.database-platform=org.hibernate.dialect.Oracle12cDialect
위 속성을 추가했다. 전의 프로젝트와 같은 속성이고 새로운 속성은
이 두 파일을 가져왔다.
여기 logback-spring파일에
위 부분을 패키지 명으로 바꿔주면된다.
파일들 다 가져왔다
보안모드가 걸려있다. 이것을 설정을 통해 해제할 수 있다.
config폴더를 만들고 class로 설정을 해보자.
보안에는 권한 부여와 인증이 있다. 권한은 어느지점까지 접근이 가능하게 하는지를 정해주는 것이고, 인증은 그 권한을 가질 자격을 주는 것이다. 일단 권한을 알아보자.
클래스는 원래 환경설정을 위한 것은 아니지만 어노테이션을 통해 그렇게 만들 수 있다. @Configuration이 이역할을 한다.
@Configuration // 자바 클래스를 설정 파일로 만들어주는 어노테이션
public class WebSecurityConfig {
// 1) DB인증을 하는 클래스 (당연히 DB에 있어야 올바른 사람이므로 당연히 있어야한다.) :
// todo : 2) 패스워드 암호화 함수 :
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(); // 암호화를 시켜주는 함수들 중 하나이다.
}
// 3) 스프링 시큐리티 규칙 정의 (권한 관리):
// 관리자와 일반 유저가 볼 수 있는 페이지를 관리하는 것, 예) 부서는 일반 유저가 볼수 있음, 갤러리는 관리자만 볼수있음
// todo 2-1) 공통 jsp, img, css, js 등 : 인증 안 받는 것들은 무시하도록 설정
@Bean
public WebSecurityCustomizer webSecurityCustomizer(){ // 이걸 안하면 다 인증에 걸려서 나오지 않음
// (web) -> web.ignoring().requestMatchers("경로", "경로2" ...)
return (web) -> web.ignoring().requestMatchers(
"/resources/img/**",
"/resources/css/**",
"/resources/js/**",
"/WEB-INF/views/**"
);
}
// todo 스프링 시큐리티 : default 값이 모든 요청에 대해 보안 모드로 적용된다.
// => 로그인 화면 (기본화면: 스프링 시큐리티가 제공한다. 하지만 허접해서 개발자가 다시 만듬)
// => webSecurityConfig.java의 filterChain() 에서 인증 설정하면 로그인 없이 화면을 볼 수 있다.
// TODO: 인증 설정 부분 : 1) authorize : 권한 설정(인가)
// (권한에 따라 화면을 볼 수 있는 설정)
// 2) authentication : 인증 (로그인 : id/password)
@Bean // 자바 설정 파일의 함수 위에 붙어서 ioc(스프링컨테이너에 객체를 생성)을 해준다.
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
// todo : 권한관리
// http.authorizeHttpRequests() // 권한 설정 부분이다. (권한에 따라 화면을 볼 수 있게 해줌) 스프링 시큐리티에는 권한설정과 인증(로그인)을 동시에 할 수 있게 해준다.
// /*.requestMatchers("/**").permitAll()*/ // root뒤에 나오는 모든 접근은 다 허용한다는 뜻. 즉, 모든 사람에게 접근허용
// .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll() // 단순페이지 열기도 버전이 증가하면서 제한을 해야하도록 바꼈다.
// .requestMatchers("/auth/**").permitAll() // 여러개 계속 사용할 수 있다. 이건 auth 밑의 경로는 다 허용한다는 뜻(이상은 안됌)
// .requestMatchers("/admin/**").hasRole("ADMIN") // ADMIN 역할을 가진 사용자만 접근 권한 허용
// .requestMatchers("/basic/**").permitAll() // 모든 사용자 접근 허용
// .requestMatchers("/advanced/**").permitAll() // 모든 사용자 접근 허용
// .requestMatchers("/").permitAll() // 모든 사용자 접근 권한 허용
// .anyRequest().authenticated(); // 나머지는 로그인 사용자만 허용
http.authorizeHttpRequests(req -> req // 이 함수를 사용하면 버전에 상관없이 사용가능하다. 위의 것은 버전이 바뀌면 사용이 불가능 해질 수 있다.
.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
.requestMatchers("/auth/**").permitAll() // 이 url 은 모든 사용자 접근 허용
.requestMatchers("/admin/**").hasRole("ADMIN") // admin 메뉴는 ROLE_ADMIN 만 가능
.requestMatchers("/basic/**").permitAll() // 이 url 은 모든 사용자 접근 허용
.requestMatchers("/").permitAll() // 이 url 은 모든 사용자 접근 허용
.anyRequest()
.authenticated());
스프링 시큐리티는 아무 처리도 안 해주면 전체를 다 보안모드로 적용되어 모든 접근을 제한한다. 아래 함수들로 이것을 설정할 수 있다.
이함수들을 이용해서 권한을 줄수가있다.
// TODO: 2) 인증(로그인/로그아웃) 관리 : 쿠키/세션 방식(스프링시큐리티에서 자동 관리)
// 1) 로그인 설정
// http.formLogin() // 스프링 시큐리티에서 제공하는 로그인화면
// .loginPage("/auth/customLogin") // 기본 로그인화면 대신 개발자가 만든 화면 사용하게 해주는 함수
// .loginProcessingUrl("/auth/login") // 로그인 버튼 클릭시 실행될 함수 url (스프링시큐리티가 자동 실행하므로 controller 를 따로 만들 필요가 없다. 대신 기본정보(user 객체 정보를 제공해야한다.)
// // 이 함수를 만들면 컨트롤러를 스프링이 알아서 만들어준다.
// .defaultSuccessUrl("/"); // 로그인 성공하면 강제이동할 페이지 url
http.formLogin(req -> req
.loginPage("/auth/customLogin") // 사용자가 만든 화면 로그인 사용
.loginProcessingUrl("/auth/login") // Url로 요청이 될 시 SpringSecurity가 직접 알아서 로그인 과정을 진행 : 컨트롤러함수 필요없음, 대신 user(userdetails) 정의 필요
.defaultSuccessUrl("/") // 로그인 성공하면 이동할 페이지 url
);
// TODO: 2) 로그아웃 설정
// http.logout() // 로그아웃 처리진행 : 스프링 시큐리티가 자동 진행
// .logoutUrl("/auth/customLogout") //jsp에서 클릭시 로그아웃 이 url이 실행된다. post 방식 숨겨서 백엔드 내에서 보내주는 것(로그인/로그아웃 동일)
// .invalidateHttpSession(true) // 로그아웃이 되면 세션 id를 뺏어야한다. 이것을 해주는 코드이고 스프링 시큐리티가 해준다.
// .logoutSuccessUrl("/"); // 로그아웃 성공하면 강제이동할 페이지 url이다.
http.logout(
req -> req
.logoutUrl("/auth/customLogout") // 스프링에서 logout url 제공함 (로그아웃 페이지는 따로 필요없음)
.invalidateHttpSession(true) // session 삭제 후
.logoutSuccessUrl("/") // logout에 성공하면 /로 redirect
);
return http.build();
}
basic으로 시작하는 애들만 모든 사용자가 접근 할 수 있게 해주었고, 그외에는.anyRequest().authenticated()로 로그인 한 사람만 접근할 수 있게 해주었다.
로그인 한 사람만 볼 수 있는 화면이 있고, 안 한 사람이 볼 수 있는 화면이 있다. 이것을 구분하는 것이 쿠키/세션 방식이다. 스프링은 기본적으로 이 시스템으로 돌아간다.
사용자가 spring으로 id/pw를 쿠키와 같이 보낸다. 웹 브라우저(chrome)에서 자동으로 쿠키를 전송한다. 예전에는 쿠키에 id/pw를 넣어서 보냈다. 그런데 해커가 쿠키만 탈취하면 id/pw가 털리는 것이다.
그럼 db에 id를 확인하고 id가 존재하면 세션id만 쿠키에 담아서 사용자에게 보내준다. 그럼 다음부터 다른 화면을 볼때 항상 쿠키를 지니는데 이 쿠키에 세션 id가 있으니 spring에서는 쿠키에 있는 세션 id를 확인하고 db랑 비교하여 db에 세션 id가 있으면 이걸로 로그인 한 유저를 구분한다.
로그인을 하면 인증정보가 있는 쿠키를 가진다. 인증정보가 없는 쿠키를 가진 사람은 걸러낸다.
쿠키는 해킹에 굉장히 취약하다 그래서 거기에 id와 pw를 넣으면 굉장히 위험해서 그 대신 세션 id를 넣어놓는다. 이건 spring 서버에서 자체적으로 가진다. 세션 아이디로 사용자를 구분하여 제한된 경로접근을 허용한다.
만약 쿠키에 세션 id가 없으면 불법 사용자이므로 403인증에러가 발생한다.
※ 전통적 세션 방법으로 jsp에서 사용한다. vue나 react에서는 jwt웹토큰 방식을 사용한다.
이 부분은 권한을 가질 자격을 주는 과정이다. 로그인을 통해 권한의 차이를 줄 수 있다.
그럼 로그인 방법을알아보자.
// TODO: 2) 인증(로그인/로그아웃) 관리 : 쿠키/세션 방식(스프링시큐리티에서 자동 관리)
// 1) 로그인 설정
http.formLogin() // 스프링 시큐리티에서 제공하는 로그인화면
.loginPage("/auth/customLogin") // 기본 로그인화면 대신 개발자가 만든 화면 사용하게 해주는 함수
.loginProcessingUrl("/auth/login") // 로그인 버튼 클릭시 실행될 함수 url (스프링시큐리티가 자동 실행하므로 controller 를 따로 만들 필요가 없다. 대신 기본정보(user 객체 정보를 제공해야한다.)
.defaultSuccessUrl("/"); // 로그인 성공하면 강제이동할 페이지 url
// TODO: 2) 로그아웃 설정
http.logout() // 로그아웃 처리진행 : 스프링 시큐리티가 자동 진행
.logoutUrl("/auth/customLogout") //jsp에서 클릭시 로그아웃 이 url이 실행된다. post 방식 숨겨서 백엔드 내에서 보내주는 것(로그인/로그아웃 동일)
.invalidateHttpSession(true) // 로그아웃이 되면 세션 id를 뺏어야한다. 이것을 해주는 코드이고 스프링 시큐리티가 해준다.
.logoutSuccessUrl("/"); // 로그아웃 성공하면 강제이동할 페이지 url이다.
return http.build();
로그인은 스프링 시큐리티에서 자동으로 실행을 시켜준다.
로그아웃또한 스프링 시큐리티에서 자동으로 실행을 시켜주므로 controller에 함수를 만들어 줄 필요는 없다.
@Entity
@Table(name = "TB_MEMBER")
@DynamicUpdate
@DynamicInsert
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
@Where(clause = "DELETE_YN = 'N'")
@SQLDelete(sql = "UPDATE TB_MEMBER " +
"SET DELETE_YN = 'Y', DELETE_TIME=TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') " +
"WHERE EMAIL = ?")
public class Member extends BaseTimeEntity2 {
@Id
private String email; // 로그인 id로 스프링 시큐리티 속성을 username 을 사용한다. username = id = email 이다.
private String password; // 암호
private String name; // 유저명
private String codeName; // 권한명 : ROLE_USER(사용자), ROLE_ADMIN(관리자)
}
로그인 id로 스프링 시큐리티 속성을 username 을 사용한다. 즉, username = id = email 이다. 나중에 헷갈리지 말자.
지난 프로젝트에서 만든 entity와 같다.
