sql
CREATE TABLE member (
email VARCHAR(100) NOT NULL,
pwd VARCHAR(1000) NOT NULL,
nick_name VARCHAR(100) NOT NULL,
reg_at DATETIME DEFAULT now(),
last_login DATETIME DEFAULT NULL,
PRIMARY KEY(email)
);
CREATE TABLE auth_member(
email VARCHAR(100) NOT NULL,
auth VARCHAR(50) NOT NULL
);
ALTER TABLE auth_member
ADD CONSTRAINT fk_auth
FOREIGN KEY (email)
REFERENCES member(email);
pom.xml
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.5.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.5.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.5.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-taglibs -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.5.3</version>
</dependency>
AuthVO.java
package com.basicWeb.www.security;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Setter
@Getter
public class AuthVO {
private String email;
private String auth;
}
MemeberVO.java
package com.basicWeb.www.security;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Setter
@Getter
public class MemberVO {
private String email;
private String pwd;
private String nickName;
private String regAt;
private String lastLogin;
private List<AuthVO> authList;
}
AuthMember.java
package com.basicWeb.www.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;
public class AuthMember extends User {
private static final long serialVersionUID = 1L;
private MemberVO mvo;
public AuthMember(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public AuthMember(MemberVO mvo) {
super(mvo.getEmail(), mvo.getPwd(),
mvo.getAuthList().stream()
.map(AuthVO -> new SimpleGrantedAuthority(AuthVO.getAuth()))
.collect(Collectors.toList()));
this.mvo = mvo;
}
}
로그인한 사용자의 정보를 가져오기 위해서 serialVersionUID를 사용해 클래스의 객체를 직렬화 한다.
로그인 세션에 있는 사용자 정보 객체를 직렬화하여 세션 상태를 유지하거나,
분산 시스템에서는 여러 서버 간에 사용자 정보를 전송할 때 직렬화가 사용될 수 있다.
Spring Security에서는 로그인한 사용자 정보를 SecurityContext라는 객체에 저장하고,
이 객체를 직렬화하여 세션에 저장하거나 전송한다.
private static final long serialVersionUID = 1L;
첫 번째 생성자 : User 클래스의 생성자를 호출하는 생성자
사용자의 이름(username), 비밀번호(password), 권한(authorities) 정보를 받아 초기화한다.
public AuthMember(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
두 번째 생성자 : MemberVO 인스턴스를 받아와서 사용자 정보를 초기화하는 생성자
사용자의 이메일, 비밀번호, 권한 정보를 MemberVO에서 가져와 초기화
public AuthMember(MemberVO mvo) {
super(mvo.getEmail(), mvo.getPwd(),
mvo.getAuthList().stream()
.map(authVO -> new SimpleGrantedAuthority(authVO.getAuth()))
.collect(Collectors.toList()));
this.mvo = mvo;
}
CustomAuthMemberService.java
package com.basicWeb.www.security;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
public class CustomAuthMemberService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO Auto-generated method stub
return null;
}
}
사용자의 이름을 받아와서 해당 사용자의 정보를 불러오는 Method
아직 controller ~ mapper를 생성하지 않아서 추후에 코드를 수정할 예정이다.
SecurityInitalizer.java
package com.basicWeb.www.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityInitalizer extends AbstractSecurityWebApplicationInitializer{
}
안전한 비밀번호를 만들기 위한 규칙을 정하는 것과 같이 보안 설정 초기화에 사용
LoginFailureHandler.java
package com.basicWeb.www.security;
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 LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
// TODO Auto-generated method stub
}
}
로그인 인증에 실패했을 때 실행될 동작을 정의
LoginSuccessHandler.java
package com.basicWeb.www.security;
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 LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
}
}
로그인 인증에 성공했을 때 실행될 동작을 정의
SecurityConfig.java
package com.basicWeb.www.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.basicWeb.www.security.CustomAuthMemberService;
import com.basicWeb.www.security.LoginFailureHandler;
import com.basicWeb.www.security.LoginSuccessHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder bcPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationSuccessHandler authSuccessHandler() {
return new LoginSuccessHandler();
}
@Bean
public AuthenticationFailureHandler authFailureHandler() {
return new LoginFailureHandler();
}
@Bean
protected UserDetailsService customUserService() {
// TODO Auto-generated method stub
return new CustomAuthMemberService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 인증되는 객체 설정
auth.userDetailsService(customUserService())
.passwordEncoder(bcPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 화면에서 설정되는 권한에 따른 주소 맵핑 설정
// csrf() 공격에 대한 설정 막기
http.csrf().disable();
// 승인 요청
// antMatchers : 접근을 허용하는 값(경로)
// antMatchers("/member/list", "/결제요청", "/상품요청..")
// permitAll() : 누구나 접근 가능한 경로
// authenticated() : 인증된 사용자만 가능
http.authorizeRequests()
.antMatchers("/member/list").hasRole("ADMIN")
.antMatchers("/", "/board/list", "/board/detail", "/comment/**", "/upload/**", "/resources/**", "/member/register", "/member/login").permitAll()
.anyRequest().authenticated();
// 커스텀 로그인 페이지를 구성
// Controller에 주소요청 맵ㅁ핑이 같이 있어야 함. (필수)
http.formLogin()
.usernameParameter("email")
.passwordParameter("pwd")
.loginPage("/member/login")
.successHandler(authSuccessHandler())
.failureHandler(authFailureHandler());
// 로그아웃 페이지
// 반드시 method = "post"
http.logout()
.logoutUrl("/member/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/");
}
}
SecurityConfig의 어노테이션(@)
@Configuration | 이 클래스가 Spring의 설정 클래스임을 나타냄 |
@EnableWebSecurity | Spring Security를 활성화하고 웹 보안 설정을 사용하도록 함 |
Security Config의 Method
bcPasswordEncoder() | BCrypt 해시 알고리즘을 사용한 비밀번호 Encoder를 Bean으로 등록 |
authSuccessHandler() | 로그인 성공 시 처리할 Handler(동작)를 Bean으로 등록 |
authFailureHandler() | 로그인 실패 시 처리할 Handler(동작)를 Bean으로 등록 |
customUserService() | 사용자 정의 UserDetailsService Bean을 등록 |
configure(AuthenticationManagerBuilder auth) Method
customUserService()를 사용하여 사용자 정보를 가져오고,
bcPasswordEncoder()로 비밀번호를 검증하여 사용자를 인증하는 방법을 설정
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService())
.passwordEncoder(bcPasswordEncoder());
}
configure(HttpSecurity http) Method
HTTP 요청에 대한 보안 설정을 구성
http.csrf().disable() | CSRF(Cross-Site Request Forgery) 공격에 대한 설정을 비활성화 |
http.authorizeRequests() | 경로별 권한 설정 |
http.formLogin() | 로그인 관련 설정 |
http.logout() | 로그아웃 설정 |
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/member/list").hasRole("ADMIN")
.antMatchers("/", "/board/list", "/board/detail", "/comment/**", "/upload/**", "/resources/**", "/member/register", "/member/login").permitAll()
.anyRequest().authenticated();
http.formLogin()
.usernameParameter("email")
.passwordParameter("pwd")
.loginPage("/member/login")
.successHandler(authSuccessHandler())
.failureHandler(authFailureHandler());
http.logout()
.logoutUrl("/member/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/");
}
/member/list 경로는 "ADMIN" 역할을 가진 사용자만 접근할 수 있도록 설정
.antMatchers("/member/list").hasRole("ADMIN")
deleteCookies("JSESSIONID")는 로그아웃 시 JSESSIONID 쿠키를 삭제
만약 로그아웃 후에도 세션 식별자를 가진 상태로 남아있다면,
해당 사용자의 세션은 종료되지 않은 채로 남아있게 되어 보안상 문제가 발생한다.
.deleteCookies("JSESSIONID")
WebConfig.java
package com.basicWeb.www.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};
}
// ... (기존 코드)
}
[Spring] 23. Security 설정 (customAuthMemberServic 제외)
(다음 게시물 예고편)
[Spring] 24. 회원 기초 MVC 구성
얼렁뚱땅 주니어 개발자
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!