스프링 스케줄러 (Spring Scheduler)
스프링 스케줄러는 스프링 프레임워크에서 작업을 주기적으로 실행하거나 특정 시점에 작업을 예약할 때 사용하는 기능입니다.
주요 기능
- 주기적인 작업 실행: 매일, 매시간, 초 단위 등으로 작업 실행.
- 특정 시간 작업 예약: 지정된 시간이나 조건에 따라 작업 실행.
- 멀티스레드 기반 병렬 처리: 여러 작업을 동시에 처리 가능.
구현 방식
- @Scheduled 어노테이션 사용:
- 메서드 위에 @Scheduled를 붙여 스케줄러를 구현.
- TaskScheduler 또는 Quartz 사용:
- 고급 스케줄링 요구사항이 있는 경우 Quartz와 같은 외부 라이브러리와 통합.
servlet-context.xml
<context:component-scan base-package="edu.springboard.scheduler" />
Namespaces에 들어가 task 체크
스레드 풀 크기는 5로 설정
<task:scheduler id="samplesScheduler" pool-size="5"/><!-- 작업방 생성 -->
<task:annotation-driven scheduler="samplesScheduler"/>
<!-- 컴포넌트에 있는 @Scheduled 어노테이션을 읽어 작업에 추가 -->
<task:scheduler> 설명
- <task:scheduler> 태그는 스케줄러를 정의하고 스레드 풀 크기와 같은 속성을 설정합니다.
- 설정된 스케줄러는 @Scheduled 작업에 사용됩니다.
- XML 네임스페이스 기반 구성으로, Java Config(@Configuration) 대신 XML 파일에서 설정.
- 속성설명
id | 정의된 스케줄러의 ID로, 다른 구성 요소에서 참조할 때 사용됩니다. |
pool-size | 스레드 풀의 크기를 지정합니다. 여러 스케줄 작업이 동시에 실행될 때 사용. |
rejected-task-policy | 스레드 풀이 꽉 찼을 때 태스크 처리 정책을 정의합니다 (선택적). |
스레드 풀 크기 (pool-size):
- 동시에 실행 가능한 작업의 수를 제한합니다.
- 예를 들어, 스레드 풀 크기가 5이면 최대 5개의 작업이 병렬로 실행됩니다.
- 추가 작업은 대기열에 쌓입니다.
태스크 실행 정책:
- rejected-task-policy를 설정하면, 스레드 풀이 바쁠 때 태스크를 처리하는 방법을 지정합니다. (예: ABORT, CALLER_RUNS 등)
package edu.springboard.scheduler;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class SampleScheduler {
@Autowired
SqlSession sqlSession;
//스케쥴러는 dispatcherservlet 내에서 동작하므로 자동 의존 주입 가능
//@Scheduled(fixedRate = 60000) // 60,000ms = 1분
//@Scheduled(fixedDelay = 60000) // 이전 작업 완료 후 1분 후 실행
//초 분 시 일 월 요일
@Scheduled(cron = "0 0/1 * * * ?") // 매 1분마다 실행
public void logScheduler() {
System.out.println("1분마다 호출 중..");
}
}
크론 표현식의 형식
스프링에서 사용하는 크론 표현식은 다음과 같은 6개의 필드로 구성됩니다:
필드 순서필드 이름허용 값설명
1 | 초 | 0-59 | 초 단위 |
2 | 분 | 0-59 | 분 단위 |
3 | 시 | 0-23 | 시간 단위 |
4 | 일 | 1-31 | 해당 월의 날짜 |
5 | 월 | 1-12 또는 JAN-DEC | 월 단위 |
6 | 요일 | 0-6 (일요일=0) 또는 SUN-SAT | 주 단위 |
크론 표현식 특수 문자
크론 표현식에는 특정 조건을 설정하기 위한 특수 문자가 포함됩니다.
문자의미
* | 모든 값 (예: 초/분/시 등 전체 범위) |
? | 특정 값을 설정하지 않음 (일 또는 요일 필드에서 사용) |
- | 범위를 지정 (예: 10-12는 10, 11, 12 포함) |
, | 여러 값을 지정 (예: 1,3,5는 1, 3, 5 포함) |
/ | 간격을 지정 (예: 0/5는 0에서 시작하여 5 단위로 실행) |
L | 마지막 값 (예: 일 필드에서는 월의 마지막 날, 요일 필드에서는 주의 마지막 요일) |
W | 가장 가까운 평일 (예: 15W는 15일에 가장 가까운 평일) |
# | 몇 번째 요일 (예: 3#2는 두 번째 화요일) |
크론 표현식 예제
다음은 크론 표현식의 예제와 실행되는 시간에 대한 설명입니다.
표현식실행 시간
0 * * * * * | 매 분의 시작 (매 분 0초에 실행) |
0 0 * * * * | 매 시간의 시작 (매 정각 실행) |
0 0 12 * * * | 매일 12:00 정오에 실행 |
0 15 10 * * * | 매일 10:15에 실행 |
0 0 0 1 * * | 매월 1일 자정 (00:00)에 실행 |
0 0 0 * * 0 | 매주 일요일 자정 (00:00)에 실행 |
0 0 8-18/2 * * * | 매일 08:00부터 18:00까지 2시간 간격으로 실행 |
0 0 9 ? * MON-FRI | 월요일부터 금요일까지 매일 09:00에 실행 |
0 0 9 15W * * | 매월 15일에서 가장 가까운 평일 09:00에 실행 |
0 0 12 ? * 6L | 매월 마지막 금요일 12:00에 실행 |
스프링 시큐리티 (Spring Security)
스프링 시큐리티는 스프링 기반 애플리케이션에 인증(Authenticate)과 권한(Authorize) 관리를 제공하는 보안 프레임워크입니다.
주요 기능
- 인증(Authentication):
- 사용자가 누구인지 확인하는 과정.
- 로그인, OAuth2, JWT 등 다양한 인증 방식을 지원.
- 인가(Authorization):
- 사용자가 특정 리소스나 기능에 접근할 권한이 있는지 확인.
- 역할(Role) 또는 권한(Authority) 기반으로 접근 제어.
- 보안 기능:
- CSRF(Cross-Site Request Forgery) 방어.
- 세션 관리.
- URL 및 메서드 레벨 보안.
기본 설정
- SecurityConfig 클래스 생성:
- @EnableWebSecurity를 사용해 시큐리티 설정을 커스터마이징.
- HttpSecurity로 URL 접근 제어:
- 특정 경로에 대한 접근을 허용하거나 차단.
스프링 시큐리티를 사용하기 위해 버전업이 필요하여 pom.xml 수정
<properties>
<java-version>1.8</java-version><!-- 수정 -->
<org.springframework-version>4.3.3.RELEASE</org.springframework-version><!-- 수정 -->
<org.aspectj-version>1.6.10</org.aspectj-version>
<org.slf4j-version>1.6.6</org.slf4j-version>
<spring.security.version>3.2.10.RELEASE</spring.security.version><!-- 추가 -->
</properties>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency><!-- 추가 -->
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version><!-- 수정 -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.8</source><!-- 수정 -->
<target>1.8</target><!-- 수정 -->
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
위의 pom.xml을 수정하고 해당 프로젝트를 마우스 우 클릭하고 run as -> maven install을 해준다
install이 완료되면 콘솔창에 BUILD SUCCESS가 나온다
위의 내용 확인 후 다시 프로젝트 마우스 우클릭 후 Maven -> Update project를 해준다
pom.xml에서 수정한 대로 자바 버전이 올라가있는 것을 확인할 수 있다
pom.xml에 밑의 코드들 추가
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring.security.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.security.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.security.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${spring.security.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
db에 접근하기 위한 dependency 추가
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
<!-- Spring-jdbc -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- Spring-test -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- Common-dbcp -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!-- DB MyBatis 연결시 필요 -->
<!-- MyBatis 3.4.1 -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<!-- MyBatis-Spring -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
테이블 추가
enabled 과 authority가 반드시 있어야 한다
authority의 DEFAULT 값은 변경하면 안된다
create table users(
userid VARCHAR(30) NOT NULL PRIMARY KEY,
passwd VARCHAR(300) NOT NULL, --비밀번호 (암호화 처리)
name VARCHAR(30) NOT NULL,
enabled INT DEFAULT '1', --계정 사용 가능 여부(휴면 계정일 경우 0)
authority VARCHAR(30) DEFAULT 'ROLE_USER' --일반사용자/관리자
);
root-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- DB 연결 정보 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<!-- & :: & 기호 -->
<property name="url"
value="jdbc:mysql://localhost:3306/board?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true" />
<property name="username" value="tester" />
<property name="password" value="ezen" />
</bean>
<!-- mybatis 연결 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" /><!-- 위의 db연결 bean을 참조 -->
<property name="configLocation" value="classpath:mybatis_config.xml"/>
<property name="mapperLocations" value="classpath:mappers/**/*Mapper.xml" /><!-- 실행해야 하는 sql들의 모음(매퍼) 위치 -->
</bean>
<!-- 쿼리를 실행하는 객체 -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>
src/main/resources 밑에 mappers 폴더와 mybatis_config.xml 생성
mybatis_config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0/EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- vo 별칭 지정 -->
<typeAliases>
<!-- 별칭 지정시 반드시 클래스부터 선언 후 별칭 지정! -->
</typeAliases>
</configuration>
UserMappler.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="edu.springSecurity.mapper.userMapper">
<!-- namespace : 별칭. 다른 매퍼와 겹치지만 않으면 된다 -->
</mapper>
web.xml
encoding 필터 추가
<!-- encoding -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
security 필터 추가
<!-- security -->
<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>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/root-context.xml
/WEB-INF/spring/security/security-context.xml<!-- 추가 -->
</param-value>
</context-param>
WEB-INF 안의 sping폴더 안에 security 폴더 생성. Spring Bean Configuration File 로 security-context.xml 생성
Namespaces 에서 security 체크
UserLoginSuccessHandler
로그인 성공시 호출
package edu.springSecurity.service;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//로그인 성공시 호출!
System.out.println("로그인 성공!");
response.sendRedirect(request.getContextPath()); //메인페이지 이동
}
}
UserLoginFailureHandler
로그인 실패시 호출
package edu.springSecurity.service;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
public class UserLoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
//로그인 실패시 호출!
System.out.println("로그인 실패!");
response.sendRedirect(request.getContextPath()+"/login.do"); //다시 로그인페이지
}
}
UserDeniedHandler
로그인은 했지만 권한이 없을 경우 호출
package edu.springSecurity.service;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
public class UserDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
//authority 값이 일치하지 않는 경로(권한이 없는 경로) 접근시 호출
System.out.println("권한 없음");
response.sendRedirect(request.getContextPath()); //메인페이지 이동
}
}
UserVO
해당 VO 객체는 security에서 로그인 또는 권한 인증시에만 사용하는 객체 타입으로서 별도의 기능을 목적으로는 사용할 수 없다. 필요시 타 VO를 추가하여 사용할 것을 권장한다
package edu.springSecurity.vo;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
/*
해당 VO 객체는 security에서 로그인 또는 권한 인증시에만 사용하는 객체 타입으로서
별도의 기능을 목적으로는 사용할 수 없다
필요시 타 VO를 추가하여 사용할 것을 권장한다
*/
public class UserVO extends User{
private String userid;
private String authority;
private boolean enabled;
public UserVO(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities,
String authority) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.userid = username;
this.authority = authority;
this.enabled = enabled;
}
public String getUserid() { return userid; }
public String getAuthority() { return authority; }
public boolean isEnabled() { return enabled; }
}
UserMapper.xml
기본생성자가 있어야만 resultType에 vo를 넣을 수 있어 resultType을 Map으로 한다
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="edu.springSecurity.mapper.userMapper">
<!-- namespace : 별칭. 다른 매퍼와 겹치지만 않으면 된다 -->
<select id="selectOneById" parameterType="String" resultType="java.util.Map">
SELECT userid
,passwd
,enabled
,authority
FROM users
WHERE userid = #{userid}
</select>
</mapper>
UserAuthenticationService
아이디가 일치하는 유저 정보를 가져옴
package edu.springSecurity.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import edu.springSecurity.vo.UserVO;
public class UserAuthenticationService implements UserDetailsService {
//컴포넌트가 아니기 때문에 @Autowired 불가능
SqlSession sqlSession;
public UserAuthenticationService(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
//UserDetails -> User -> UserVO
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//String username == 사용자 아이디
Map<String,Object> user
= sqlSession.selectOne("edu.springSecurity.mapper.userMapper.selectOneById", username);
System.out.println("Map userid : "+ (String)user.get("userid"));
int enabled_map = (Integer)user.get("enabled");
boolean enabled = false;
if(enabled_map == 1) {
enabled = true;
}else {
enabled = false;
}
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority((String)user.get("authority")));
UserVO vo = new UserVO(
(String)user.get("userid")
,(String)user.get("passwd")
,enabled
,true
,true
,true
,authorities
,(String)user.get("authority")
);
return vo;
}
}
밑의 코드는 enabled를 boolean으로 수정하는 코드이다.
int enabled_map = (Integer)user.get("enabled");
boolean enabled = false;
if(enabled_map == 1) {
enabled = true;
}else {
enabled = false;
}
밑의 코드로 수정할 수 있다
boolean enabled;
if( String.valueOf(user.get("enabled")).equals("1") ) {
enabled = true;
}else {
enabled = false;
}
로그인을 하기 위해 간단하게 회원가입 진행
<insert id="insert" parameterType="java.util.Map">
INSERT INTO users(userid, passwd, name)
VALUES (#{userid},#{passwd},#{name});
</insert>
@RequestMapping(value="/joinOk.do", method = RequestMethod.POST)
public String joinOk(String userid,String passwd,String name) {
//BCryptPasswordEncoder 복호화가 불가능
//암호화 진행, 두값이 같은 값인지 비교
BCryptPasswordEncoder epwe = new BCryptPasswordEncoder();
Map<String,String> user = new HashMap<>();
user.put("userid", userid);
user.put("passwd", epwe.encode(passwd)); //비밀번호 암호화
user.put("name", name);
int result = sqlSession.insert("edu.springSecurity.mapper.userMapper.insert", user);
if(result > 0) {
System.out.println("회원가입성공");
}else {
System.out.println("회원가입실패");
}
return "redirect:/";
}
BCrypt는 암호화 해시 알고리즘으로, 비밀번호를 암호화한 후 복호화할 수 없으며, 매번 다른 해시 값을 생성하는 특성이 있습니다. 이를 통해 동일한 비밀번호라도 서로 다른 해시 값이 생성되므로 보안성이 높습니다.
빈 생성 BCryptPasswordEncoder는 기본적으로 strength라는 인자를 받을 수 있습니다. strength는 해시를 생성할 때 얼마나 많은 계산을 할지 결정하는 값으로, 값이 클수록 보안성이 높아지지만 성능에 영향을 미칩니다. 기본 값은 10입니다.
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
비밀번호 암호화 사용자의 비밀번호를 암호화할 때는 encode() 메서드를 사용합니다.
String rawPassword = "myPassword";
String encodedPassword = encoder.encode(rawPassword);
System.out.println(encodedPassword);
비밀번호 일치 확인 사용자가 입력한 비밀번호가 저장된 암호화된 비밀번호와 일치하는지 확인하려면 matches() 메서드를 사용합니다.
String rawPassword = "myPassword";
String encodedPassword = "$2a$10$0RtHgB8yV8U6Z1Dl6A6J5ehSwaeSKVs6tvLSI7lmbkdb4fMoeNOqu"; // 예시로 제공된 암호화된 비밀번호
boolean isMatch = encoder.matches(rawPassword, encodedPassword);
System.out.println(isMatch); // 비밀번호가 일치하면 true, 아니면 false
security-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<!-- 제외 url pattern -->
<security:http pattern="/resources/**" security="none" />
<security:http auto-config="true" use-expressions="true" create-session="never">
<security:intercept-url pattern="/" access="permitAll" />
<security:intercept-url pattern="/user/**" access="hasAnyRole('ROLE_USER,ROLE_ADMIN')" />
<security:intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" />
<security:form-login login-page="/login.do"
login-processing-url="/loginOk.do"
authentication-success-handler-ref="userLoginSuccessHandler"
authentication-failure-handler-ref="userLoginFailureHandler"
username-parameter="userid"
password-parameter="passwd"/>
<!-- 로그아웃 설정 -->
<security:logout logout-url="/logout.do"
logout-success-url="/login.do"
invalidate-session="true"
delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE"/>
</security:http>
<bean id="userLoginSuccessHandler" class="edu.springSecurity.service.UserLoginSuccessHandler" />
<bean id="userLoginFailureHandler" class="edu.springSecurity.service.UserLoginFailureHandler" />
<bean id="userDeniedHandler" class="edu.springSecurity.service.UserDeniedHandler" />
<bean id="userService" class="edu.springSecurity.service.UserAuthenticationService">
<constructor-arg name="sqlSession" ref="sqlSession" /> <!-- 생성자 매개변수이름 name -->
</bean>
<!-- 비밀번호 비교할 암호화 객체 선언 -->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<!--
로그인 버튼 클릭시 security에서 어떤 userAuthenticationService를
사용할 지와 반환된 User 객체에서 어떤 비밀번호 암호화를 사용하여 비교할 것인지에 대한 정보를 설정 영역
-->
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService"><!-- userVO 반환 객체 참조 -->
<security:password-encoder ref="passwordEncoder" />
</security:authentication-provider>
</security:authentication-manager>
</beans>
인증 및 권한 설정
- auto-config="true": 기본 설정을 활성화합니다.
- use-expressions="true": SpEL(스프링 표현 언어)을 사용하여 권한을 관리합니다.
- create-session="never": Security가 세션을 생성하지 않도록 설정합니다.
경로별 접근 제어
- /: 모든 사용자 접근 허용 (permitAll)
- /user/**: 사용자와 관리자 역할만 접근 가능 (hasAnyRole('ROLE_USER', 'ROLE_ADMIN'))
- /admin/**: 관리자만 접근 가능 (hasRole('ROLE_ADMIN'))
로그인 설정
- login-page: 사용자 정의 로그인 페이지 경로 설정
- login-processing-url: 로그인 인증 요청 처리 URL
- authentication-success-handler-ref: 로그인 성공 시 실행될 핸들러
- authentication-failure-handler-ref: 로그인 실패 시 실행될 핸들러
- username-parameter: 로그인 폼에서 사용자 ID 필드 이름
- password-parameter: 로그인 폼에서 비밀번호 필드 이름
로그아웃 설정
- logout-url: 로그아웃 요청 경로
- logout-success-url: 로그아웃 성공 후 이동할 경로
- invalidate-session: 세션 무효화 설정
- delete-cookies: 쿠키 삭제 설정 (예: JSESSIONID)
인증 관리자 및 암호화 연동
- user-service-ref: userService를 통해 인증 처리
- password-encoder: BCryptPasswordEncoder를 통해 비밀번호 암호화 및 비교
세션과 쿠키의 차이
세션
- 서버에서 사용자별 데이터를 관리.
- 서버 자원을 소비하며, 사용자당 고유 세션이 생성됨.
쿠키
- 클라이언트(PC)에 데이터를 저장.
- 사용자의 브라우저에 저장되며, 서버로 전송되어 인증 및 데이터를 확인.
1. 저장 위치
구분세션(Session)쿠키(Cookie)
저장 위치 | 서버(Server) | 클라이언트(Client) (브라우저) |
설명 | 세션 데이터는 서버의 메모리 또는 데이터베이스에 저장됩니다. | 쿠키 데이터는 사용자의 브라우저에 저장됩니다. |
2. 데이터의 보안성
구분세션(Session)쿠키(Cookie)
보안 | - 서버에 저장되므로 상대적으로 안전합니다. - 사용자가 직접 데이터를 수정할 수 없습니다. | - 브라우저에 저장되므로 변조 가능성이 있습니다. - 중요한 정보를 저장하면 안 됩니다. (예: 암호, 인증 정보) |
권장 사용 | 보안이 중요한 정보 (예: 로그인 상태, 권한 정보) 관리 | 사용자의 선호 정보나 세션 ID와 같은 비민감 데이터 관리 |
3. 동작 방식
세션
- 클라이언트가 서버에 처음 요청하면, 서버는 세션을 생성하고 고유 ID(JSESSIONID)를 발급합니다.
- 클라이언트는 이 ID를 쿠키에 저장하고, 이후 요청마다 서버로 ID를 전달합니다.
- 서버는 전달받은 ID를 사용해 해당 클라이언트의 세션 데이터를 확인합니다.
쿠키
- 클라이언트가 서버에 데이터를 요청하면, 서버는 쿠키에 저장할 데이터를 클라이언트에게 전달합니다.
- 이후 클라이언트는 쿠키를 브라우저에 저장하고, 해당 도메인에 다시 요청할 때마다 쿠키를 함께 보냅니다.
- 서버는 이 데이터를 활용해 요청을 처리합니다.
4. 유효 기간
구분세션(Session)쿠키(Cookie)
유효 기간 | - 브라우저가 종료되면 기본적으로 삭제됩니다. - 설정에 따라 서버에서 특정 시간 동안 유지 가능 (예: 30분) | - 만료 시간을 설정할 수 있습니다. - 만료 시간이 설정되지 않으면 브라우저 종료 시 삭제됩니다. |
5. 서버 자원 소비
구분세션(Session)쿠키(Cookie)
자원 소비 | 서버의 메모리나 디스크를 사용하여 데이터를 저장합니다. | 클라이언트의 저장 공간을 사용하므로 서버 자원을 소비하지 않습니다. |
6. 사용 예시
구분세션(Session)쿠키(Cookie)
사용 예시 | - 로그인 상태 관리 - 장바구니 정보 (대용량일 경우 DB 사용) | - "아이디 저장" 기능 - 사용자 설정(테마, 언어 등) |
7. 장단점 비교
구분장점단점
세션(Session) | - 보안성이 높음 - 서버에서 데이터 관리로 신뢰 가능 | - 서버 자원 소모 - 확장성이 낮아질 수 있음 |
쿠키(Cookie) | - 클라이언트 자원 사용 - 서버 부하 감소 | - 보안 취약성 (데이터 노출 및 변조 위험) |
요약
- 세션: 서버에서 관리하며, 보안이 중요한 데이터(예: 로그인 상태) 처리에 적합합니다.
- 쿠키: 클라이언트에서 관리하며, 단순한 데이터(예: 사용자 설정 정보) 처리에 적합합니다.
home.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" %>
<html>
<head>
<title>Home</title>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="<%= request.getContextPath() %>/resources/css/spring.css" />
<style>
div a:hover{
text-decoration: underline;
text-underline-position: under;
text-underline-offset: 0;
text-decoration-thickness: 2px;
text-decoration-color: #767676;
}
</style>
</head>
<body>
<h1>Security 홈페이지</h1>
<sec:authorize access="isAnonymous()"><!-- 로그인 X -->
<div style="font-size:18px; text-decoration: none; color:black; font-weight: bold;">
<a href="join.do">회원가입</a> |
<a href="login.do">로그인</a><br>
</div>
</sec:authorize>
<sec:authorize access="isAuthenticated()"><!-- 로그인 O -->
<!-- 로그인 안된 상태에서 sec:authentication 사용시 오류 발생 -->
<sec:authentication property="principal.username"/>
<sec:authentication property="principal.authority"/>
<div style="font-size:18px; text-decoration: none; color:black; font-weight: bold;">
<a href="logout.do">로그아웃</a><br>
</div>
</sec:authorize>
<sec:authorize access="hasAnyRole('ROLE_USER','ROLE_ADMIN')">
일반유저
</sec:authorize>
</body>
</html>
'Spring' 카테고리의 다른 글
[Spring] 필터(filter) (0) | 2024.12.03 |
---|---|
[Spring] 인터셉터(interceptor) (0) | 2024.12.02 |
[Spring] 파일 업로드 (0) | 2024.12.01 |
[Spring] AJAX 여러건의 데이터 받기 (0) | 2024.11.30 |
[Spring] AJAX 한 건의 데이터 받기 (1) | 2024.11.29 |