일반고객x, vip만 받음 // DB에 리스트 갖고 있음
・시큐리티에 vip목록을 담을 형식을 시큐리티가 만들어줌
(UserDetails||public class UserDetailsVO implements UserDetails)
・vip목록 전체를 주는 것이 아닌 로그인을 먼저 받아서(request객체)
id를 알려주면 목록에서 검색하여 알려주겠다.(세션공간에 메모리를 올린다|시큐리티가 pw는 알려주지 않음)
(loadUserByUsername||public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException)
Security tag 라이브러리
인증이 완료되면, 이미 로그인 유저의 정보는 세션에 저장되어 있음
Security ContextHolder[SecurityContext[Authentication[Principal=id][Credentials=pw][Authorities]]]
위의 세션 정보를 활용하여, 태그 라이브러리로 제공
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="s"%>
〇〇님 환영합니다 설정
시큐리티에 저장되어 있는 id/pw를 사용
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
[계산 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; } //============================================================================== public String getCart() { return "이것은 장바구니다"; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { // TODO Auto-generated method stub return authorities; } @Override public String getPassword() { // TODO Auto-generated method stub return password; } @Override public String getUsername() { // TODO Auto-generated method stub return username; } // 계정이 만료 되지 않았는가? @Override public boolean isAccountNonExpired() { // TODO Auto-generated method stub return true; } // 계정이 잠기지 않았는가? @Override public boolean isAccountNonLocked() { // TODO Auto-generated method stub return true; } // 패스워드가 만료되지 않았는가? @Override public boolean isCredentialsNonExpired() { // TODO Auto-generated method stub return true; } // 계정이 활성화 되었는가? @Override public boolean isEnabled() { // TODO Auto-generated method stub 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"%> <!-- jsp파일에서 로그인 세션 정보 가져와서 〇〇님 환영합니다 --> <!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> <p>principal: <sec:authentication property="principal"/></p> <!-- property="principal" ⇒ getPrincipal() : principal: edu.global.ex.vo.UserDetailsVO@12022b7b --> <p><sec:authentication property="principal.username"/>님 환영합니다.</p> <p><sec:authentication property="principal.authorities"/></p> <p><sec:authentication property="principal.cart"/></p> <sec:authorize access="hasRole('ADMIN')"> <p>당신은 관리자 입니다.</p> </sec:authorize> <sec:authorize access="hasRole('USER')"> 당신은 일반 유저 입니다. </sec:authorize> </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 java.security.Principal; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import lombok.extern.slf4j.Slf4j; @Slf4j @Controller public class LoginController { @GetMapping("/login") public String login() { log.info("login().."); return "login/login"; } @RequestMapping(value = "/loginInfo", method = RequestMethod.GET) public String loginInfo(Authentication authentication, Principal principal, Model model) { // session에 저장된 시큐리티 객체에서 불러온다.(UserDetails 기반으로 만들었음) //Controller함수에서만 받아올 수 있음 String user_id; // 1.SpringContextHolder를 통하여 가져오는 방법(일반적인 빈에서 사용 할수있음 ) Authentication auth = SecurityContextHolder.getContext().getAuthentication(); user_id = auth.getName(); System.out.println("유저 아이디:" + user_id); // 2.authentication 객체로 가져오는 방법(유저이름, 권한 가져올 수 있음) System.out.println("authentication 유저 아이디:" + authentication.getName()); System.out.println("authentication 권한들:" + authentication.getAuthorities()); // 3.Principal 객체로 가져오는 방법(가져올수 있는게 getName() 정도) System.out.println("Principal 유저 아이디:" + principal.getName()); return "redirect:/"; } } --------------------------------------------------------------------- [계산 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/user/userHome