・스프링 시큐리티(인증과 권한) = 인증과 권한을 위한 솔루션(프레임워크)
시큐리티 커스터 마이징 : 비밀번호를 암호화 시켜주는것 || 사용자별로 인증 및 인가를 적절하게 지정할 수 있음
실행순서
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번호를 삭제하도록 요청하는 것
ERD Cloud 사이트
https://www.erdcloud.com/
논리적 이름(코멘트)
-회원정보
-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 등)