Spring(2023-04-19)

권단비·2023년 4월 19일
0

IT

목록 보기
125/139

[스프링 시큐리티 커스텀 마이징]

・스프링 시큐리티(인증과 권한) = 인증과 권한을 위한 솔루션(프레임워크)
시큐리티 커스터 마이징 : 비밀번호를 암호화 시켜주는것 || 사용자별로 인증 및 인가를 적절하게 지정할 수 있음

실행순서
1.loadUserByUsername : [CustomUserDetailsService.java]
일반고객x, vip만 받음 // DB에 리스트 갖고 있음
・시큐리티에 vip목록을 담을 형식을 시큐리티가 만들어줌
 (UserDetails||public class UserDetailsVO implements UserDetails)
・vip목록 전체를 주는 것이 아닌 로그인을 먼저 받아서(request객체)
 id를 알려주면 목록에서 검색하여 알려주겠다.(시큐리티가 pw는 알려주지 않음)
 (loadUserByUsername||public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException)


[계산 pom.xml]
<?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.6.8</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>edu.global</groupId>
	<artifactId>ex</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring_boot_kdb_security</name>
	<description>Board project for Spring Boot</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<repositories>
		<repository>
			<id>oracle</id>
			<url>http://www.datanucleus.org/downloads/maven2/</url>
		</repository>
	</repositories>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- spring-boot-devtools는 클래스 수정시 웹서버를 재시작하여 결과를 바로 반영 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- 오라클 JDBC 드라이버 -->
		<dependency>
			<groupId>oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

		<!-- MyBatis 라이브러리 -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.4</version>
		</dependency>

		<!-- MyBatis sql pretty -->
		<dependency>
			<groupId>org.bgee.log4jdbc-log4j2</groupId>
			<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
			<version>1.16</version>
		</dependency>

		<!-- JSP를 사용하기 위한 라이브러리 -->
		<!-- 톰캣 파서 -->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
		</dependency>

		<!-- jstl 라이브러리 -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
		</dependency>
	</dependencies>

	<build><!-- 컴파일~배포까지 -->
		<plugins>
			<plugin>
				<groupId>org.apache.tomcat.maven</groupId>
				<artifactId>tomcat7-maven-plugin</artifactId>
				<version>2.2</version>
				<configuration>
					<url>http://146.56.137.240:8282/manager/text</url>
					<username>admin</username>
					<password>1234</password>
				</configuration>
			</plugin>
			<!-- cmd에 입력 ( 배포 ) : mvnw.cmd tomcat7:redeploy -->
			<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>
---------------------------------------------------------------------
[계산 application.properties]
#server port number
server.port = 8282

#datasource (oracle)
#spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
#spring.datasource.url=jdbc:oracle:thin:@localhost:1521/xe
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.url=jdbc:log4jdbc:oracle:thin:@localhost:1521/xe
spring.datasource.username=scott
spring.datasource.password=tiger

spring.devtools.livereload.enabled=true

