Security 로그인
SecurityConfig
package com.example.security1.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터(SecurityConfig)가 스프링 필터체인에 등록이 된다.
public class SecurityConfig {
@Bean //해당 메서드의 리턴되는 오브젝트를 IoC로 등록해 준다.
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeHttpRequests()
.requestMatchers("/user/**").authenticated() //인증만 되면 들어갈 수 있는 주소
.requestMatchers("/manager/**").hasAnyRole("ADMIN", "MANAGER")
.requestMatchers("/admin/**").hasAnyRole("ADMIN")
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/loginForm")
//.usernameParameter("username2") -> html의 login받는 form에서 username을 username2로 바꾸면 PrincipalDetailsService의 username과 매칭이 되지 않는다.
//그럴때 usernameParameter() 를 이용하여 바꾸어 주면 된다.
.loginProcessingUrl("/login")
/*
loginProcessingUrl() :
"/login"이라는 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해준다.
=> "/login"을 만들지 않아도 된다. security가 대신 해주기 때문에
*/
.defaultSuccessUrl("/"); //로그인이 완료되면 default인 메인페이지로 가게된다. 또한 특정페이지에서 로그인을 하면 그 특정페이지로 이동한다.
return http.build();
}
}
authenticated() | 인증만 되면 들어갈 수 있는 주소 |
usernameParameter() | html의 login을 받는 form에서 username을 다른 변수로 바꾸면 PrincipalDetailsService 의 username과 매칭이 되지 않는다. 그럴때 usernameParameter()를 이용하여 바꿀 수 있다. |
loginProcessingUrl("/login") | "login"이라는 주소가 호출 되면 시큐리티가 낚아채서 대신 로그인을 진행한다. 따라서 컨트롤러에서 "/login"을 만들지 않아도 된다. |
defaultSuccessUrl() | 로그인이 완료되면 default로 설정한 페이지로 가게된다. 또한 특정페이지에서 로그인을 하면 그 특정 페이지로 이동한다. |
UserRepository
package com.example.security1.repository;
import com.example.security1.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
//@Repository라는 어노테이션이 없어도 IoC가 된다. JpaRepository를 상속했기 때문
public interface UserRepository extends JpaRepository<User, Long> {
// findBy규칙 -> Username 문법
//SELECT * FROM USER WHERE USERNAME = 1? 가 호출된다.
public User findByUsername(String username); //Jpa Query methods
}
PrincipalDetails
package com.example.security1.auth;
//시큐리티가 /login 주소 요청이 오면 낚아채서 로그인을 진행시킨다.
//로그인 진행이 완료되면 시큐리티 session을 만들어준다. (Security ContextHolder)
//오브젝트 타입 => Authentication 타입 객체
//Authentication 안에 User정보가 있어야 됨.
//User오브젝트타입 => UserDetails 타입 객체
import com.example.security1.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
//Security Session 영역 => Authentication => UserDetails
public class PrincipalDetails implements UserDetails {
/*
UserDetails를 implements 받아 PrincipalDetails는 Authentication안의 UserDetails 처럼
이용할 수 있게 되었다.
PrincipalDetails의 정보를 Authentication안에 넣을 수 있게 되었다.
*/
private User user; //컴포지션
public PrincipalDetails(User user) {
this.user = user;
}
//해당 User의 권한을 리턴하는 역할
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collect;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
//우리사이트에서 1년동안 회원이 로그인을 안하면 휴먼 계정으로 전환하기로 함.
//현재시간 - 로그인시간 => 1년을 초과하면 return을 false 한다 등으로 설정 가능
return true;
}
}
시큐리티가 login 주소 요청이 오면 낚아채서 로그인을 진행시킨다.
-> 로그인 진행이 완료되면 시큐리티가 session을 만들어준다. (SecurityContextHolder)
오브젝트 타입 => Authentication 타입 객체
Authentication안에는 User정보가 있어야 한다.
User 오브젝트 타입 => UserDetails 타입 객체
SecuritySession 영역 내부에는
Authentication이 있고
그 안에는 UserDetails가 있다.
UserDetails를 implements 받아 PrincipalDetails는 Authentication안의
UserDetails처럼 이용할 수 있게 된다.
따라서 PrincipalDetails의 정보를 Authentication안에 넣을 수 있게 되었다.
isAccountNonExpired()
@Override
public boolean isAccountNonExpired() {
return true;
}
사용자 계정의 만료 여부를 나타내는 boolean값을 반환한다.
계정이 만료되지 않았을 경우 "true"를 반환하고, 만료되었을 경우 "false"를 반환해야 한다.
ex)
계정이 특정 날짜를 지나면 자동으로 만료되도록 설정할 수 있다.
위의 코드는 "true"를 반환하므로 해당 사용자의 계정은 만료되지 않은
계정으로 간주된다.
isAccountNonLocked()
@Override
public boolean isAccountNonLocked() {
return true;
}
사용자 계정의 잠김 상태를 나타내는 boolean값을 반환한다.
계정이 잠기지 않을 경우 "true", 잠긴 경우 "false"를 반환해야 한다.
ex)
계정이 일정 횟수의 로그인 실패로 잠금 되는 정책이 있다면
해당 로직을 이 메서드에서 구현하면 된다.
위의 코드는 "true"를 반환하고 있으므로, 해당 사용자 계정은 잠긴 상태가 아닌
계정으로 간주된다.
isCredentialsNonExpired()
@Override
public boolean isCredentialsNonExpired() {
return true;
}
사용자의 인증 자격 즉, 비밀번호의 만료 여부를 나타내는 boolean 값을 반환한다.
비밀번호의 만료 여부는 보안을 위해 주기적으로 비밀번호를 변경해야 하는지를
판단하는 데 사용된다.
ex)
비밀번호가 일정 기간 후에 만료되어야 하는 정책이 있다면, 해당 로직을
이 메서드에서 구현하면 된다.
위의 코드는 "true"를 반환하고 있으므로, 해당 사용자 계정의 비밀번호는 만료되지 않은
상태로 간주된다.
isEnabled()
@Override
public boolean isEnabled() {
return true;
}
사용자 계정이 활성화되어 있는지 여부를 나타내는 boolean 값을 반환한다.
활성화된 계정은 인증과 권한 부여에 참여할 수 있으며,
비활성화된 계정은 인증과 권한 부여에 참여할 수 없다.
ex)
사용자 계정의 활성화 상태를 DB에 저장하고, 해당 계정이 활성화되어야만
인증 및 권한이 유효하도록 설정할 수 있다.
위의 코드에서는 항상"true"를 반환 하고 있으므로, 해당 사용자 계정은
활성화된 상태로 간주된다.
휴먼 계정으로의 전환 논리를 구현하려면 현재 시간과 로그인 시간을 비교하여
1년 이상이 경과하면 "false"를 반환하는 로직을 추가해 볼 수도 있다.
PrincipalDetailsService
package com.example.security1.auth;
import com.example.security1.model.User;
import com.example.security1.repository.UserRepository;
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;
/*
시큐리티 설정에서 loginProcessingUrl("/login");
/login 요청이 오면 자동으로 UserDetailsService 타입으로 IoC되어 있는
loadUserByUsername 함수가 실행된다.
*/
@Service
public class PrincipalDetailsService implements UserDetailsService {
/*
로그인form의 로그인 버튼을 클릭하면 -> "/login" 호출 -> 스프링은 IoC에서 UserDetailsService로 등록되어있는 타입을 찾는다.
-> 따라서 스프링은 PrincipalDetailsService를 찾게됨 -> 찾아지면 바로 loadUserByUsername 함수를 호출한다. -> 그때 form에 넣은 parameter인 username을 가져온다.
*/
@Autowired
private UserRepository userRepository;
//시큐리티 session => Authentication => Userdetails
//시큐리티 session => Authentication(내부 UserDetails)
//시큐리티 session(내부 Authentication(내부 UserDetails)
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userEntity = userRepository.findByUsername(username);
if(userEntity != null) {
return new PrincipalDetails(userEntity);
}
return null;
}
}
시큐리티 설정에서 loginProcessingUrl("/login")에 의해
login요청이 오면 자동으로 UserDetailsService 타입으로 IoC 되어 있는
loadUserByUsername 함수가 실행된다.
로그인 form의 로그인 버튼을 클릭 -> "/login" 호출
-> 스프링은 IoC에서 UserDetailsService로 등록되어 있는 타입을 찾는다.
-> 따라서 스프링은 PrincipalDetailsService를 찾게 된다.
-> 찾아지면 바로 loadUserByUsername함수를 호출한다.
-> 그때 form에 넣은 parameter인 username을 가져온다.
PrincipalDetails와 PrincipalDetailsService에 의해서
Userdetails 정보를 Authentication안에 저장하고
시큐리티 session안의 Authentication 정보를 저장하게 된다.
session > Authentication > Userdetails
'자바 탐구' 카테고리의 다른 글
스프링) SpringSecurity - 6) Oauth2.0 구글 로그인 준비 (0) | 2023.05.26 |
---|---|
스프링) SpringSecurity - 5) Security 권한 처리 (0) | 2023.05.26 |
스프링) SpringSecurity - 3) Security 회원 가입 (0) | 2023.05.26 |
스프링) SpringSecurity - 2) SecurityConfig 설정 (0) | 2023.05.26 |
스프링) SpringSecurity - 1) Security, Mustache 환경 설정 (0) | 2023.05.26 |