JAN's History
Spring Security Session 권한처리 본문
- Http Request로 username과 password를 통한 post요청이 옵니다.
- AuthenicationFliter가 UsernamePasswordAuthentication에게 이 요청을 전달하면, HttpServlet에서 username과 password를 추출해서 UsernamePasswordAuthenticationToken 객체를 생성합니다.
- UsernamePasswordAuthenticationToken 은 AuthenticationManager에게 넘겨지고, AuthenticationManager는 해당 UsernamePasswordAuthenticationToken 을 인증할 수 있는 AuthenticationProvider을 찾아서 인증을 진행합니다.
- 이때 인증 과정이 실패하면 SecurityContextHolder를 비우고, remember-me가 설정되어 있다면, RememberMeService.loginFail을 실행합니다. 마지막으로 AuthenticationFailureHandler를 실행시킵니다.
- AuthenticationProvider는 인증과정에 성공하게 되면 Session 정보에 오브젝트를 저장합니다.
- 이때 Session 에 저장될 때에는 오브젝트로 UserDetailsService만 저장이 가능합니다. 그리하여 보통은 오버라이드하여 PrincipalDetailsService implements UserDetailsService를 합니다.
- 그리고 UserDetailsService에는 UserDetails만 저장이 가능하므로, 이 때에도 PrincipalDetails implements UserDetails 를 합니다.
- 그리고 저장된 객체를 다시 AuthenticationProvider에게 돌려줍니다.
- 9. 이를 다시 AuthenicationFliter에게 돌려주면
10. SecurityContextHolder에 정상적으로 저장이 됩니다.
0. 미리 알아두자
- 인증(Authentication): 해당 사용자가 본인이 맞는지를 확인하는 절차
- 인가(Authorization): 인증된 사용자가 요청한 자원에 접근 가능한지를 결정하는 절차
1. pom.xml 의존성 설정
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
스프링 시큐리티를 사용할 것이기 때문에 spring-security 의존성을 설정해줍니다.
2. SecurityConfig class 설정
@Configuration
@EnableWebSecurity //스프링 시큐리티 필터가 스프링 필터체인에 등록이 된다.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean //해당 메서드의 리턴되는 오브젝트를 IoC로 등록해준다.
public BCryptPasswordEncoder encodePwd(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/user/**").authenticated() //로그인(=인증된) 한 사람만 들어올 수 있다.
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')") //두가지 중 하나만 가져야 들어올 수 있음
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll() //위의 3가지 url이 아니면 모두 들어올 수 있음
.and()
.formLogin() //위 3가지 url을 들어가려면 login페이지로 이동하게 함
.loginPage("/loginForm")
.loginProcessingUrl("/login") //login주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해준다.즉, controller에 /login을 안만들어도됨
.defaultSuccessUrl("/"); //로그인 성공하면 디폴트 url
}
}
- http.csrf().disable();: CSRF(Cross-Site Request Forgery) 공격을 방지하기 위해 Spring Security의 CSRF 보호를 비활성화합니다
- http.authorizeRequests(): HTTP 요청에 대한 접근 권한을 구성하기 위한 메서드 호출을 시작합니다.
- .antMatchers("/user/**").authenticated(): "/user/**" 패턴의 URL에 대해 인증된 사용자만 접근할 수 있도록 설정합니다. 즉, 로그인한 사용자만 해당 URL에 접근할 수 있습니다.
- .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')"): "/manager/**" 패턴의 URL에 대해 "ROLE_ADMIN" 또는 "ROLE_MANAGER" 권한을 가진 사용자만 접근할 수 있도록 설정합니다. 즉, 이 URL에 접근하려면 관리자 또는 매니저 권한이 필요합니다.
- .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')"): "/admin/**" 패턴의 URL에 대해 "ROLE_ADMIN" 권한을 가진 사용자만 접근할 수 있도록 설정합니다. 즉, 이 URL에 접근하려면 관리자 권한이 필요합니다.
- .anyRequest().permitAll(): 위에서 설정한 URL 패턴 이외의 모든 요청은 권한 없이 허용됩니다. 즉, 인증되지 않은 사용자도 이러한 요청을 할 수 있습니다.
- .and(): 이전 규칙을 종료하고 새로운 보안 규칙을 시작합니다.
- .formLogin(): 폼 기반 인증을 활성화합니다. 이것은 사용자가 로그인 폼을 통해 인증하는 데 사용됩니다.
- .loginPage("/loginForm"): 사용자가 로그인할 때 사용할 사용자 정의 로그인 페이지를 지정합니다. "/loginForm"은 사용자 정의 로그인 페이지의 URL입니다.
- .loginProcessingUrl("/login"): 로그인 프로세스가 처리되는 URL을 지정합니다. 즉, "/login" 주소로 요청이 오면 Spring Security가 로그인 처리를 수행합니다.
- .defaultSuccessUrl("/"): 로그인 성공 후 리다이렉트할 기본 URL을 지정합니다. 여기서는 "/"로 지정되어 있으므로 로그인 성공 후 홈 페이지로 이동합니다.
3. PrincipalDetails Setting
form 로그인은 Spring Security Session 기반 로그인입니다. 인증된 사용자의 정보는 아래 과정에 따라 Security 내부의 자체적인 Session에서 관리됩니다.
- POST "/login" 을 낚아채 로그인 진행
- 로그인이 완료되면 시큐리티 자체 session을 만들어 저장 (key: Security ContextHolder)
- 오브젝트 타입 : Authentication
- Authentication 안에 User(UserDetails) 객체를 가짐
- Security Session -> Authentication -> UserDetails
그러니 Principal/Authentication 객체로 현재 인증된 사용자 정보를 꺼내 쓰려면 UserDetails 인터페이스를 상속받는 User 클래스가 있어야합니다. 저는 User 대신 `PrincipalDetails`를 만들었습니다.
//시큐리티가 /login주소 요청이 오면 낚아채서 로그인을 진행시킨다.
//로그인 진행이 완료가되면 session을 만들어준다.(Security ContextHolder에 세션정보저장함)
//Security ContextHolder에 저장될 수 있는 오브젝트는 Authentication 타입 객체임!
//Authentication 안에 User정보가 있어야함.
//User오브젝트의 타입은 UserDetails타입의 객체여야한다.
//즉, Security Session에 들어갈 수 있는 객체가 Authentication 객체여야하고, 이 안에는 UserDetails만 들어갈 수 있다.
public class PrincipalDetails implements UserDetails {
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() {
//user.getRole은 string타입이라서 바로 리턴할 수 없기 때문에
//collect를 오버라이드 해서 던져줘야함.
@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년동안 회원이 로그인을 안하면 휴먼계정으로 변환되기 했다면?
//user.getLoginDate();를 들고와서
//현재 시간 - 로그인 시간을 해서 1년을 초과하면
//return false하면 된다.
return true;
}
}
4. PrincipalDetailsService Setting
- POST "/login" 요청이 오면 UserDetailsService(인터페이스) 타입으로 IoC되어 있는 loadUserByUsername 함수 실행
- 들어온 아이디값(username)을 가지고 존재하는 사용자인지 체크
- 사용자가 존재하면 User 정보를 담은 UserDetails 객체를 반환
//시큐리티 설정에서 loginProcesingUrl("/login");
//login요청이 오면 자동으로 UserDetailsService 타입으로 IoC되어잇는
//loadUserByUsername 함수가 실행된다.
@Service
public class PrincipalDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
//시큐리티 session - Authentication Type만 저장 가능 <- UserDetails만 저장 가능
//Authentication(내부 UserDetails) => 시큐리티 session(Authentication(내부 UserDetails))
//loadUserByUsername를 오버라이드 해주면 알아서 session안에 (Authentication(내부 UserDetails)를 넣어줌!
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userEntity = userRepository.findByUsername(username);
if(username != null) {
return new PrincipalDetails(userEntity);
}
return null;
}
}
5. Create joinForm.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
<h1>회원가입 페이지</h1>
<hr/>
<form action="/join" method="POST">
<input type="text" name="username" placeholder="Username"/> <br/>
<input type="password" name="password" placeholder="Password"/> <br/>
<input type="email" name="email" placeholder="Email"/> <br/>
<button>회원가입</button>
</form>
</body>
</html>
6. Create UserController
@Controller
public class UserContoller{
@PostMapping("/join")
public String join(User user){
System.out.println(user);
user.setRole("ROLE_USER");
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
user.setPassword(encPassword); //password 암호화 진행
userRepository.save(user);
return "redirect:/loginForm";
}
}
7. 성공적으로 저장!
'JWT' 카테고리의 다른 글
JWT는 무엇이고 어디에 쓰일까? (0) | 2024.06.20 |
---|---|
Spring Security - OAuth2 session 방식으로 구현 해보기(Google,Facebook,Naver) (0) | 2024.06.17 |
로그인 필터 구현 (0) | 2024.06.12 |
SecurityConfig 클래스 (0) | 2024.06.11 |
CSRF Token의 개념과 공격 (0) | 2024.06.08 |