[Spring Security] Spring Security의 기본 구조, 동작 방식 (1) - InMemory User
Spring Security
흐름 적용을 위해
CSR 방식이 아닌 SSR 방식을 이용함
각각의 화면에 맞는 html 파일이 있다! (타임리프 방식)
화면 설명 ⬇
커피 보기 화면 - 모든 사용자 접근 가능
전체 주문 목록 보기 화면 - admin only
마이페이지 화면 - 일반사용자(user) only
Spring Security 적용하기
1. 의존 라이브러리 추가 : spring-boot-starter-security
2. spring security 디폴트 로그인 페이지로 이동
username : user
password : 로그에 출력됨
디폴트 페이지가 아닌 만들어둔 html 파일로 로그인하고 싶다?
➡ Spring Security Configuration 적용
: 원하는 인증방식, 웹 페이지 접근 권한 설정 가능함
import org.springframework.context.annotation.Configuration;
@Configuration
public class SecurityConfiguration {
// 여기에 Spring Security의 설정을 진행합니다.
}
애플리케이션의 사용자 인증을 위한
두 가지 방법을 배움. 이 글에서는 1)만 다룬다!
1) InMemory User 로 인증하기
2) DB 연동을 통한 User로 인증하기
1) InMemory User 로 인증하기 (테스트 환경, 데모 환경에 유용)
: UserDetail을 직접 코드 안에 박아 넣음! (객체를 @Bean 으로 등록)
...
@Bean
public UserDetailsManager userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("hello@gmail.com")
.password("1111")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
...
: InMemoryUserDetailManager 구현체 사용
* UserDetailmanager - 사용자의 핵심 정보를 포함한 UserDetails 관리
* withDefaultPasswordEncoder()
: 패스워드를 암호화
⬇
HTTP 보안 구성 기본
: SecurityFilterChain, HttpSecurity
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// HttpSecurity를 통해 HTTP 요청에 대한 보안 설정을 구성한다.
// SecurityFilterChain 을 Bean 으로 등록
...
}
⬇
커스텀 로그인 페이지 지정하기 ➡ SecurityFilterChain 안에서 작성됨
request URI 에 접근 권한 부여(role) ➡ SecurityFilterChain 안에서 작성됨
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable() //csrf 공격 설정 비활성화
.formLogin() // 폼 로그인 방식
.loginPage("/auths/login-form") // html 파일
.loginProcessingUrl("/process_login") // 요청 URL
.failureUrl("/auths/login-form?error") // 실패 URL
.and() // 체인 형태
.authorizeHttpRequests() // 클라이언트 요청 - 접근 권한 확인
.anyRequest()
.permitAll(); //모든 요청에 접근 허용
return http.build();
}
⬇ ⬇ ⬇ ⬇ ⬇
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
... (동일)
...
// 권한 없는 사용자가 접근할경우 나오는 html (403)
.exceptionHandling().accessDeniedPage("/auths/access-denied")
.and()
// request URI 접근권한 부여
.authorizeHttpRequests(authorize -> authorize
// ADMIN - 모든 orders 하위 URL 접속 가능
.antMatchers("/orders/**").hasRole("ADMIN")
// USER - ROLE 부여받은자만 my-page 접속 가능
.antMatchers("/members/my-page").hasRole("USER")
// 모든 URL - ROLE에 상관없이 접속 가능
.antMatchers("⁄**").permitAll()
*andMatchers 순서 유의할 것!
);
return http.build();
}
⬇
InMemory User에 새로운 User 등록하기 (회원가입)
🤔 순서
- PasswordEncoder Bean 등록
- MemberService Bean 등록을 위한 JavaConfiguration 구성
- InMemoryMemberService 클래스 구현
1. PasswordEncoder Bean 등록
* PasswordEncoder : Spring Security 에서 제공하는 패스워드 암호화 기능 제공 컴포넌트
(default = bcrypt)
회원가입 폼 ➡ 애플리케이션 으로 비번 전달시
암호화 된 비번이 아니라 plain text (비번 그대로) 임!
InMemory User 로 등록하기 전에 비밀번호는 암호화 되어야 한다.
@Configuration
public class SecurityConfiguration {
...
...
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
//DelegatingPasswordEncoder - 실질적으로 PasswordEncoder 구현 객체 생성
}
⬇
2. MemberService Bean 등록을 위한 JavaConfiguration 구성
1) MemberService 인터페이스 생성 (편의상 class 아닌 interface)
: createMember()
2) MemberService 인터페이스의 구현체 InMemoryMemberService 클래스 생성
JavaConfiguration ⬇
@Configuration
public class JavaConfiguration {
//InMemoryMemberService를 Bean 으로 등록
@Bean
public MemberService inMemoryMemberService(UserDetailsManager userDetailsManager,
PasswordEncoder passwordEncoder) {
return new InMemoryMemberService(userDetailsManager, passwordEncoder);
}
// ⬆ DI
// UserDetailsManager - db 연동 없이 메모리에 user 등록
// PasswordEncoder - user등록시 패스워드 암호화
}
⬇
3) InMemoryMemberService 클래스 구현
public class InMemoryMemberService implements MemberService {
private final UserDetailsManager userDetailsManager;
private final PasswordEncoder passwordEncoder;
// DI
public InMemoryMemberService(UserDetailsManager userDetailsManager, PasswordEncoder passwordEncoder) {
this.userDetailsManager = userDetailsManager;
this.passwordEncoder = passwordEncoder;
}
public Member createMember(Member member) {
// User의 권한 목록 list 생성
// ROLE_ + 권한명 형태
List<GrantedAuthority> authorities = createAuthorities(Member.MemberRole.ROLE_USER.name());
// 유저 패스워드 암호화
String encryptedPassword = passwordEncoder.encode(member.getPassword());
// UserDertails 생성
UserDetails userDetails = new User(member.getEmail(), encryptedPassword, authorities);
// User 등록
userDetailsManager.createUser(userDetails);
return member;
}
private List<GrantedAuthority> createAuthorities(String... roles) {
// SimpleGrantedAuthority 객체 생성 ➡ List 형태로 리턴
return Arrays.stream(roles)
.map(role -> new SimpleGrantedAuthority(role))
.collect(Collectors.toList());
}
}