#MyBatis
#xml location
#src/main/resources/mappers
mybatis.mapper-locations=classpath:mappers/`**/*.xml
mybatis.type-aliases-package=edu.global.ex

#jsp 
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

#logging
#logging.level.root=info
---------------------------------------------------------------------
[계산 HomeController.java]
package edu.global.ex.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class HomeController {

	@GetMapping("/")
	public String home() {
		return "home";
	}

	@GetMapping("/user/userHome")
	public void userHome() { // void는 return값이 없지만, userHome.jsp를 리턴함
		log.info("userHome ...");
	}
//상기 userHome함수는 하기와 동일한 의미이다.
//	@GetMapping("/user/userHome")
//	public String userHome() {
//		log.info("userHome ...");
//		return "user/userHome";
//	}

	@GetMapping("/admin/adminHome")
	public void adminHome() {
		log.info("adminHome ...");
	}
}
---------------------------------------------------------------------
[계산 SecurityConfig.java]
package edu.global.ex.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import edu.global.ex.security.CustomUserDetailsService;

@Configuration // @Component + 의미(설정할 수 있는 파일)//(객체 생성해서 IOC컨테이너에 넣어라)
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록됨 = 스프링 시큐리티를 작동시키는 파일이라는 것을 알려줌 - 스프링한테.
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private CustomUserDetailsService customUserDetailsService;

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// 우선 CSRF설정을 해제한다.
		// 초기 개발시만 해주는게 좋다.
		http.csrf().disable();
		http.authorizeRequests().antMatchers("/user/**").hasAnyRole("USER") // DB상에서는 ROLE_USER이다 /user/**(URL) : user로 입력하여 들어오는 모든 것에 인증을 시키겠다.
			.antMatchers("/admin/**").hasAnyRole("ADMIN") // id: admin, pw:admin /admin/**(URL) : admin으로 들어오는 모든 것에 인증을 시키겠다.
			.antMatchers("/**").permitAll();

//		http.formLogin(); // 스프링 시큐리티에 있는 기본 로그인 폼을 사용하겠다.
		http.formLogin().loginPage("/login") // loginPage() 는 말 그대로 로그인 할 페이지(LoginController.java) url 비교이고,
			.usernameParameter("id") // login.jsp의 name을 database의 이름인 "username"이 아닌 "id"로 설정
			.passwordParameter("pw")
			.permitAll(); // 모든 유저가 로그인 화면을 볼 수 있게 한다.
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {

//		auth.inMemoryAuthentication().withUser("user").password("{noop}user").roles("USER").and()
//		.withUser("admin").password("{noop}admin").roles("ADMIN");

		auth.userDetailsService(customUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
	}
}
---------------------------------------------------------------------
[계산 UserVO.java]
package edu.global.ex.vo;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class UserVO {
	private String username;
	private String password;
	private int enabled;

	private List<AuthVO> authList;
}
---------------------------------------------------------------------
[계산 AuthVO.java]
package edu.global.ex.vo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class AuthVO {
	private String username;
	private String authority;
}
---------------------------------------------------------------------
[계산 UserMapper.java]
package edu.global.ex.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import edu.global.ex.vo.UserVO;

@Mapper // MyBatis용 인터페이스라는 것을 알려주는 어노테이션
public interface UserMapper {
	public UserVO getUser(String username);

	@Insert("insert into users(username,password,enabled) values(#{username},#{password},#{enabled})")
	public int insertUser(UserVO userVO);

	@Insert("insert into AUTHORITIES (username,AUTHORITY) values(#{username},'ROLE_USER')")
	public void insertAuthorities(UserVO UserVO);
}
---------------------------------------------------------------------
[계산 UserMapper.xml]
<?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="edu.global.ex.mapper.UserMapper">

	<resultMap id="userMap" type="UserVO">
		<result property="username" column="username" />
		<result property="password" column="password" />
		<result property="enabled" column="enabled" />
		<collection property="authList" resultMap="authMap"></collection>
	</resultMap>

	<resultMap id="authMap" type="AuthVO">
		<result property="username" column="username" />
		<result property="authority" column="authority" />
	</resultMap>

	<select id="getUser" resultMap="userMap">
		<!-- 조인 처리 -->
		select * from users , authorities
		where users.username = authorities.username and users.username = #{username}
	</select>
</mapper>
---------------------------------------------------------------------
[계산 UserDetailsVO.java]
package edu.global.ex.vo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class UserDetailsVO implements UserDetails { // DB와 연결 || ID/PW/Authority를 가져와 연결
	private String username; // ID
	private String password; // PW
	private List<GrantedAuthority> authorities;

	public UserDetailsVO(UserVO user) {
		this.setAuthorities(user);
		this.setPassword(user.getPassword());
		this.setUsername(user.getUsername());
	}

	// setter
	public void setUsername(String username) {
		this.username = username;
	}

	// setter
	public void setPassword(String password) {
		this.password = password;
	}

	// setter
	public void setAuthorities(UserVO userVO) {

		List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

		for (AuthVO auth : userVO.getAuthList()) {
			authorities.add(new SimpleGrantedAuthority(auth.getAuthority()));
		}

		this.authorities = authorities;
	}
	// ==============================================================================
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return authorities;
	}

	@Override
	public String getPassword() {
		return password;
	}

	@Override
	public String getUsername() {
		return username;
	}

	// 계정이 만료되지 않았는가?
	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	// 계정이 잠기지 않았는가?
	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	// 패스워드가 만료되지 않았는가?
	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	// 계정이 활성화 되었는가?
	@Override
	public boolean isEnabled() {
		return true;
	}
}
---------------------------------------------------------------------
[계산 CustomUserDetailsService.java]
package edu.global.ex.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import edu.global.ex.mapper.UserMapper;
import edu.global.ex.vo.UserDetailsVO;
import edu.global.ex.vo.UserVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class CustomUserDetailsService implements UserDetailsService {

	@Autowired
	private UserMapper userMapper;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// id/pw 결과를 무전기로 알려주는 부분
		//throws는 id를 찾을 수 없을 때 login.jsp의 <c:if test="${param.error != null}">로 이동한다.
		log.warn("Load User By UserVO number: " + username);
		UserVO vo = userMapper.getUser(username);

		log.warn("queried by UserVO mapper: " + vo);

		return vo == null ? null : new UserDetailsVO(vo);
	}
}
---------------------------------------------------------------------
[계산 UserMapperTest.java] : Junit Test(UserMapper.java 우측 클릭)
package edu.global.ex.mapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import edu.global.ex.vo.UserVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@SpringBootTest
class UserMapperTest {

	@Autowired
	private UserMapper userMapper;

//	@Test
//	void testInserUser() {
//
////      @Insert("insert into users(username,password,enabled) values(#{username},#{password},#{enabled})")
////      public int insertUser(UserVO userVO);
////
////      @Insert("insert into AUTHORITIES (username,AUTHORITY) values(#{username},'ROLE_USER')")
////      public void insertAuthorities(UserVO UserVO);
//
//		UserVO user = new UserVO();
//		user.setUsername("kim4");
//		user.setPassword(new BCryptPasswordEncoder().encode("1234"));
//		user.setEnabled(1);
//
//		userMapper.insertUser(user);
//		userMapper.insertAuthorities(user);
//	}

	@Test
	void testInwerAdminsur() {
		UserVO user = new UserVO();
		user.setUsername("admin2");
		user.setPassword(new BCryptPasswordEncoder().encode("admin2"));
		user.setEnabled(1);

		userMapper.insertUser(user);
//		userMapper.insertAuthorities(user);
		userMapper.insertAdminAuthorities(user);
	}
}
---------------------------------------------------------------------
[계산 home.jsp]
<%@ 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="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="sec"
	uri="http://www.springframework.org/security/tags"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>메이페이지</title>
</head>
<body>
	<h1>메인페이지</h1>
	<sec:authorize access="isAnonymous()">
		<p>
			<a href="<c:url value="/login/loginForm" />">로그인</a>
		</p>
	</sec:authorize>

	<sec:authorize access="isAuthenticated()">
		<form:form action="${pageContext.request.contextPath}/logout"
			method="POST">
			<input type="submit" value="로그아웃" />
		</form:form>
		<p>
			<a href="<c:url value="/loginInfo" />">로그인 정보 확인 방법3 가지</a>
		</p>
	</sec:authorize>
	<h3>
		[<a href="<c:url value="/add/addForm" />">회원가입</a>] [<a
			href="<c:url value="/user/userHome" />">유저 홈</a>] [<a
			href="<c:url value="/admin/adminHome" />">관리자 홈</a>]
	</h3>
</body>
</html>
---------------------------------------------------------------------
[계산 adminHome.jsp]
<%@ 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="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="sec"
	uri="http://www.springframework.org/security/tags"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>관리자 홈</title>
</head>
<body>
	<h1>관리자 페이지 입니다.</h1>
	<h3>
		[<a href="<c:url value="/" />">홈</a>]
	</h3>
</body>
</html>
---------------------------------------------------------------------
[계산 userHome.jsp]
<%@ 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="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="sec"
	uri="http://www.springframework.org/security/tags"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>유저 페이지</title>
</head>
<body>
	<h1>유저 페이지 입니다.</h1>
	<p>
		principal:
		<sec:authentication property="principal" />
	</p>
	<%-- <p>EmpVO: <sec:authentication property="principal.emp"/></p>
<p>사용자이름: <sec:authentication property="principal.emp.ename"/></p>
<p>사용자월급: <sec:authentication property="principal.emp.sal"/></p>
<p>사용자입사일자: <sec:authentication property="principal.emp.hiredate"/></p> --%>
	<p>
		<a href="<c:url value="/" />">홈</a>
	</p>
</body>
</html>
---------------------------------------------------------------------
[계산 LoginController.java]
package edu.global.ex.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class LoginController {

	@GetMapping("/login")
	public String login() {
		log.info("login()..");
		return "login/login";
	}
}
---------------------------------------------------------------------
[계산 login.jsp]
<%@ 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="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>로그인 페이지</title>
</head>
<body onload="document.f.id.focus();">
	<h3>아이디와 비밀번호를 입력해주세요.</h3>
	<c:url value="/login" var="loginUrl" />
	<p>${loginUrl}</p>
	<form:form name="f" action="${loginUrl}" method="POST"> <%-- action="${loginUrl}" == /login --%>
		<c:if test="${param.error != null}"> <!-- CustomUserDetailsService.java와 링크됨. -->
			<p>아이디와 비밀번호가 잘못되었습니다.</p>
		</c:if>
		<c:if test="${param.logout != null}">
			<p>로그아웃 하였습니다.</p>
		</c:if>
		<p>
			<label for="username">아이디</label>
			<input type="text" id="id" name="id" /> <!-- SecurityConfig.java의 .usernameParameter("id")로 인해 username이 아닌 id로 받아올 수 있다. -->
		</p>
		<p>
			<label for="password">비밀번호</label>
			<input type="password" id="password" name="pw" /><!-- SecurityConfig.java의 .usernameParameter("pw")로 인해 password이 아닌 pw로 받아올 수 있다. -->
		</p>
		<%-- <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> --%>
		<button type="submit" class="btn">로그인</button>
	</form:form>
</body>
</html>
[결과값]

localhost:8282/

유저 홈 클릭


홈-로그아웃

관리자 홈


[로그인]

쿠키-세션방식 : 연결성 유지를 위한 방식 || 한 번 로그인 하면 종료해도 로그인되어 있음
쿠키 : 웹브라우저에 저장할 수 있는 공간 
세션 : 서버에 저장할 수 있는 공간

〇〇님 환영합니다 설정

시큐리티에 저장되어 있는 id/pw를 사용

[로그아웃 : 세션 만료]

<form:form action="${pageContext.request.contextPath}/logout" method="POST">
<input type="submit" value="로그아웃" />
</form:form>

세션을 날리는 작업
-쿠키 삭제 > 재로그인 : 클라이언트 쪽에 세션id번호를 삭제
-서버에서 세션 날리기 > 재로그인(현재방법) : 서버 쪽에 세션id번호를 삭제하도록 요청하는 것

[ERDCloud]

ERD Cloud 사이트
https://www.erdcloud.com/

참고
https://inpa.tistory.com/entry/ERD-CLOUD-%E2%98%81%EF%B8%8F-ERD-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8%EC%9D%84-%EC%98%A8%EB%9D%BC%EC%9D%B8%EC%97%90%EC%84%9C-%EA%B7%B8%EB%A0%A4%EB%B3%B4%EC%9E%90

논리적 이름(코멘트)
-회원정보
-id
-이메일
-이미지

물리적 이름(DB에 입력됨)
-users
-id
-email
-images

생성된 오라클 문의 ""는 DB입력 시 삭제하여 사용한다.

Identifying Relationship : 1:N (현재사용)
-식별 : primary key가 2개 생김
Non-Identifying Relationship
-비식별 : primary key와 컬럼 생김

1.테이블, 뷰, 컬럼을 비롯한 모든 식별자들은 소문자로 작성하는 것이 좋다.
2.복합어구에는 _를 사용(image_color / boreds_id 등)
3.테이블명은 복수로 해주는 케이스가 많음(boards / orders 등)

0개의 댓글