예제 순서
- spring_board_security
- spring_board_security_using_db
오늘 공부 한 것
- 화면에 출력되는 것을 몇개만 잘라서 보여줘야됨 페이징필요 예제) spring_board
- 쿼리문
ㅇ 시작번호와 끝번호를 쿼리문에 전달 필요
ㅁ StartEnd dto객체를 통해 시작값과 종료값을 담아 전달
package kr.co.jhta.board.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
// lombok을 통해 dto 설정
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StartEnd {
private int startNo;
private int endNo;
}
ㅁ sqlMapConfig의 별칭을 지정해서 쿼리문을 전달 할 것임
<typeAlias type="kr.co.jhta.board.dto.StartEnd" alias="se"/>
ㅁ dao에서 인자값으로 시작번호와 종료번호를 전달하고
객체로 감싸서 mapper로 전달
@Override
public List<BoardDTO> selectAll(int startNo, int endNo) {
StartEnd se = new StartEnd(startNo, endNo);
logger.info("ss : "+ ss);
return ss.selectList("kr.co.jhta.board.selectAll", se);
}
ㅁ service에서도 인터페이스와 구현클래스에 인자값을 전달
@Override
public List<BoardDTO> readAll(int startNo, int endNo) {
logger.info("dao : "+ dao);
return dao.selectAll(startNo, endNo);
}
ㅁ 컨트롤러에서 서비스로 인자값을 전달
model.addAttribute("list", bs.readAll(startNo, endNo));
ㅁ BoardMapper에서 쿼리문을 수정
<select id="selectAll" resultType="bdto" parameterType="se">
SELECT RN , BNO, WRITER, TITLE, CONTENTS, REGDATE, HITS, IP, STATUS
FROM ( SELECT ROWNUM RN , BNO, WRITER, TITLE, CONTENTS, REGDATE, HITS, IP, STATUS
FROM (SELECT BNO, WRITER, TITLE, CONTENTS, TO_CHAR(REGDATE,'YYYY/MM/DD') REGDATE, HITS, IP, STATUS
FROM BOARD
ORDER BY BNO DESC )
WHERE ROWNUM <=#{endNo} )
WHERE RN >=#{startNo}
</select>
- 전체 게시물수 출력
ㅇ 컨트롤러 => 서비스 => dao => mapper (전체 카운트)를 통해 게시물의 수 전달
// Controller 총 게시물 수를 받아 변수에 대입
int totalNumber = bs.getTotal();
// BoardService 인터페이스 추상메서드 선언
public int getTotal();
// BoardServiceImple 오버라이드
@Override
public int getTotal() {
return dao.getTotal();
}
// Dao 인터페이스 추상메서드 선언
public int getTotal();
// BoardOracleDAO 오버라이드
@Override
public int getTotal() {
// 1건만 가져오면 됨
return ss.selectOne("kr.co.jhta.board.totalCount");
}
// BoardMapper 별칭 지정 및 쿼리문 작성
<select id="totalCount" resultType="int">
SELECT count(bno) cnt
FROM board
</select>
- 현재 페이지 번호, 현재 페이지 게시물 시작번호, 현재 페이지 게시물 끝번호,
페이지당 게시물 수, 총 페이지수 컨트롤러에서 구한 후 list페이지로 넘겨서
출력되는 것을 확인
// 현재 페이지 번호
// int currentPageNo = 1;
// 총 게시물 수
int totalNumber = bs.getTotal();
// 시작번호
int startNo = (currentPageNo-1)*10+1;
// 끝 번호
int endNo = currentPageNo*10;
// 페이지당 게시물 수
int countPerPage = 10;
// 총 페이지 수
int totalPage = (totalNumber%countPerPage==0)? totalNumber/countPerPage:
totalNumber/countPerPage+1;
// 페이지 시작 번호
int startPageNo = currentPageNo-5<=0? 1:currentPageNo-5;
// 페이지 끝 번호
int endPageNo = startPageNo+10 >= totalPage? totalPage:startPageNo+9;
- 리스트 페이지 하단 부에 반복문을 통해 전달받은 시작페이지번호와
종료 페이지번호 만큼 반복 반복된 수만큼 선언한 i변수의 값을 증가 되고
화면에 출력 => a태그를 통해 list url과 쿼리스트링으로
<c:forEach var="i" begin="${startPageNo}" end="${endPageNo}">
<a href="list?currentPageNo=${i}"><button>${i}</button></a>
</c:forEach>
@RequestMapping(value = "/list")
public String list(Model model,
@RequestParam(name = "currentPageNo", defaultValue = "1")int currentPageNo)
// 전달받은 파라미터에 초기값을 지정하면 굳이 변수를 선언 할 필요가 없다.
- 이전 버튼과 다음 버튼을 구현
boolean prev = currentPageNo > 5? true : false;
<c:choose>
<c:when test="${prev}">
<a href="list?currentPageNo=${currentPageNo-5}">
<button>[이전]</button>
</a>
</c:when>
</c:choose>
boolean next = currentPageNo+5 >= totalPage? false : true;
<c:choose>
<c:when test="${next}">
<a href="list?currentPageNo=${currentPageNo+5}">
<button>[다음]</button>
</a>
</c:when>
</c:choose>
- spring security 보안 강화 예제) spring_board_security
ㅇ 인증(Authentication) : 본인이 무엇인가를 증명하는 행위
ㅇ 인가(Authorization) : 권한 부여 => 인증을하면 나의 등급에 맞게 부여되는 권한
ㅇ security_context.xml sprig confing 생성 -> 보안관련 설정 여기다 할 것임
ㅁ web.xml 추가
DelegatingFilterProxy : 모든 요청은 이 프록시필터를 거친다. 스프링시큐리티는 이를 통해 인증, 인가를 수행
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/root-context.xml
/WEB-INF/spring/appServlet/security-context.xml
</param-value>
</context-param>
ㅇ pom.xml(web, taglibs, core, config) 추가 : mvn repository 사용
<!-- security core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- security web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- security config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- security taglibs -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
ㅇ security-context.xml namespace security 설정
ㅇ security-context.xml security 설정추가
<!-- * 현 디렉토리의 모든 파일 ** 하위 디렉토리까지 모두 -->
<!--
ROLE_ 형식은 반드시 지켜야됨
pattenr에 해당하는 요청을 받았을 때, access로 설정된 권한을
가지고 있어야 접근 가능(지정된 권한만 허용)
-->
<!-- ex) url로 board가 왔을 때 인증화면을 띄운다. -->
<security:http auto-config="true">
<security:intercept-url pattern="/board/*" access="isAuthenticated()"/>
<security:intercept-url pattern="/admin/*" access="hasRole('ROLE_ADMIN')"/>
<security:intercept-url pattern="/public/**" access="permitAll"/>
<security:intercept-url pattern="/member/**" access="hasRole('ROLE_USER')"/>
</security:http>
<!--
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="user" password="123" authorities="ROLE_USER"/>
<security:user name="admin" password="123" authorities="ROLE_ADMIN,ROLE_USER"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
<!-- 요청이 들어 올 때 이걸 잡아내는 필터가 필요함 -->
ㅇ web.xml 필터추가
ㅁ DelegatingFilterProxy : 모든 요청은 이 프록시필터를 거친다. 스프링시큐리티는 이를 통해 인증, 인가를 수행
<!-- 연결이 요청이오면 어느 필터 접근하는지 파악하고 시큐리티가 동작하게 됨 -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
ㅇ Controller에서 security에서 지정한 요청으로 전체 url 수정
@RequestMapping(value = "/board/list")
public String list(Model model,
ㅇ home페이지에서 a태그 요청URL을 board/list로 수정
<a href="board/list">게시판 가기</a>
ㅇ home 페이지에서 a태그를 통해 board/list url로 접근시 인증페이지 출력 확인
ㅇ security 설정에서 지정한 아이디와 비밀번호 입력 후 진행시 오류 발생 확인
ㅁ why?
spring 4.0까지는 아무 비밀번호나 텍스트롤 사용해도 되었는데 5.0부터는 보안 문제로
password를 암호화해서 체크 필요
ㅁ 임시 해결 : 한시적으로 password에 noop을 지정해서 진행가능
인증에 대한 처리를 쉽게 spring을 통해 구현 가능
<security:user name="user" password="{noop}123" authorities="ROLE_USER"/>
<security:user name="admin" password="{noop}123" authorities="ROLE_ADMIN,ROLE_USER"/>
ㅇ 로그인을 한 번하면 재검사를 하지 않음.
ㅁ JSESSIONID : 서버에서 인증가능한 키 wat tomcat was 마다 이름이 조금씩 다름
ㅁ session이 끊기지 않는한 로그인 상태 유지 됨
ㅁ 브라우저 종료하고 다시 키면 JSESSIONID가 다름. 로그인이 다시 필요함
ㅇ 전체 메서드에 공통으로 들어갈 url을 requestMapping을 통하여 추가
@RequestMapping("/board")
- write 기능 구현
ㅇ jstl talib 추가
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
ㅇ 인증받은 유저 아이디값으로 작성자 고정 및 변경 못 하게 지정
<input type="text" name="" id="" value="<sec:authentication property="principal.username" />"
readonly="readonly"/>
ㅇ 서버가 발급한 특정한값을 입력할때만 수정을 받아줄게.
이거 내 사이트에서 발급한 토근이 맞아? 일련의 수를 실어줌??????
ㅇ 토근 발급
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token }"
ㅇ security-context.xml 사용하지 않게 설정 할 수도 있음.
<security:csrf disabled="true"/>
ㅇ Controller에서 redirect 사용시에 url board추가해줘야 진행됨
return "redirect:/board/list";
ㅇ post로 url전달 시에 토큰 키를 발급해줘야 됨.
- 커스텀 로그인 페이지
- spring은 bootstrap 기반으로 이루어져있음(4.0)
ㅇ 사용자 정의용으로 커스텀하게 로그인페이지를 구성 가능
ㅇ security-context.xml 커스텀 로그인 추가
ㅇ security:http 안에
<security:form-login login-page="/common/cLogin"/>
ㅇ
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>사용자 정의형 로그인 페이지</title>
</head>
<body>
<h1>사용자 정의형 로그인 페이지</h1>
<h2>
<c:out value="${error}" />
</h2>
<h2>
<c:out value="${logout}"/>
</h2>
<c:url value="/login" />
<form action="<c:url value="/login" />" method="post">
<table>
<tr>
<th>ID</th>
<td>
<input type="text" name="username" id="" value="user" />
</td>
<tr>
<th>password</th>
<td>
<input type="text" name="password" id="" value="123"/>
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="로그인" />
<input type="hidden" name="${_csrf.parameterName}"
value="${_csrf.token }"/>
</td>
</tr>
</table>
</form>
</body>
</html>
- CommonController 클래스 생성
- Get방식으로 url요청이 올 경우 진행
package kr.co.jhta.board.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class CommonController {
private static final Logger logger = LoggerFactory.getLogger(CommonController.class);
@RequestMapping(value = "/common/cLogin")
public void login(String error, String logout, Model model) {
if(error!= null)
model.addAttribute("error", "로그인 중 에러 발생");
else if (logout!=null)
model.addAttribute("logout", "logout");
}
- 로그아웃 기능 구현
ㅇ list 페이지 a태그를 사용하여
<sec:authorize access="isAuthenticated()">
<!-- c:url을 쓰면 contextpath를 포함하여 url값을 받는다. -->
<a href="<c:url value="/common/customLogout" />">로그아웃</a>
</sec:authorize>
ㅇ security-context.xml 설정 추가 security:http 안에
<security:logout delete-cookies="JSESSIONID"
invalidate-session="true"
logout-url="/common/customLogout" logout-success-url="/" />
ㅇ Controller 추가
@RequestMapping(value = "/common/customLogout")
public void logoutGet() {
logger.info("커스텀 로그아웃");
}
ㅇ
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>logout</h1>
<c:url value="/common/customLogout"/>
<form action="<c:url value="/common/customLogout"/>" method="post">
<input type="hidden" name="${_csrf.parameterName}"
value="${_csrf.token }"/>
<input type="submit" value="로그아웃" />
</form>
</body>
</html>
- 아이디와 패스워드를 굳이 공개 할 필요가 있을까? - spring_board_security
ㅇ 인증을 데이터베이스를 통해 할 수 있다.
ㅇ 반드시 정해져있는는 테블명과 컬럼명으로 해야된다.
ㅁ sql문을 하지 않아도 된다.
SQL> create table users
2 (username varchar2(50) not null primary key,
3 password varchar2(50) not null,
4 enavled char(1));
테이블이 생성되었습니다.
SQL> create table authorities
2 (username varchar2(50) not null,
3 authority varchar2(50) not null);
테이블이 생성되었습니다.
SQL> insert into users values ('user1', '123', '1');
1 개의 행이 만들어졌습니다.
SQL> COMMIT
SQL> insert into authorities values('LJY','ROLE_USER');
1 개의 행이 만들어졌습니다.
SQL> COMMIT
- 인증 설정에 db 관련된 인증을 추가
ㅇ spring 설정 파일 읽는 순서가 있음 => dataSource를 현재 설정파일로 이동
ㅇ 기존 아이디 비밀번호 인증은 삭제
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"
value="oracle.jdbc.driver.OracleDriver" />
<property name="url"
value="jdbc:oracle:thin:@192.168.0.35:1521:orcl" />
<property name="username" value="scott" />
<property name="password" value="tiger" />
</bean>
<security:jdbc-user-service data-source-ref="dataSource"/>
- 패스워드 반듣시 암호화 시켜 스프링 버젼업
password encoder라는 것을 반드시 추가해야됨
{noop} password encoder가 없어도 됬으나 db를 쓸경우에는 반드시 추가 해야됨.
security 패키지 및 custompasswordencoder 클래스 생성
xml에 입력하면 아이디 비밀번호가 공개되어있음 => db를 통해서 접속 할 것임
스피링 5.0이 되면서 암호화방식이 필수로 해야 됨
현재는 그냥 리턴하게 해놨음
package kr.co.jhta.board.security;
import org.springframework.security.crypto.password.PasswordEncoder;
public class CunstomPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
System.out.println("패스워드 : " +rawPassword);
return rawPassword.toString();
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
// 입력된 패스워드와 인코딩 패스워드가 동일한지 여부를 리턴
return rawPassword.toString().equals(encodedPassword);
}
}
<!-- 비밀번호 암호화 bean -->
<bean id="passwordEncoder" class="kr.co.jhta.board.security.CunstomPasswordEncoder" />
<security:password-encoder ref="passwordEncoder"/>
<a href="list"><input type="button" value="목록" /></a>
<!-- 속성을 변수로 만든 것 -->
<sec:authentication property="principal" var="pinfo"/>
<!-- 자기 자신의 아이디와 작성자와 같을 경우에만 버튼이 사용자에게 보여진다. -->
<!-- 배열 형태로 담겨 있다. -->
<!-- c:out 화면에 system.out.println 값을 출력하는 것 -->
<c:out value="${pinfo.authorities}"></c:out>
<c:forEach var="p" items="${pinfo.authorities}">
<c:if test="${pinfo.username eq dto.writer || p eq 'ROLE_ADMIN'}">
<a href="modify?bno=${dto.bno}"><input type="button" value="수정" /></a>
<a href="delete?bno=${dto.bno}"><input type="button" value="삭제" /></a>
</c:if>
</c:forEach>
- 사용자의 속성을 볼 수 있는 security 속성
복습 할 것
whois검색 : 도메인을 누가 가지고 있는 확인 할 수 있음
tip.
용어
'자바 풀스택 교육 > Spring' 카테고리의 다른 글
Spring 교육정리 15일차 (0) | 2020.02.25 |
---|---|
Spring 교육정리 13일차 (0) | 2020.02.21 |
Spring 교육정리 12일차 (0) | 2020.02.20 |
Spring 교육정리 11일차 (0) | 2020.02.19 |
Spring 교육정리 9일차 (0) | 2020.02.18 |