20220926_mon
실습내용
- 게시판 대표적 기능
- 로그인,로그아웃,게시글목록,게시글 상세조회,글쓰기,글수정,글삭제
- 인증을 받지않더라도 접근가능한 페이지 기능 구현
: 로그인,게시글 목록, 회원가입
- 인증을 받아야 접근가능한 페이지를 (기준)
: 로그아웃,게시글 상세조회,글쓰기
- 인증을 받는다고 하더라도 인가가 필요한 페이지(권한여부)
: 로그인은 기본이고 관리자만 가능한지, 실제 로그인한 사람만 가능한지
: 글수정,글삭제 -> 글을 쓴 "작성자"만이 글을 수정 및 삭제가 가능하기 때문에
- 시큐리티 관련 디펜던시를 porm.xml에 추가
: html에서 태그를 사용하기위 위해서는 별도로 dependency 태그를 추가해야한다.
- (1) ctrl+스페이스바 클릭 후 > Add starter 클릭 > spring security 기능 추가 > 두개의 초록색 선 두개 클릭 후 finish 완료
- (2) < html에서 시큐리티 정보를 가져올수 있는 태그 사용 (2개) > 도 추가해줘야한다.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.3</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>Kh.study</groupId> <artifactId>Shop</artifactId> <version>0.0.1-SNAPSHOT</version> <name>Shop</name> <description>Demo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- log4jdbc : 쿼리실행은 콘솔에 출력하기 위해 추가 --> <dependency> <groupId>org.bgee.log4jdbc-log4j2</groupId> <artifactId>log4jdbc-log4j2-jdbc4.1</artifactId> <version>1.16</version> </dependency> <!-- 타임리프의 fragment 기능 사용을 위한 추가 --> <dependency> <groupId>nz.net.ultraq.thymeleaf</groupId> <artifactId>thymeleaf-layout-dialect</artifactId> <version>3.1.0</version> </dependency> <!-- validation처리 추가 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <!-- html에서 시큐리티 정보를 가져올수 있는 태그 사용 (2개) --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>5.0.7.RELEASE</version> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
- 시큐리티 태그 추가한 결과
: list 기본경로페이지 이동할 경우, 로그인 페이지가 자동으로 뜬다.
- 로그인 방법
: 프로젝트 run후, 콘솔창에 비밀번호값이 보인다.
Using generated security password: 0a017fd7-15a2-4998-a5be-46b0cece7b15
: ID값은 항상 user 이다.
- 시큐리티 설정 파일 생성하기(클래스파일)
kh.study.board.config.SecurityConfig
package kh.study.board.config;
// 어노테이션 정리: 객체를 만들어주는 어노테이션
// Controller,Service,Configuration,Bean
// @Bean : "메소드의 리턴 타입"에 대한 객체를 생성하는데,
// 클래스 위에서 생성하는 다른 어노테이션과 달리 메소드 위에서 정의한다
@Configuration //컨피규레이션 객체 생성 어노테이션
@EnableWebSecurity //내부적으로 이 객체를 가져가 파일을 실행해주는 어노테이션
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity security) throws Exception{
security.csrf().disable()
.authorizeRequests()
.antMatchers("/member/login", "/board/list","/member/join").permitAll()
// .antMatchers("/board/logout", "/board/detail","/board/reg").hasAnyRole("MEMBER","WRITER")
// .antMatchers("/board/update","/board/delete").hasRole("WRITER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/member/login")
.defaultSuccessUrl("/board/list")
.failureUrl("/member/loginFail")
.and()
.exceptionHandling()
.accessDeniedPage("/member/accessDenied")
;
return security.build();
}
}
- 로그인,로그아웃 기능 구현
UserDetailsService
인터페이스의 메소드를 구현하기
: 이 메소드가 로그인을 시켜주는 기능을 담당한다.
: kh.study.board.member.service.userDetailsServiceImpl
package kh.study.board.member.service;
//직접호출하지 않아서 만들지않아도 상관없지만 편의상 이해를 돕기위해 생성
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService{//자동완성해야함!
// @Autowired
// private SqlSessionTemplate sqlSession;
@Resource(name = "memberService")
private MemberService memberService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
MemberVO loginInfo = memberService.login(username);
if (loginInfo == null) {
throw new UsernameNotFoundException(" 오류발생 :: 조회되는 아이디가 없습니다.");
}
UserDetails userDetails = User
.withUsername(loginInfo.getMemberId())
.password("{noop}"+loginInfo.getMemberPw())
.roles(loginInfo.getIsAdmin())// -- Y/N
.build();
return userDetails;
}
}
- 로그인
- 주의사항!! : 조회시 관리자여부같이조회해야하면서, 회원상태가 활성화되어있어야하는 조건이 필요하다!!!
- 시큐리티 추가 수정사항
- 로그인쿼리 실행시 비번은 필요없이 아이디값만 필요하다!
- 왜, 비밀번호를 조회하는건가?
- 이유, 조회하여 가져온 데이터베이스 저장된 비밀번호를 가져와서 내가 입력한 비밀번호와 비교하여 일치해야 로그인 진행된다.
- 단, 아이디,비번,권한(IS_ADMIN ) 만 조회해야한다!
- memberId라는 매개변수가 아닌 userName값으로 사용하는 이유는?
- 넘어오는 데이터가 하나일 때는! 데이터 변수명은 달라도 상관이 없다!!!
- 어차피 넘어오는 데이터 하나! 빈 공간도 하나! 이면 알아서 채워주기 때문에<select id="login" resultMap="selectMember"> SELECT MEMBER_ID , MEMBER_PW ,IS_ADMIN FROM BOARD_MEMBER WHERE MEMBER_ID = #{memberId} AND MEMBER_STATUS = 'ACTIVE' </select>
시큐리티 로그인 쿼리 실행시
- 시큐리티로 userDetailsServiceImpl 사용시,
- 로그인 메소드 자동 호출된다.
- 언제?어떻게?
- post방식으로 /login 이라는 요청이 발생하면 로그인 메소드를 자동으로 실행한다.
- 그래서 컨트롤러에 로그인 메소드 생략한다.
- 시큐리티 적용 전 로그인 form태그
<form th:action="@{/member/login}" class="row g-3" method="post" th:object = "${memberVO}" >
- 시큐리티 적용 후 로그인 form태그
<form th:action="@{/login}" class="row g-3" method="post" th:object="${memberVO}">
- UserDetailsServiceImpl 사용을 하면, 자동으로 로그인 메소드 호출한다.
- 단, 컨트롤러에 포스트매핑 로그인을 생략하게된다.
- 대신 원래 memebrId 값(name)으로로 넘어가는 데이터를 받을 컨트롤러가 없으니
- 아이디의 경우 name값은 항상 username 으로 넘겨줘야한다! -> name="username"
:input태그 입력된 비번값과 데이터베이스에 저장된 비밀번호값 비교해야하니까- 비밀번호의 경우, name값을 password 로 반드시 속성값 입력해줘야한다 -> name="password"
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}">
<div layout:fragment="content">
<div class="row justify-content-center">
<div class="col-6">
<form th:action="@{/login}" class="row g-3" method="post" th:object="${memberVO}">
<div class="col-12">
<label for="memberId" class="form-label" >ID</label>
</div>
<div class="col-12">
<label for="memberPw" class="form-label" >PW</label>
<input type="password" class="form-control" name="password" >
</div>
<div class="col-12 d-grid gap-2">
<button type="submit" class="btn btn-outline-success">로그인</button>
<button type="button" class="btn btn-outline-warning" onclick="location.href='/member/join';" >회원가입</button>
</div>
</form>
</div>
</div>
</div>
</html>
실습내용
- 로그아웃
: 로그아웃은 post방식으로 /logout 이라는 요청이 발생하면 로그인 메소드를 자동으로 실행한다.
- 로그인,로그아웃에 따른 상단 내용의 변화를 sercurity를 적용해보기
: 로그인 성공후, ~~ 님 반갑습니다 로 바꾸기
- 게시글 목록 페이지에서 "글쓰기 버튼"은 로그인한 사람만 보일수있도록 security사용하기
- 수정,삭제 버튼은 로그인한 사람이 관리자,로그인한 사람이 해당 글의 작성자 일때만 보이도록 기능 구현하기.
css,js 파일이 있을 시, 모든 파일은 인증을 받아야하기때문에 /js , /css 와 같은 파일 요청이 있으면 무시하도록 설정해야 문제없이 페이지가 이동한다.
@Bean public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring().antMatchers("/js/**", "/css/**"); }
package kh.study.board.config;
@Configuration //컨피규레이션 객체 생성 어노테이션
@EnableWebSecurity //내부적으로 이 객체를 가져가 파일을 실행해주는 어노테이션
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity security) throws Exception{
security.csrf().disable()
.authorizeRequests()
.antMatchers("/member/login", "/board/list","/member/join").permitAll()
// .antMatchers("/board/logout", "/board/detail","/board/reg").hasAnyRole("MEMBER","WRITER")
// .antMatchers("/board/update","/board/delete").hasRole("WRITER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/member/login")
.defaultSuccessUrl("/board/list")
.failureUrl("/member/loginFail")
.loginProcessingUrl("/member/login")// 실제 로그인을 진행할 요청 정보
.and()
.logout()
.invalidateHttpSession(true)
.logoutSuccessUrl("/board/list")
.and()
.exceptionHandling()
.accessDeniedPage("/member/accessDenied")
;
return security.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/js/**", "/css/**");
}
}
//직접호출하지 않아서 만들지않아도 상관없지만 편의상 이해를 돕기위해 생성
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService{//자동완성해야함!
// @Autowired
// private SqlSessionTemplate sqlSession;
@Resource(name = "memberService")
private MemberService memberService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
MemberVO loginInfo = memberService.login(username);
if (loginInfo == null) {
throw new UsernameNotFoundException(" 오류발생 :: 조회되는 아이디가 없습니다.");
}
UserDetails userDetails = User
.withUsername(loginInfo.getMemberId())
.password(loginInfo.getMemberPw())//-- 비밀번호 암호화처리할땐 noop제외하기
.roles(loginInfo.getIsAdmin())// -- Y/N
.build();
return userDetails;
}
}
@Service("memberService")//컨트롤러에 보내 사용할 객체를 어노테이션을 이용해 보내준다.
public class MemberServiceImpl implements MemberService{
@Autowired//쿼리실행 어노테이션으로 객체생성
private SqlSessionTemplate sqlSession;
@Autowired//암호화 어노테이션으로 객체생성
private PasswordEncoder passwordEncoder;
//회원가입
@Override
public void join(MemberVO memberVO) {
// -------------------- 비밀번호 암호화 하기 --------------------------//
// 회원가입 후, 디비를 조회해보면 암호화되어 비밀번호 조회 가능함
String pw = passwordEncoder.encode(memberVO.getMemberPw());
memberVO.setMemberPw(pw);
sqlSession.insert("memberMapper.join", memberVO);
}
//로그인
@Override
public MemberVO login(String memberId) {
return sqlSession.selectOne("memberMapper.login", memberId);
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"><!-- sec 문법 사용 -->
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div th:fragment="topFragment">
<!-- <div align="right">
<div>
<span sec:authorize="isAuthenticated()" th:text="${#authentication.name}">
님 반갑습니다✨
</span>
<form action="/logout" method="post">
<button sec:authorize="isAuthenticated()" type="submit" >로그아웃</button>
</form>
</div>
<div >
<span >
<button sec:authorize="isAnonymous()" type="button"
onclick="location.href='/member/login'">로그인</button>
<button sec:authorize="isAnonymous()" type="button"
onclick="location.href='/member/join'">회원가입</button>
</span>
</div>
</div> -->
<!-- 시큐리티 적용 로그인/로그아웃 표기 -->
<!-- 아래문법사용위해서 sec 태그 위에 추가해야한다. -->
<!-- sec:authorize="isAnonymous()" : if문과 같다. 인증이 안되어있다면~-->
<!-- sec:authorize="isAuthenticated()" : 인증이 되었다면~~-->
<!-- sec:authentication="principal.authorities" : 인증된사용자의 정보(아이디,권한 중 '권한') 불러오기-->
<!-- sec:authentication="name" : 인증된사용자의 정보(아이디,권한 중 '아이디') 불러오기-->
<!-- 타임리프방법 으로 인증된 사용자의 데이터 불러오기-->
<!-- <span th:text="${#authentication.name}"></span>-->
<!-- 로그아웃은 post방식으로 /logout 이라는 요청이 발생하면 로그인 메소드를 자동으로 실행한다.-->
<!-- 때문에 post방식이 아닌 get방식(a태그)사용시 오류발생한다. -->
<div class="row" >
<div class="col text-end" style="color:#224B0C;">
<!-- 로그인 인증이 되지 않을 때 -->
<div sec:authorize="isAnonymous()">
<a th:href="@{/member/login}">login</a>
<a th:href="@{/member/join}">join</a>
</div>
<!-- 로그인 인증 되었을 때 -->
<div sec:authorize="isAuthenticated()" >
<span sec:authentication="name"></span>님 반갑습니다😊
<form th:action="@{/logout}" method="post">
<button sec:authorize="isAuthenticated()" type="submit" >로그아웃</button>
</form>
</div >
</div>
</div>
<!-- 로그인 클릭시 로그인html파일 이동방법 -->
<!-- 1번 <a href="/board/login">login</a> -->
<!-- 2번 <a th:href="@{/board/login}"> login</a><!-- 타임리프 경로이동시!!! 골뱅이+중괄호 -->
<!-- <div class="row">
<div class="col text-end" style="color:#224B0C;">
주의할 점 : sessionScope 는 jsp // session 은 타임리프 th
<th:block th:if="${session.loginInfo == null}">
<a th:href="@{/member/login}">login</a>a태그 -> get방식!!!
<a th:href="@{/member/join}">join</a>a태그 -> get방식!!!
<span th:onclick="location.href='/board/join';"></span>
</th:block>
<th:block th:unless="${session.loginInfo == null}">
<p th:text="${#authentication.name}"></p>님 반갑습니다😊
<a th:href="@{/member/logout}">LOGOUT</a>a태그 -> get방식!!!
</th:block>
</div>
</div> -->
<br>
<br>
<div class="row">
<div class="col text-center" style="background-color: #EEF2E6;">
<span style="font-weight: bold; font-size: 50px; color: #3D8361;"><a href="/board/list">B O A R D</a></span>
</div>
</div>
</div>
</body>
</html>
컨트롤러에서 시큐리티를 사용하는 대신 기존에 있던 session 세션 사용을 제거 하고, 자동호출되는 @postMapping의 로그인과 로그아웃 컨트롤러는 생략한다.
package kh.study.board.member.controller;
@Controller
@RequestMapping("/member")
public class MemberController {
@Resource(name = "memberService")//서비스임플에서 어노테이션으로 만든 객체가져온다.
private MemberService memberService;
// ---------------------- 커맨드객체 정의 -------------------------------------------//
// 커맨드객체는 html로 데이터를 전달하는 코드를 작성하지 않아도 자동으로 넘어간다.
// 이때 데이터가 넘어가는 이름은 클래스명에서 앞글자만 소문자로 변경된 이름으로 넘어간다.
//-----------------------------------------------------------------------------------//
// 회원가입_a태그로 top파일에서 넘어올 때 @GetMapping매핑
@GetMapping("/join") // a태그는 무조건 get 방식!!!(암기)
public String join(MemberVO memberVO) {
// 자동으로 커맨드 객체는 넘어간다.(memberVO) //커맨드객체에 아이디값 임의로 넣어주면, html로 넘어갈때 데이터 넘어감
System.out.println(memberVO);
return "content/member/join";
}
//-------------validation처리 :유효성 검사-----------------------------------------//
// @Valid : post로 전달된 데이터가 검증 규칙을 따르는지 판단하는 어노테이션
// 해당 어노테이션 다음에는 반드시 검증할 객체가 valid 다음 바로 와야한다
// BindingResult :검증 대상 객체와 검증 결과의 대한 정보를 담고 있는 객체.
// 검증객체 바로 다음에 선언되어야 한다.
// 실제로 input태그에 입력한 데이터가 memberVO에 들어오는 걸 바인딩이라고하는데, 그 결과를 말함.
// model도 매개변수로 넣어준이유?
// : model 같이 매개변수 사용하면 데이터를 굳이 담지않아도(눈에보이지않아도) 바인딩결과의 오류여부정보와 검증한 객체memberVO 값을
// 자동으로 리턴값 join.html에 보내준다.
//----------------------------------------------------------------------------------//
// 회원가입_form태그로 top파일에서 넘어올 때 @PostMapping매핑
@PostMapping("/join")
public String joinProcess(@Valid MemberVO memberVO, BindingResult bindingResult, Model model) {
//System.out.println("!!!!!!" + memberVO.getMemberTell()); // "010,1111,2222"
//System.out.println("!!!!!!" + memberVO.getMemberPw()); // "010,1111,2222"
//String[] tells = memberVO.getMemberTells(); for(String e : tells) {//리스트는
//반복문으로 데이터꺼내기 System.out.println(e); }
// 1) validation 체크 (데이터 유효성 검증)
if (bindingResult.hasErrors()) {// 바인딩하는데 오류가 생겼니?
System.out.println("error발생!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
return "content/member/join";// 에러발생하면 join페이지로 다시 이동
}
// 2)회원가입 쿼리 실행
memberService.join(memberVO);
// 3)페이지이동
return "redirect:/board/list";
}
// 로그인_a태그로 top파일에서 넘어올 때
//주의사항!!! login.html에서 커맨드객체를 object로 사용하려면, 반드시 매개변수로 memberVO가 있어야한다!
@GetMapping("/login") // a태그는 무조건 get 방식!!!(암기)
public String login(MemberVO memberVO) {
System.out.println("로그인 a태그로 클릭해서 넘어왔다!!!!!!!!!!!!!!!!!!");
// return "redirect:/member/login"; //redirection 너무 많아서 오류
// 컨트롤러로 페이지이동해야 로그인 성공 후, 목록페이지로 넘어간다
return "content/member/login"; //(x)
}
// -------------------------- 시큐리티 로그인 쿼리 실행시--------------------------------//
// 시큐리티로 userDetailsServiceImpl 사용시,
// 로그인 메소드 자동 호출된다.
// 언제?어떻게?
// post방식으로 /login 이라는 요청이 발생하면 로그인 메소드를 자동으로 실행한다.
// 그래서 컨트롤러에 로그인 메소드 생략한다.
// 로그인_form태그로 top파일에서 넘어올 때
//!!로그인은 유효성검사 필요없다
// @PostMapping("/login")//form태그와 ajax일때 -> 무조건 post!! 나머지 get!
// public String loginProcess(HttpSession session, MemberVO memberVO) {
//
// MemberVO loginInfo = memberService.login(memberVO);
//
//
// if (loginInfo != null) {
// //로그인 세션사용하려면 위의 쿼리문으로 만든 로그인인포만든것을 세션에 값을 넣어주는대신,
// //위에 매개변수로 만들어주면 된다.!!
// session.setAttribute("loginInfo", loginInfo);
// //로그인성공하면 바로 게시판목록페이지이동
// }
// else {
// System.out.println("로그인실패!!!!!!!!!!!!!!!!!!!!!!!!!!");
// //로그인 실패시에도 다시 로그인페이지로 이동
// // 회원가입 실패시, 입력 데이터를 유지???
// // 화면에 입력한 데이터를 그대로 가져가려면! 무조건 페이지로 이동해야한다.
// // 다시말해서, 아래 redirect로는 데이터를 가져갈 수 있는 방법을 아직 안배워서 안된다.
// // return "redirect:/member/login"; 데이터가져갈수없다!(아직..방법안배움)
// return "content/member/login";//데이터가져가기때문에 input태그에 입력한 값들이 남아있는다.
// //대신 html에 데이터넘기려면 model객체 매개변수필요할 수 있지만!
// //커맨드 객체인 memeberVO가 이미 있기때문에 데이터를 보낼 때 굳이 model 객체가 매개변수로 필요하지않다.
//
// }
// return "redirect:/board/list";
// }
// ------------------------------------------------------------------------------------------------------------------//
// 로그인 실패시
@GetMapping("/loginFail")
public String loginFail( ) {
return "content/member/login";
}
// 접근거부시
@GetMapping("/accessDenied")
public String accessDenied( ) {
return "content/member/accessDenied";
}
//---------- 시큐리티 사용시 자동호출되기때문에 로그인과 마찬가지로 생략한다 ----------//
// // 로그아웃
// @GetMapping("/logout")
// public String logout(MemberVO memberVO ) {
// memberVO.setMemberId(null);
//
// return "redirect:/board/list";
// }
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div layout:fragment="content">
<br>
<br>
<div>
<table class="table">
<thead class="table-light">
<tr>
<td>글번호</td>
<td>글제목</td>
<td>작성일</td>
<td>작성자</td>
</tr>
</thead>
<tbody>
<th:block th:if="${#lists.size('boardList') == 0}">
<tr>
<td colspan="4"> 게시글이 없습니다.</td>
</tr>
</th:block>
<!-- boardListr가져올때 홀따옴표 감싸서 가져오기 주의!!!! -->
<th:block th:unless="${#lists.size('boardList') == 0}" >
<tr th:each="board : ${boardList}">
<td th:text="${board.boardNum}"></td>
<td>
<a th:href="@{/board/detail(boardNum=${board.boardNum})}" th:text="${board.title}"></a>
</td>
<td th:text="${board.createDate}"></td>
<td th:text="${board.memberId}"></td>
</tr>
</th:block>
</tbody>
</table>
</div>
<!-- 시큐리티 적용 후_Teacher -->
<div align="center">
<button sec:authorize="isAuthenticated()" type="button" class="btn btn-outline-success" th:onclick="@{location.href='/board/reg';}">글쓰기</button>
</div>
<!-- 시큐리티 적용 후_Myself -->
<!-- <div align="center">
<div sec:authorize="isAuthenticated()" >
<button>
글쓰기
</button>
</div>
</div> -->
<!-- 시큐리티 적용 전 -->
<!-- <div align="center">
<th:block th:if="${session.loginInfo != null}">
<button sec:authorize="isAuthenticated()" type="button" class=" btn btn-outline-success" th:onclick="@{location.href='/board/reg';}">글쓰기</button>
</th:block>
</div> -->
</div>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<div layout:fragment="content">
<div class="row justify-content-center">
<form th:action="@{/board/reg}" method="post" th:object="${boardVO}">
<div class="mb-3">
<!-- 시큐리티 사용하기위해서는 session 을 삭제해야 오류가 발생하지 않는다!-->
<label for="exampleFormControlInput1" class="form-label">작성자 :</label>
<span sec:authorize="isAuthenticated()" sec:authentication="name"></span>
</div>
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label" >글 제목 :</label>
<input name="title" type="text" class="form-control" th:field="*{title}" placeholder="제목을 입력하세요">
<div class="hasError" th:if="${#fields.hasErrors('title')}" th:errors="*{title}" ></div>
</div>
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">글 내용 :</label>
<textarea name="content" class="form-control" th:field="*{content}" rows="3" placeholder="내용을 입력하세요"></textarea>
<div class="hasError" th:if="${#fields.hasErrors('content')}" th:errors="*{content}" ></div>
</div>
<div align="center">
<button class="btn btn-light" type="submit" th:onclick="@{location.href='/board/reg';}">글등록</button>
</div>
</form>
</div>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<div layout:fragment="content">
<div class="mb-3" >
<label for="exampleFormControlspan1" class="form-label">글 번호 :</label>
<th:block th:text="${board.boardNum}"></th:block>
</div>
<div class="mb-3">
<label for="exampleFormControlspan1" class="form-label"> 작성자 :</label>
<th:block th:text="${board.memberId}"></th:block>
</div>
<div class="mb-3">
<label for="exampleFormControlspan1" class="form-label"> 작성일 :</label>
<th:block th:text="${board.createDate}"></th:block>
</div>
<div class="mb-3">
<label for="exampleFormControlspan1" class="form-label">글제목 :</label>
<th:block th:text="${board.title}"></th:block>
</div>
<div class="mb-3">
<label for="exampleFormControlTextarea1" class="form-label">글내용 :</label>
<div th:text="${board.content}"></div>
</div>
<!-- 세션 정보는 삭제해야 시큐리티 적용했을 때 오류발생하지 않는다!!! -->
<!-- 게시글 작성자 데이터 불러오기 -->
<!-- <div th:text="${board.memberId}"></div> -->
<!-- 로그인 인증자 아이디 정보 불러오기 -->
<!-- <div sec:authentication="name"></div> -->
<!-- 위 두개가 일치한다면, 글수정및삭제 버튼 보이기 -->
<div align="center">
<div th:if="${board.memberId == #authentication.name}" >
<div sec:authorize="hasRole('ROLE_Y')">
<button type="button" class="btn btn-light" th:onclick="|location.href='@{/board/update(boardNum=${board.boardNum})}'|">수정</button>
<button type="button" class="btn btn-danger" th:onclick="|location.href='@{/board/delete(boardNum=${board.boardNum})}'|">삭제</button>
</div>
</div>
<button type="button" class="btn btn-dark" th:onclick="@{location.href='/board/list';}">뒤로가기</button>
</div>
</div>
</html>