목차 | |
1. | security |
1. security
■ 이론
□ 실습
▣ pom.xml
- spring-security 5.5.3
- core-5.5.3, web-5.5.3, config-5.5.3
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ezen</groupId>
<artifactId>spring</artifactId>
<name>spring_study_javaClass</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>11</java-version>
<org.springframework-version>5.3.10</org.springframework-version>
<org.aspectj-version>1.6.10</org.aspectj-version>
<org.slf4j-version>2.0.7</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<!-- logback-classic 1.4.5 / logback-core / log4jdbc-log4j2-jdbc4.1-1.16 -->
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.5</version>
<!-- <scope>test</scope> -->
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.4.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bgee.log4jdbc-log4j2/log4jdbc-log4j2-jdbc4.1 -->
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
<version>1.16</version>
</dependency>
<!-- <dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency> -->
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<!-- 여기서부터 추가 dependency -->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
<!-- DB 관련 -->
<!-- mysql-connector-j-8.0.33 / mybatis-3.5.10 / mybatis-spring-2.0.6 -->
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<!-- spring-JDBC-5.3.10 / spring-Test-5.3.10 -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!-- Hikari CP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>6.0.0</version>
</dependency>
<!-- 댓글관련 : json 파싱 역할 -->
<!-- jackson-databind-2.13.0 jackson-dataformat-xml -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-xml -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.13.1</version>
</dependency>
<!-- 파일관련 -->
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.14</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tika/tika-core -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>2.4.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tika/tika-parsers -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parsers</artifactId>
<version>2.4.1</version>
<type>pom</type>
</dependency>
<!-- 스케쥴러 관련 -->
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz-jobs -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.2</version>
</dependency>
<!-- 시큐리티 관련 -->
<!-- spring-security 5.5.3 core, web, config, taglibs -->
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.5.3</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.5.3</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.5.3</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.5.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>11</source>
<target>11</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
<!-- 플러그인 추가 : web.xml이 없어도 class를 통해서 설정정보를 읽을 수 있게 추가 설정-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
▣ SecurityConfig.java
package com.ezen.spring.config;
import org.springframework.context.annotation.Bean;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import com.ezen.spring.security.CustomAuthUserService;
import com.ezen.spring.security.LoginFailureHandler;
import com.ezen.spring.security.LoginSuccessHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
// 비밀번호를 암호화 객체 PasswordEncoder 빈 생성
@Bean
public PasswordEncoder bcPasswordEncoder() {
return new BCryptPasswordEncoder();
}
// SuccessHandler 객체 빈 생성 => 사용자 커스텀 객체
@Bean
public AuthenticationSuccessHandler authSuccessHandler() {
return new LoginSuccessHandler();
}
// FailureHandler 객체 빈 생성 => 사용자 커스텀 객체
@Bean
public AuthenticationFailureHandler authFailureHandler() {
return new LoginFailureHandler();
}
// UserDetail 객체 빈 생성 => 사용자 커스텀 객체
@Bean
public UserDetailsService customDetailService() {
return new CustomAuthUserService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 인증용 객체 생성 매니저 설정
auth.userDetailsService(customDetailService()).passwordEncoder(bcPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 화면에서 설정되는 권한에 따른 주소 맵핑 설정
// 화면에서 오는 로그인 정보 구성
// csrf() : 공격에 대한 설정 풀기
http.csrf().disable();
// 권한 승인 요청
// antMatchers : 접근을 허용하는 경로
// permitAll : 누구나 접근이 가능한 경로
// hasRole('권한') : 해당 권한 확인
// authenticated() : 인증된 사용자만 가능한 경로
// USER, ADMIN, MANAGER
http.authorizeRequests()
.antMatchers("/user/list").hasRole("ADMIN")
.antMatchers("/","/user/login","/user/register","/board/list","/board/detail", "/upload/**","/comment/**","/resources/**").permitAll()
.anyRequest().authenticated();
// 로그인 페이지 구성 : id => email / pw => pwd
// Controller에 주소요청 맵핑은 같이 이어야 함. (필수)
http.formLogin()
.usernameParameter("email")
.passwordParameter("pwd")
.loginPage("/user/login")
// .defaultSuccessUrl("/")
// .failureUrl("/");
.successHandler(authSuccessHandler())
.failureHandler(authFailureHandler());
// 로그아웃 구성 : method = "post" (?)
http.logout()
.logoutUrl("/user/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/");
}
}
▣ SecurityInitalizer.java
package com.ezen.spring.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
// AbstractSecurityWeApplicationIntializer
// 를 상속받아야 시큐리티 관련 필터들이 활성화 됨.
public class SecurityInitalizer extends AbstractSecurityWebApplicationInitializer{
}
LoginSuccessHandler.java
package com.ezen.spring.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Component;
import com.ezen.spring.dao.UserDAO;
import lombok.Getter;
import lombok.Setter;
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private UserDAO udao;
@Getter
@Setter
private String authEmail;
@Getter
@Setter
private String authUrl; // 로그인 성공 후 가는 url
// redirect 데이터를 가지고 리다이렉트 경로로 이동하는 역할
private RedirectStrategy redStr = new DefaultRedirectStrategy();
// 세션의 캐쉬 정보, 경로
private RequestCache reqCache = new HttpSessionRequestCache();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// 1. lastLogin 기록, 로그인 후 가야하는 경로 설정
// authentication => username => getName()
setAuthEmail(authentication.getName());
int isOk = udao.updateLastLogin(getAuthEmail());
setAuthUrl("/board/list");
// 시큐리티 로그인을 시도해서 실패하면 기록이 남게 됨.
// 2. 로그인에 성공하면, 기존에 실패했던 기록을 삭제
// 세션 가져오기
HttpSession ses = request.getSession();
if(isOk == 0 || ses == null) {
return;
}else {
// removeAttribute : 세션의 객체 삭제
ses.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
// 3. 로그인 직전 URL 연결
SavedRequest saveRequest = reqCache.getRequest(request, response);
redStr.sendRedirect(request, response, saveRequest != null ? saveRequest.getRedirectUrl() : getAuthUrl());
}
}
▣ LoginFailureHandler.java
package com.ezen.spring.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Getter
@Setter
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
private String authEmail;
private String errorMessage;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
// BadCredentialException || InternalAuthenticationServiceException
setAuthEmail(request.getParameter("email"));
// exception 발생 시 메세지를 저장
if(exception instanceof BadCredentialsException) {
// setErrorMessage(exception.getMessage().toString());
setErrorMessage("아이디 또는 비밀빈호가 잘못되었습니다.");
}else if(exception instanceof InternalAuthenticationServiceException) {
setErrorMessage("관리자에게 문의해주세요.");
}else {
setErrorMessage("관리자에게 문의해주세요.");
}
log.info(">>> errMsg > {}", getErrorMessage());
request.setAttribute("email", getAuthEmail());
request.setAttribute("errMsg", getErrorMessage());
request.getRequestDispatcher("/user/login?error").forward(request, response);
}
}
▣ CustomAuthUserService.java
package com.ezen.spring.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 com.ezen.spring.dao.UserDAO;
import com.ezen.spring.domain.UserVO;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CustomAuthUserService implements UserDetailsService {
// username, password를 가지고 인증용 토큰 생성 => DB에서 확인
// 리턴은 UserDetails : 인증 객체 리턴
@Autowired
private UserDAO udao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// username : 로그인을 시도하는 email
UserVO uvo = udao.selectEmail(username);
// 아이디가 없거나 잘못 입력되면...
if(uvo == null){
throw new UsernameNotFoundException(username);
}
uvo.setAuthList(udao.selectAuths(username));
log.info(">>> userDetails >> {}", uvo);
return new AuthUser(uvo);
}
}
▣ sqlFile
-- 241029
create table board(
bno bigint auto_increment,
title varchar(500) not null,
writer varchar(500) not null,
content text,
is_del varchar(5) default 'N',
reg_date datetime default now(),
read_count int default 0,
primary key(bno));
-- 241031
create table comment(
cno bigint auto_increment,
bno bigint,
writer varchar(500) not null,
content text,
reg_date datetime default now(),
primary key(cno));
-- 241101
create table file (
uuid varchar(256) not null,
save_dir varchar(256) not null,
file_name varchar(256) not null,
file_type tinyint(1) default 0,
bno bigint,
file_size bigint,
reg_date datetime default now(),
primary key(uuid));
-- 241105
-- user table
create table user(
email varchar(256),
pwd varchar(256),
nickName varchar(256),
reg_date datetime default now(),
lastLogin datetime default now(),
primary key(email));
-- 권한 테이블 (auth)
-- ADMIN + MANAGER + USER
create table auth(
id bigint auto_increment,
email varchar(256) not null,
auth varchar(256) not null,
-- 외래키 지정
primary key(id),
foreign key(email) references user(email));
▣ WebConfig.java
package com.ezen.spring.config;
import javax.servlet.Filter;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer{
@Override
protected Class<?>[] getRootConfigClasses() {
// TODO Auto-generated method stub
return new Class[] {RootConfig.class, SecurityConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
// TODO Auto-generated method stub
return new Class[] {ServletConfiguration.class};
}
@Override
protected String[] getServletMappings() {
// TODO Auto-generated method stub
return new String[] {"/"};
}
// encoding filter 설정
@Override
protected Filter[] getServletFilters() {
// TODO Auto-generated method stub
// CharacterEncodingFilter encoding = new CharacterEncodingFilter("UTF-8", true);
CharacterEncodingFilter encoding = new CharacterEncodingFilter();
encoding.setEncoding("UTF-8");
encoding.setForceEncoding(true); // 외부로 나가는 데이터 인코딩 여부
return new Filter[] {encoding};
}
// 사용자 지정 설정이 필요한 경우 사용. (파일업로드)
@Override
protected void customizeRegistration(Dynamic registration) {
// 파일 업로드 설정 (위치 설정)
String uploadLocation = "D:\\_myProject\\_java\\_fileUpload";
int maxFileSize = 1024*1024*20; // 20MB
int maxReqSize = maxFileSize * 3;
int fileSizeThreshold = maxFileSize;
MultipartConfigElement multipartConfig = new MultipartConfigElement(uploadLocation, maxFileSize, maxReqSize, fileSizeThreshold);
registration.setMultipartConfig(multipartConfig);
}
}
▣ ServletConfiguration.java
package com.ezen.spring.config;
import org.springframework.context.annotation.Bean;
//import javax.servlet.MultipartConfigElement;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@EnableScheduling
@EnableWebMvc
@ComponentScan(basePackages = {"com.ezen.spring.controller","com.ezen.spring.service","com.ezen.spring.handler","com.ezen.spring.security"})
public class ServletConfiguration implements WebMvcConfigurer{
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// resources 경로 설정 (css, js, img, font)
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
// 나중에 파일 업로드 경로도 추가 예정
registry.addResourceHandler("/upload/**").addResourceLocations("file:///D:\\_myProject\\_java\\_fileUpload\\");
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// view를 jsp(jstl 포함)로 어떻게 보여줄지 설정
// view 경로 설정
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
// 화면에 뷰를 구성할 때 jstl에 대한 사용을 하는 jsp를 구성하겠다.
viewResolver.setViewClass(JstlView.class);
registry.viewResolver(viewResolver);
}
// 나중에 파일 업로드 리졸버도 추가 예정.
// 빈 이름이 multipartResolver이어야 에러가 안 남
@Bean(name = "multipartResolver")
public MultipartResolver getMultipartResolver( ) {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
return multipartResolver;
}
}
▣ UserVO.java
package com.ezen.spring.domain;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class UserVO {
private String email;
private String pwd;
private String nickName;
private String regDate;
private String lastLogin;
private List<AuthVO> authList;
}
▣ AuthVO.java
package com.ezen.spring.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class AuthVO {
private long id;
private String email;
private String auth;
}
▣ header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>header.jsp</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</head>
<body>
<!-- <div class="container-md"> -->
<h1>
My Spring Project!!
</h1>
<!-- </div> -->
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#">Spring</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<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="/board/list">게시판 보기</a>
</li>
<sec:authorize access="isAnonymous()">
<li class="nav-item">
<a class="nav-link" href="/user/register">회원가입</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/user/login">로그인</a>
</li>
</sec:authorize>
<sec:authorize access="isAuthenticated()">
<!-- 인증 객체가 만들어져 있는 상태 -->
<!-- 인증된 객체 가져오기 => 현재 로그인 정보는 : principal -->
<sec:authentication property="principal.uvo.email" var="authEmail"/>
<sec:authentication property="principal.uvo.nickName" var="authNick"/>
<li class="nav-item">
<a class="nav-link" href="/board/register">게시판 글쓰기</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">${authNick}(${authEmail})</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/user/logout">로그아웃</a>
</li>
</sec:authorize>
</ul>
</div>
</div>
</nav>
▣ register.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<div class="container-md">
<jsp:include page="../layout/header.jsp"/>
<h1>User Join Page...</h1>
<hr>
<form action="/user/register" method="post">
<div class="mb-3">
<label for="e" class="form-label">email</label>
<input type="text" class="form-control" id="e" name="email" placeholder="email...">
</div>
<div class="mb-3">
<label for="p" class="form-label">password</label>
<input type="text" class="form-control" id="p" name="pwd" placeholder="PassWord...">
</div>
<div class="mb-3">
<label for="n" class="form-label">nickName</label>
<input type="text" class="form-control" id="n" name="nickName" placeholder="nickName...">
</div>
<button type="submit" class="btn btn-primary">JOIN</button>
</form>
</div>
<jsp:include page="../layout/footer.jsp"/>
▣ login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<div class="container-md">
<jsp:include page="../layout/header.jsp"/>
<h1>User Login Page...</h1>
<hr>
<form action="/user/login" method="post">
<div class="mb-3">
<label for="e" class="form-label">email</label>
<input type="text" class="form-control" id="e" name="email" placeholder="email...">
</div>
<div class="mb-3">
<label for="p" class="form-label">password</label>
<input type="text" class="form-control" id="p" name="pwd" placeholder="PassWord...">
</div>
<!-- 로그인 실패시 errMessage 출력 -->
<c:if test="${param.errMsg ne null }">
<div class="text-danger">${param.errMsg }</div>
</c:if>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
<jsp:include page="../layout/footer.jsp"/>
▣ UserController.java
package com.ezen.spring.controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.ezen.spring.domain.UserVO;
import com.ezen.spring.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/user/**")
@Controller
public class UserController {
private final UserService usv;
private final BCryptPasswordEncoder bcEncoder;
// mapping 경로와 jsp의 경로가 같으면 void 처리 가능.
@GetMapping("/register")
public void register() {}
@PostMapping("/register")
public String register(UserVO uvo){
log.info(">>> USer VO >> {}", uvo);
// encode : 암호화
uvo.setPwd(bcEncoder.encode(uvo.getPwd()));
log.info(">>> USer VO >> {}", uvo);
int isOk = usv.register(uvo);
return "redirect:/";
}
@GetMapping("/login")
public void login() {
}
@PostMapping("/login")
public String login(HttpServletRequest request, RedirectAttributes re) {
// 실제 로그인은 Security의 필터에서 갖감
// 로그인 실패시 다시 로그인 페이지로 돌아와 오류 메세지를 전송
// 재 로그인을 유도
log.info(">>> errMsg >> {}", request.getAttribute("errMsg").toString());
re.addAttribute("email",request.getAttribute("email"));
re.addAttribute("errMsg", request.getAttribute("errMsg"));
return "redirect:/user/login";
}
}
▣ UserService.java
package com.ezen.spring.service;
import com.ezen.spring.domain.UserVO;
public interface UserService {
int register(UserVO uvo);
}
▣ UserServiceImpl.java
package com.ezen.spring.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ezen.spring.dao.UserDAO;
import com.ezen.spring.domain.UserVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
@Slf4j
@Service
public class UserServiceImpl implements UserService{
private final UserDAO udao;
@Transactional
@Override
public int register(UserVO uvo) {
// TODO Auto-generated method stub
int isOk = udao.insert(uvo);
return udao.insertAuthInit(uvo.getEmail());
}
}
▣ UserDAO.java
package com.ezen.spring.dao;
import java.util.List;
import com.ezen.spring.domain.AuthVO;
import com.ezen.spring.domain.UserVO;
public interface UserDAO {
int insert(UserVO uvo);
int insertAuthInit(String email);
UserVO selectEmail(String username);
List<AuthVO> selectAuths(String username);
int updateLastLogin(String authEmail);
}
▣ userMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ezen.spring.dao.UserDAO">
<insert id="insert">
insert into user(email, pwd, nickName) values(#{email}, #{pwd}, #{nickName})
</insert>
<!-- 회원가입 후 권한 부여 -->
<insert id="insertAuthInit">
insert into auth(email, auth) values(#{email}, 'ROLE_USER')
</insert>
<select id="selectEmail" resultType="com.ezen.spring.domain.UserVO">
select * from user where email = #{email}
</select>
<select id="selectAuths" resultType="com.ezen.spring.domain.AuthVO">
select * from auth where email = #{email}
</select>
<update id="updateLastLogin">
update user set lastLogin = now()
where email = #{email}
</update>
</mapper>
▣ AuthUser.java
package com.ezen.spring.security;
import java.util.Collection;
import java.util.stream.Collectors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import com.ezen.spring.domain.UserVO;
import lombok.Getter;
@Getter
public class AuthUser extends User {
private static final long serialVersionUID = 1L;
private UserVO uvo;
public AuthUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public AuthUser(UserVO uvo) {
super(uvo.getEmail(), uvo.getPwd(),
uvo.getAuthList().stream()
.map(authVO -> new SimpleGrantedAuthority(authVO.getAuth()))
.collect(Collectors.toList())
);
this.uvo = uvo;
}
}
▷ 출력
'Java > Spring' 카테고리의 다른 글
Spring 기초(login) - AWS 풀스택 과정 72일차 (0) | 2024.11.06 |
---|---|
Spring 기초(scheduler) - AWS 풀스택 과정 70일차 (0) | 2024.11.04 |
Spring 기초(file) - AWS 풀스택 과정 69일차 (0) | 2024.11.01 |
Spring 기초(comment) - AWS 풀스택 과정 68일차 (0) | 2024.10.31 |
Spring 기초(paging, search) - AWS 풀스택 과정 67일차 (0) | 2024.10.30 |