202220927_tue
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()
.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();
}
//------css,js 요청 인증 무시 -------------------------------------------------//
// css,js 파일 모두 열리면 인증을 받아야하기때문에
// /js,/css와 같은 파일 요청이 있으면 무시하도록 설정
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/js/**", "/css/**");
}
}
<!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 th:if="${board.memberId == #authentication.name}" >
<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 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> -->
<!-- 관리자 권한이 아닌사람이 첫번째 매개변수에 포함이 되는가? -->
<!-- 결과값은 t/f 참과 거짓으로 나온다. -->
<!-- <div th:text="${#authentication.authorities}"></div>'권한' 데이터 // [ROLE_N] -->
<!-- <div th:text="${#strings.contains(#authentication.authorities,'ROLE_N')}"></div>true -->
<!-- 위의 두 조건을 or로 충족시키는 if문을 요약하여 만들어 사용해보자. -->
<!-- 작성자 아이디값 과 로그인 인증자의 아이디값이 같거나 관리자라면 수정,삭제 버튼이 뜨도록 만든다. -->
<div align="center">
<span th:if="${board.memberId == #authentication.name or #strings.contains(#authentication.authorities,'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>
</span>
<span>
<button type="button" class="btn btn-dark" th:onclick="@{location.href='/board/list';}">뒤로가기</button>
</span>
</div>
</div>
</html>
- 글 등록(시큐리티)
: 시큐리티 사용시, 세션Session
객체 사용불가능!!!
: 대신,Authentication
객체를 사용한다!!- [시큐리티를 사용해서 로그인한 정보를 가져오는 방법]
- ✔✔ 시큐리티를 이용하면서 주의할점
- 로그인한 사람의 정보를 MemberVO가 아니라
- UserDetailsService에서 사용하는 user정보에 로그인 정보가 담겨져있다.
- 항상 담겨있는 정보는 '아이디'와 '권한' 만 있다!!!(이를 이용해서 데이터 추출)
- 형변환은 필수이다!
- BoardController
@PostMapping("/reg")
public String reg(@Valid BoardVO boardVO, BindingResult bindingResult, Model model
,Authentication authentication) {
User user = (User) authentication.getPrincipal();
boardVO.setMemberId(user.getUsername());
boardService.insertBoard(boardVO);
// 주의! 순서중요하다. 유효성체크 먼저 한 후, 로그인정보값 boardVO에 넣어주기 !!
if (bindingResult.hasErrors()) {
System.out.println("에러발생!!!!");
//주의!!! 컨트롤러(redirect)가 아닌 html페이지로 가야 데이터가 남아있는다
return"content/board/reg_board";
}
// 작성자(memberId)는 PK이기때문에 글등록양식페이지에서 화면에 보이도록 띄워줘야함
// <시큐리티 사용시> 세션session 사용불가능하기 때문에 생략
// MemberVO loginInfo = (MemberVO)session.getAttribute("loginInfo");
//boardVO.setMemberId(loginInfo.getMemberId());
//boardService.insertBoard(boardVO);
return "content/board/reg_result";a
}
<!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>
<!-- 시큐리티 적용 후_내가 구현한 방법 -->
<!-- <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>
-- [SHOP_프로젝트 테이블]
DROP TABLE SHOP_MEMBER;
--회원정보 테이블
CREATE TABLE SHOP_MEMBER (
MEMBER_ID VARCHAR2(100) CONSTRAINT SHOP_MEMBER_PK PRIMARY KEY
-- CONSTRAINT : 오라클에서 내부적으로 저장된 제약조건 이름
, MEMBER_PW VARCHAR2(100) NOT NULL
, MEMBER_NAME VARCHAR2(100) NOT NULL
, MEMBER_ADDR VARCHAR2(100)
, ADDR_DETAIL VARCHAR2(100)
, MEMBER_EMAIL VARCHAR2(100)
, MEMBER_ROLE VARCHAR2(20) -- 시큐리티적용_컬럼명 수정/기본값없이 권한등급 : MEMBER(일반회원), ADMIN(관리자)
, MEMBER_STATUS VARCHAR2(100) -- ACTIVE / DELETED
);
--SHOP_MEMBER 테이블 코멘트 추가하는 방법
COMMENT ON TABLE SHOP_MEMBER IS '회원정보테이블';
COMMENT ON COLUMN SHOP_MEMBER.MEMBER_ID IS '회원아이디';
COMMENT ON COLUMN SHOP_MEMBER.MEMBER_PW IS '회원비밀번호';
COMMENT ON COLUMN SHOP_MEMBER.MEMBER_NAME IS '회원이름';
COMMENT ON COLUMN SHOP_MEMBER.MEMBER_ADDR IS '회원주소';
COMMENT ON COLUMN SHOP_MEMBER.ADDR_DETAIL IS '상세주소';
COMMENT ON COLUMN SHOP_MEMBER.MEMBER_EMAIL IS '회원이메일';
COMMENT ON COLUMN SHOP_MEMBER.MEMBER_ROLE IS '권한';
COMMENT ON COLUMN SHOP_MEMBER.MEMBER_STATUS IS '회원상태';
: 수정된 테이블 컬러명(IS_ADMIN -> MEMBER_ROLE) 변경하기.
: 회원가입,로그인 쿼리에 아이디,권한,비밀번호값 세가지 반드시 조회한다. 단, 조건절은 비밀번호 없이 아이디값만 넣는다!!
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--해당 파일에 모든 쿼리문을 작성 -->
<mapper namespace="memberMapper">
<!-- 패키지명 클래스명 -->
<resultMap type="Kh.study.shop.member.vo.MemberVO" id="selectMember">
<id column="MEMBER_ID" property="memberId"/>
<result column="MEMBER_PW" property="memberPw"/>
<result column="MEMBER_NAME" property="memberName"/>
<result column="MEMBER_ADDR" property="memberAddr"/>
<result column="ADDR_DETAIL" property="addrDetail"/>
<result column="MEMBER_EMAIL" property="memberEmail"/>
<result column="MEMBER_ROLE" property="memberRole"/>
<result column="MEMBER_STATUS" property="memberStatus"/>
</resultMap>
<!-- 회원가입 -->
<!-- 기본값 있는 관리자여부 제외 모두 입력해야함
회원상태는 회원가입하면 기본적으로 활성화상태이기때문에 ACTIVE 입력-->
<!-- 시큐리티 적용시, 디비 변경하면서 권한도 회원가입시 insert해야한다. -->
<!-- Enum 파일을 사용하면서, 회원상태를 단순히 문자열이 아닌 변수로 받도록한다. -->
<insert id="join">
INSERT INTO SHOP_MEMBER
(MEMBER_ID
,MEMBER_PW
,MEMBER_NAME
,MEMBER_ADDR
,ADDR_DETAIL
,MEMBER_EMAIL
,MEMBER_STATUS
,MEMBER_ROLE)
VALUES (#{memberId}
,#{memberPw}
,#{memberName}
,#{memberAddr}
,#{addrDetail}
,#{memberEmail}
,#{memberStatus}
,#{memberRole})
</insert>
<!-- 로그인 -->
<!-- 시큐리티 적용시, pw값은 조건절 포함시키지않는다 아이디값만 !!! -->
<!-- 그리고 항상 비밀번호값도 조회를 해야 비교를하여 로그인처리가 가능하다! -->
<select id="login" resultMap="selectMember">
SELECT MEMBER_ID , MEMBER_ROLE , MEMBER_PW
FROM SHOP_MEMBER
WHERE MEMBER_ID = #{memberId}
</select>
</mapper>
- src/main/java
- Kh.study.shop
- admin
- config
- item
- member
- Security 사용을 위해서 UserDetailsServiceImpl 을 생성하여 사용하는 것이 point이다!!
<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>
<?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>Board</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Board</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-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.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>
<!-- jsp 라이브러리 , jsp에서 톰캣을 사용할 수 있는 라이브러리-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- jstl 라이브러리 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</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>
프로젝트 재실행 후, 기본경로 이동시 로그인창 띄워진다.
이때 아이디값은 user , 비밀번호는 콘촐창에 있다.
a8c410d6-1bb0-4643-b28b-3dfa3272296a
Config 패키지 생성
package Kh.study.shop.config
실습내용
1. 로그인시, top의 내용 변경
ex) 000님 반갑습니다. logout ...
2. 로그아웃 기능 구현
3. 관리자 권한의 회원이 로그인하면, 관리자 페이지로 이동해야한다.
: 회원가입후, 데이터베이스 내에서 update 쿼리문으로 변경해줘야한다.
-> 처음 열리는 관리자 페이지는 상품등록페이지로 이동하면 된다.
- 상품등록.html:content/admin/reg_item.html
단, 중요한 거은 관리자 권한의 계정으로 로그인하면 화면의 레이아웃이 상단,사읻,컨텐츠영역의 총 3단으로 변경되어야한다. (기존 레이아웃에서 admin_layout.html에서 side.html을 구성하면된다.)- side : 상품등록,상품관리, 회원권한설정,메뉴관리
✔👀 point ✔👀
:시큐리티 사용을 위해서는 해당 파일을 반드시 생성하여 사용해야한다. 컨트롤러에서 리턴값을 user로 만들려면 이 서비스임플을 필요로 하기 때문이다.
: Enum 파일 생성후 변수명 선언하기만 하면, 다른 파일에서 Enum객체 불러와 쉽게 사용가능하다.
package Kh.study.shop.admin;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/admin")
public class AdminController {
@GetMapping("/regItem")
public String admin() {
return "content/admin/reg_item";
}
}
package Kh.study.shop.config;
import javax.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@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()
// .anyRequest().authenticated()
// .and()
.formLogin()
.loginPage("/member/login")
.defaultSuccessUrl("/member/loginResult")
.failureUrl("/member/loginResult")
.loginProcessingUrl("/member/login")// 실제 로그인을 진행할 요청 정보
// 아래 두개를 사용하면, 별도로 name값 username, password 값을 수정할 필요가 없다
.usernameParameter("memberId")
.passwordParameter("memberPw")
.and()
.logout()
.invalidateHttpSession(true)
.logoutSuccessUrl("/item/list")
.and()
.exceptionHandling()
.accessDeniedPage("/member/accessDenied")
;
return security.build();
}
//------------------------- 스프링 암호화 -------------------------------//
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//------css,js 요청 인증 무시 -------------------------------------------------//
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/js/**", "/css/**");
}
}
package Kh.study.shop.config;
public enum MemberStatus {
ACTIVE,DELETED
}
package Kh.study.shop.config;
public enum MemberRole {
MEMBER,ADMIN
}
- 모달창띄우기위해서 매개변수로 boolean값의 isLoginFail을 받아온다. 이 값을 (T/F) 사용하여 모달창을 띄울지 결정하도록 만든다.
@Controller
@RequestMapping("/item")
class ItemController {
//상품목록페이지
//boolean isLoginFail : 로그인 실패시 true/ 그렇지않으면 false
@GetMapping("/list")
public String itemList(boolean isLoginFail, Model model) {
//-----로그인 성공 및 실패 여부를 html에 데이터 전달하기-------//
// 확인
// System.out.println("@@@@@@@@@@@@@@@@@@@" + isLoginFail);
model.addAttribute("isLoginFail",isLoginFail);
return "content/item/item_list";
}
}
- 시큐리티 적용하면서 기존 session 객체를 제거한다.
- 시큐리티를 사용하게되면, 로그인,로그아웃은 별도로 컨트롤러에 메소드 사용없이 자동호출되어 사용가능하다.
- 때문에 PostMapping 방식으로 불러올 필요없다. form태그를 사용하여 html 에서 불러와사용한다. 대신 config파일에도 작성해야한다.
package Kh.study.shop.member.controller;
@Controller
@RequestMapping("/member")
public class MemberController {
@Resource(name = "memberService")
private MemberService memberService;
//---- config에서 만든 암호화 객체 불러와서 사용하기-----------------------------------------------------//
@Autowired
private PasswordEncoder encoder;
//회원가입
@PostMapping("/join")
public String join(MemberVO memberVO) {
// 쿼리문이기때문에 통상적으로는 컨트롤러에 작성하나
// serviceImpl에 한번에 작성하기도 한다. (문제없음)
// memberVO값에 status값 세팅해주기
// null값들어가지않도록 Enum파일에 있는 'ACTIVE' 값넣어주기
memberVO.setMemberStatus(MemberStatus.ACTIVE.toString());
memberVO.setMemberRole(MemberRole.MEMBER.toString());
// 위에서 불러온 암호화 객체를 사용해서 암호화한 비밀번호값 넣어 디비저장해준다.
memberVO.setMemberPw(encoder.encode(memberVO.getMemberPw()));
memberService.join(memberVO);
return "redirect:/item/list";//redirect: 사용해야 컨트롤러 사용한다.(상품목록페이지 )
}
// -------------------------------- 시큐리티 적용 로그인--------------------------------------------------------//
// !!!! 시큐리티 적용하면 post매핑 로그인 자동호출되어 필요 없다 !!!!! //
// //로그인(ajax.ver)
// @ResponseBody//ajax사용할때(단,리턴값은 필요한 데이터만! html페이지가 아님!)
// @PostMapping("/ajaxLogin")
// public boolean ajaxLogin(HttpSession session, MemberVO memberVO) {
// MemberVO loginInfo = memberService.login(memberVO);
// // 로그인 정보 세션 저장
// if(loginInfo != null) {
// session.setAttribute("loginInfo", loginInfo);
// }
// // 바로 loginInfo를 주지않고 삼항연산자 사용한다
// return loginInfo == null? false :true;//자료형 boolean
// }
// 로그인성공시,로그인실패시 -> 로그인페이지로 이동
// 스프링 시큐리티 config에서 설정한 경로대로 보내준다.
@GetMapping("/loginResult")
public String loginResult() {
System.out.println("로그인 결과");
return "content/member/login_result";
}
//------------------------------------------------------------------------------------------------------------//
//관리자_상품등록
@GetMapping("/regItem")
public String regItem() {
System.out.println("상품등록 페이지이동");
return "content/admin/reg_item";
}
// -------------------------------- 시큐리티 적용 전 --------------------------------------------------------//
// //로그인(ajax.ver)
// @ResponseBody//ajax사용할때(단,리턴값은 필요한 데이터만! html페이지가 아님!)
// @PostMapping("/ajaxLogin")
// public boolean ajaxLogin(HttpSession session, MemberVO memberVO) {
// MemberVO loginInfo = memberService.login(memberVO);
// // 로그인 정보 세션 저장
// if(loginInfo != null) {
// session.setAttribute("loginInfo", loginInfo);
// }
// // 바로 loginInfo를 주지않고 삼항연산자 사용한다
// return loginInfo == null? false :true;//자료형 boolean
// }
// //로그인(alert.ver)
// @PostMapping("/login")
// public String login(HttpSession session, MemberVO memberVO) {
// //System.out.println(memberVO);
// MemberVO loginInfo = memberService.login(memberVO);
// // 로그인 정보 세션 저장
// if(loginInfo != null) {
// session.setAttribute("loginInfo", loginInfo);
//
// } else {
// System.out.println("null!!!!!");
// session.setAttribute("loginInfo", null);
// }
// return "content/member/login_result";
// }
//---------------------------------------------------------------------------------------------------------------//
//로그아웃
//get방식과 post방식의 차이점
//GET 방식 : 어떠한 정보를 가져와서 조회하기 위해 사용되는 방식
//POST 방식: 데이터를 서버로 제출하여 추가 또는 수정하기 위해서 데이터를 전송하는 방식
// @GetMapping("/logout")//get메소드사용해야 에러가 안난다
// public String logout(HttpSession session ) {
// session.removeAttribute("loginInfo");
//
// return "redirect:/item/list";
// }
}
- src/main/resources
- mappers
- static
- templates
: 아래 코드를 사용해 하나의 메소드를 만들고 이 메소드를 실행하도록 변수선언해준다.
function isLoginFail(){
const isLoginFail = document.querySelector('#isLoginFail').value;
if(isLoginFail == 'true'){
//아이디를 통해서 모달객체를 받아서 show로 강제로 열리도록(닫히도록) 할수있다.
const myModalAlternative = new bootstrap.Modal('#login_modal');
myModalAlternative.show(); //myModalAlternative.hide();
}
// 확인) 로그인 실패여부를 js에 데이터 불러오기
//const a = `[[${isLoginFail}]]`;
//alert(a);
}
//----------------- 페이지 열림과 동시에 실행되는 코드-----------------------//
isLoginFail();// 아래 함수정의에서 사전에 구현된 기능을 가져오기
//------------------------------ 변수 ---------------------------------------//
//회원가입 모달
const join_modal = document.querySelector('#join_modal');
//로그인 모달
const login_modal = document.querySelector('#login_modal');
//------------------------------ 함수 정의 ----------------------------------//
//회원가입에서 search 버튼 클릭시, 진행 메소드
function searchAddr() {
new daum.Postcode({
oncomplete: function(data) {
// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분입니다.
// 예제를 참고하여 다양한 활용법을 확인해 보세요.
// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 도로명 주소의 노출 규칙에 따라 주소를 조합한다.
// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
const roadAddr = data.roadAddress; // 도로명 주소 변수
document.querySelector('#memberAddr').value = roadAddr;
}
}).open();
}
//로그인 기능 함수(ajax로 로그인실행시 진행)
function goLogin(loginInfo) {
//[[${sessionScope.loginInfo}]] js에서 데이터가져오는 방법
const member_id = login_modal.querySelector('#memberId').value;
const member_pw = login_modal.querySelector('#memberPw').value;
//ajax start
//$을 사용하려면, 제이쿼리 문법이기때문에 자스보다 먼저 로딩해야한다
$.ajax({
url: '/member/ajaxLogin', //요청경로
type: 'post',
data: { 'memberId':member_id ,'memberPw' :member_pw ,'loginInfo' : loginInfo }, //필요한 데이터
success: function(result) {
if(result) {/*loginInfo 값이 null아니면(false)*/
alert('로그인 성공!!!');
location.href='/item/list';
}
else{
alert('로그인 실패!!!');
}
},
error: function() {
alert('로그인 실패');
}
});
//ajax end
}
//// 로그인 실패여부로 모달창을 띄워주는 기능
function isLoginFail(){
const isLoginFail = document.querySelector('#isLoginFail').value;
if(isLoginFail == 'true'){
//아이디를 통해서 모달객체를 받아서 show로 강제로 열리도록(닫히도록) 할수있다.
const myModalAlternative = new bootstrap.Modal('#login_modal');
myModalAlternative.show(); //myModalAlternative.hide();
}
// 확인) 로그인 실패여부를 js에 데이터 불러오기
//const a = `[[${isLoginFail}]]`;
//alert(a);
}
//----------------------------- 이벤트 정의 ---------------------------------//
//////////////////회원가입 모달이 닫히면 실행되는 이벤트(함수 매개변수 event)
join_modal.addEventListener('hidden.bs.modal', function(event) {//모달이 완전히 닫혔을 때 실행
//--------- <모듈창을 다시 열었을 때, 이전 값들 남아있지 않도록 빈값 만들기> ---------//
//[방법(1)_기본]
//회원가입 모달창에 있는 input 태그를 모두들고온다
//('')안에는 css와 동일하다
//input태그 중 버튼만 제외하고 모두 선택한다
// const modal_inputs = join_modal.querySelectorAll('input:not[type="button"]');
//반복문돌려서 모든 input태그 ''빈값을 주면 모듈창을 닫았을 때 빈값이다.
// for(const inputTag of modal_inputs){
// inputTag.value ='';
// }
//[방법(2)_간략]
// 모달창의 from태그를 선택하여 초기화
join_modal.querySelector('form').reset();
});
//////////////////로그인 모달이 닫히면 실행되는 이벤트
login_modal.addEventListener('hidden.bs.modal', function(event) {
login_modal.querySelector('form').reset();
});
<!-- 모든 페이지에 적용시키는 파일 -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"><!-- 타임리프,레이아웃 기능 사용하겠다 -->
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!-- 레이아웃페이지에서 앞으로 모든 내용들이 나오기때문에 여기에 부트스트랩가져오면 모두 자동 적용된다 -->
<!-- 외부의 부트스트랩 사용위해 가져오기 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
<!-- css가져오기 단,static 경로설정은 /부터 넣고 시작한다!(template와의 차이)-->
<link href="/css/common.css" rel="stylesheet">
<!-- body아래 부분에 로딩을 하면 각각의 어떤 파일이 열리는지 순서에따라 상관이 있기때문에 문제가 생길수 있다. -->
<!-- 어떤파일이 열리든 순서 상관없이 로딩시켜서 적용시킬수있도록 가장먼저 로딩한다. -->
<!-- 부트스트랩 로딩 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-u1OknCvxWvY5kfmNBILK2hRnQC3Pr17a+RTT6rIHI7NnikvbZlHgTPOOmMi466C8" crossorigin="anonymous"></script>
<!-- ajax 제이쿼리 로딩 -->
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
</head>
<body>
<div class="container">
<!-- 고정된 top.html 화면 -->
<div class="row">
<div class="col">
<div th:replace="fragment/top::top"></div><!-- fragment폴더 안에 top html파일을 top이라는 이름으로, fragment를 불러와 대체하겠다 -->
</div>
</div>
<!-- 계속바뀌는 베이스화면 -->
<div class="row">
<div class="col">
<div layout:fragment="content"></div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"><!-- 타임리프,레이아웃 기능 사용하겠다 -->
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
<link href="/css/common.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-u1OknCvxWvY5kfmNBILK2hRnQC3Pr17a+RTT6rIHI7NnikvbZlHgTPOOmMi466C8" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
</head>
<body>
<div class="container">
<!-- 고정된 top.html 화면 -->
<div style="margin-bottom: 50px;" class="row">
<div class="col">
<div th:replace="fragment/top::top"></div>
</div>
</div>
<!-- 고정된 side.html 화면 -->
<div style="position: fixed; width: 200px;" class="row">
<div class="col">
<div th:replace="fragment/side::side"></div>
</div>
</div>
<!-- 계속바뀌는 베이스화면 -->
<div style="position: relative; left: 250px;" class="row">
<div class="col">
<div layout:fragment="content"></div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<div th:fragment="top">
<!-- 아이템컨트롤러에서 던져진 로그인여부값을 히든으로 가져와서 받는다. -->
<input type="hidden" id="isLoginFail" th:value="${isLoginFail}">
<div class="row">
<div class="col text-end" sec:authorize="isAnonymous()">
<span data-bs-toggle="modal" data-bs-target="#join_modal" style="color: #3D8361;">JOIN</span>
<span data-bs-toggle="modal" data-bs-target="#login_modal" style="color: #3D8361;">LOGIN</span>
</div>
<div class="col text-end" sec:authorize="isAuthenticated()" >
<span sec:authentication="name"></span> 님 반갑습니다😊
<form th:action="@{/logout}" method="post">
<button sec:authorize="isAuthenticated()" type="submit">LOGOUT</button>
</form>
</div>
</div>
<div class="row">
<div class="col text-center" >
<span style=" color: #A1C298; font-weight: bold; font-size: 55px;">S H O P</span>
</div>
</div>
<!--top MENU -->
<div class="row">
<div class="col">
<nav class="navbar navbar-expand-lg bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#" style="color: #3D8361;">MENU</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Features</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Pricing</a>
</li>
<li class="nav-item">
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
</div>
<!-- login 클릭시 실행 Modal -->
<div class="modal fade" id="login_modal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel" style="font-weight: bold;">L O G I N</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- 로그인창 modal -->
<div class="modal-body">
<!-- 로그인 - 세션이용 데이터 form태그로 보내주기 -->
<!-- 부트스트랩에서 가져왔기때문에 함부러 수정x 형식에 맞게 해야사용가능하다 -->
<form class="row g-3" name="formLogin" method="post" action="/member/login" >
<div class="mb-3">
<!-- label은 input태그의 라벨(스티커)이다. 웬만하면, for값과 id값이 같아야한다 -->
<!-- html 파일 내에 memberId 라는 id값이 여러개 존재한다.(중복발생) -->
<label for="memberId" class="form-label" >ID</label>
<input id="memberId" type="text" class="form-control" name="memberId" aria-describedby="emailHelp" placeholder="Input your ID" >
</div>
<div class="mb-3">
<label for="memberPw" class="form-label">Password</label>
<input type="password" class="form-control" name="memberPw" id="memberPw" placeholder="Input your password">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Login</button> <!--submit은 ajax에서 사용하는 것이 아니다!!! onclick이용해서 ajax? -->
<button type="button" class="btn btn-primary" onclick="goLogin();">AjaxLogin</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- join 클릭시 실행 Modal -->
<div class="modal fade" id="join_modal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel" style="font-weight: bold;">J O I N</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- 너비 조정-->
<!-- col-12 / col-6 : 한 줄 너비 총 크기 12이기때문에 12분등 중 어느만큼 사용하는건지 -->
<form class="row g-3" action="/member/join" method="post">
<!-- class값은 고정 건드리면 안됨. 부트스트랩사용하기위한 의미 -->
<!-- for태그와 id태그 이름은 같게 하도록 -->
<div class="col-12">
<label for="memberId" class="form-label">ID</label>
<input type="text" class="form-control" id="memberId" placeholder="put your ID" name="memberId">
</div>
<div class="col-12">
<label for="memberPw" class="form-label">PASSWORD</label>
<input type="password" class="form-control" id="memberPw" name="memberPw" placeholder="put your Password">
</div>
<div class="col-12">
<label for="memberName" class="form-label">NAME</label>
<input type="text" class="form-control" id="memberName" name="memberName" placeholder="put your Name">
</div>
<!-- 주소(상세주소 + 검색버튼추가) -->
<!-- 줄을 맞추기위해 실제내용은 화면에 안보이도록 공백문자사용/ class에 form-control 붙여넣기 -->
<div class="col-9">
<label for="memberAddr" class="form-label"> ADDRESS </label>
<input type="text" class="form-control" id="memberAddr" name="memberAddr" readonly onclick="searchAddr();"><!-- 값변경못하도록 읽기전용속성값부여하기 ( 데이터넘기기 가능) -->
</div>
<div class="col-3" >
<label for="" class="form-label"> </label>
<input type="button" class="btn btn-secondary form-control" onclick="searchAddr();" value="Search">
</div>
<div class="col-12" >
<input type="text" class="form-control" id="addrDetail" name="addrDetail">
</div>
<div class="col-12">
<label for="memberEmail" class="form-label">EMAIL</label>
<input type="text" class="form-control" id="memberEmail" placeholder="put your Email" name="memberEmail">
</div>
<div class="d-grid gap-2 col-12" >
<!-- 버튼클릭하나로 모든 데이터 가져가야하므로 무조건 submit! -->
<button type="submit" class="btn btn-primary">JOIN</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- 카카오API 사용위해 자바스크립트 사용 전, 미리 로드하기 -->
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<!-- js불러오는 방법_기본 (1) -->
<!-- 주의)html 파일 이외의 파일들은 static 폴더 기준이면서 맨앞은 '/'넣어줘야한다!!!(templates X) -->
<!-- src속성값을 넣어주면 해당 파일에 js찾아서 불러온다는 기능 -->
<!-- <script type="text/javascript" src="/js/layout/top.js"></script> -->
<!-- js불러오는 방법_타임리프 (2) -->
<!-- 기본방법과 비슷하지만 @{}사용해야한다! -->
<script type="text/javascript" th:src="@{/js/layout/top.js}"></script>
<!-- 위 방법의 차이점은? -->
<!-- 디자이너와 협업시 용이하다.
이클립스를 사용하지 않는 디자이너분들은 타임리프를 사용해서 타임리프로 된 html파일은 데이터 던져주면,
데이터값을 확인할 수 있다. 타임리프를 사용하지않으면 디자이너분들이 사용하기 불편하기때문에 사용하는 것이다. -->
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div th:fragment="side">
<!-- 사이드메뉴 -->
<div class="d-flex align-items-start">
<div class="nav flex-column nav-pills me-3" id="v-pills-tab" role="tablist" aria-orientation="vertical">
<button class="nav-link active" id="v-pills-home-tab" data-bs-toggle="pill" data-bs-target="#v-pills-home" type="button" role="tab" aria-controls="v-pills-home" aria-selected="true">상품등록</button>
<button class="nav-link" id="v-pills-profile-tab" data-bs-toggle="pill" data-bs-target="#v-pills-profile" type="button" role="tab" aria-controls="v-pills-profile" aria-selected="false">상품관리</button>
<button class="nav-link" id="v-pills-disabled-tab" data-bs-toggle="pill" data-bs-target="#v-pills-disabled" type="button" role="tab" aria-controls="v-pills-disabled" aria-selected="false" >회원권한설정</button>
<button class="nav-link" id="v-pills-messages-tab" data-bs-toggle="pill" data-bs-target="#v-pills-messages" type="button" role="tab" aria-controls="v-pills-messages" aria-selected="false">메뉴관리</button>
</div>
<div class="tab-content" id="v-pills-tabContent">
<div class="tab-pane fade show active" id="v-pills-home" role="tabpanel" aria-labelledby="v-pills-home-tab" tabindex="0"></div>
<div class="tab-pane fade" id="v-pills-profile" role="tabpanel" aria-labelledby="v-pills-profile-tab" tabindex="0"></div>
<div class="tab-pane fade" id="v-pills-disabled" role="tabpanel" aria-labelledby="v-pills-disabled-tab" tabindex="0"></div>
<div class="tab-pane fade" id="v-pills-messages" role="tabpanel" aria-labelledby="v-pills-messages-tab" tabindex="0"></div>
</div>
</div>
</div>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 시큐리티적용시 세션이 적용되면 실행이 되지 않는다!!! -->
<!-- 시큐리티 적용 후-->
<!-- 로그인 인증 실패하면-->
<div sec:authorize="isAnonymous()">
<script type="text/javascript">
alert('로그인 실패');
location.href='/item/list?isLoginFail=true';
//로그인 모달창 띄운다.
</script>
</div>
<div sec:authorize="isAuthenticated()" >
<div sec:authorize="hasRole('ROLE_MEMBER')" >
<script type="text/javascript">
alert('로그인 인증 성공');
location.href='/item/list';
</script>
</div>
<div sec:authorize="hasRole('ROLE_ADMIN')" >
<script type="text/javascript">
alert('관리자 인증 성공');
location.href='/admin/regItem';
</script>
</div>
</div>
</body>
</html>
<!-- 시큐리티 적용 전 ajax사용 -->
<!-- <th:block th:if="${session.loginInfo == null}">
<script type="text/javascript">
alert('로그인 실패!!');
location.href='/item/list';
</script>
</th:block>
<th:block th:unless="${session.loginInfo == null}">
<script type="text/javascript">
alert('로그인 성공!!!');
location.href='/item/list';
</script>
</th:block> -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="layout/admin_layout">
<div layout:fragment="content">
상품관리등록 관리자 페이지입니다.
</div>
</html>
원래 ajax를 이용하여 페이지이동시, ajax는 한페이지에서만 가능하기때문에 사라진다. 모달창이 꺼진다.
하지만, 이를 로그인실패시에도 계속 로그인 모달창이 뜰 수 있도록 구현하려한다.
잘못된 로그인 정보 값 입력하여 로그인 실패하기
로그인 실패 alert창이 뜬다.
확인 버튼 클릭시, 다시 로그인 창으로 이동한다.이때, 다시 모달창이 뜨도록 구현했다.(js모달창)
관리자(사전 권한부여하여 디비 업데이트)로 로그인하여 관리자페이지 보이도록 만든다.
관리자페이지 총 3단으로 구성하여 구현한다.
: 왼쪽 사이드바, 위는 top으로 아이디값과 로그아웃버튼, 오른쪽은 계속바뀌는 content영역으로 가장처음은 상품관리등록페이지로 구현함.