쌓고 쌓다
Spring Security 로그인 본문
반응형
SecurityConfig
package com.example.spotserver.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.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// 해당 메서드의 리턴되는 오브젝트를 IoC로 등록해준다.
@Bean
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable);
http.authorizeHttpRequests(request ->
request
.requestMatchers("/user/**").authenticated() // 인증만 된다면 들어갈 수 있는 주소
.requestMatchers("/manager/**").hasAnyRole("ADMIN", "MANAGER")
.requestMatchers("/admin/**").hasAnyRole("ADMIN")
.anyRequest().permitAll())
.formLogin(formLogin -> formLogin
.usernameParameter("name")
.loginPage("/loginForm")
.defaultSuccessUrl("/")
.loginProcessingUrl("/login") // "/login" 요청시 시큐리티가 낚아채 로그인을 진행해줌.
// loginForm에서 login을 왔다면 "/"로 보내고, 다른 페이지에서 로그인 왔다면 그 페이지로 리다이렉트 해줌.
);
return http.build();
}
}
추가 및 변경된 사항은 다음과 같다.
- usernameParameter("name") : 뒤의 내용들을 알아야 이해가 가는 부분이다.
- UserDetailsSerivce의 loadUserByusername(String username)는 보면 파라미터가 username으로 되어 있다.
- 로그인 폼에서 input 태그의 속성으로 name="username"를 해줘서 username으로 일치를 시켜야하지만
- usernameParameter를 사용하여 로그인 폼에서 사용할 식별자를 변경할 수 있다.
- loginProcessingUrl : 스프링 시큐리티에서 로그인 폼을 제출할때 사용할 URL을 설정한다. 해당 URL로 POST 요청을 보낸다.
loginForm.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
<form action="/login" method="post">
<input type="text" name="name" placeholder="name"> <br>
<input type="password" name="password" placeholder="password"> <br>
<button>로그인</button>
</form>
<a href="/joinForm">회원가입을 하지 않으셨나요?</a>
</body>
</html>
"/login"으로 POST 요청을 보낸다.
위에 SecurityConfig에서 loginProcessingUrl을 "/login"으로 했으므로 이 요청을 시큐리티가 가로챈다.
현재 name="name"으로 적어놨는데 시큐리티가 처리하는 기본값은 name="username"이다.
usernameParameter("name")을 작성했기에 name="name"이 처리될 수 있는것이다.
다음의 규칙을 이해하자.
- "/login" 요청을 시큐리티가 낚아채어 로그인을 처리한다.
- 로그인 완료시 시큐리티 세션을 만들어준다.
- 이 세션에 들어갈 수 있는 오브젝트는 정해져 있으며 Authentication 객체여야한다.
- Authentication 안에는 유저 정보가 있어야한다.
- 유저 정보도 타입(오브젝트)가 정해져 있으며 UserDetails 객체여야한다.
=> 즉, 시큐리티 세션 ( Authentication ( UserDetails ) ) 구조이다.
로그인을 위해 다음의 UserDetails(PrincipalDetails.java) 클래스를 만들어 주자.
PrincipalDetails.java
package com.example.spotserver.config.auth;
import com.example.spotserver.securityStudy.TestUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
public class PrincipalDetails implements UserDetails {
private TestUser testUser;
public PrincipalDetails(TestUser testUser) {
this.testUser = testUser;
}
// 해당 유저의 권한을 리턴하는 곳.
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 현재 TestUser의 권한은 String이라 타입을 맞춰줘야함
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return testUser.getRole();
}
});
return collection;
}
@Override
public String getPassword() {
return testUser.getPassword();
}
@Override
public String getUsername() {
return testUser.getName();
}
@Override
public boolean isAccountNonExpired() {
// 계정이 만료되었는지 여부를 판단하는 로직을 구현
// 만료되었으면 false, 그렇지 않으면 true 반환
return true;
}
@Override
public boolean isAccountNonLocked() {
// 계정이 안잠겼니?
return true;
}
@Override
public boolean isCredentialsNonExpired() {
//isCredentialsNonExpired() 메소드는 계정의 자격 증명이 만료되었으면 false를 반환하며, 만료되지 않았으면 true를 반환합니다.
return true;
}
@Override
public boolean isEnabled() {
// 계정이 활성화 되어 있니?
// 이런것들 언제 false 하느냐? User 필드의 TimeStamp를 통해 휴면 계정을 관리하여
// 여기에 getLoginDate를 가져와서 현재시간 - 로그인 시간이 1년을 넘으면 false로 반환해버리면 된다!
return true;
}
}
오버라이딩한 메서드에 대한 설명은 주석으로 모두 설명해두었다. 읽어보자~
PrincipalDetailsService.java (UserDetailsService)
package com.example.spotserver.config.auth;
import com.example.spotserver.securityStudy.TestUser;
import com.example.spotserver.securityStudy.TestUserRepository;
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 {
private TestUserRepository testUserRepository;
@Autowired
public PrincipalDetailsService(TestUserRepository testUserRepository) {
this.testUserRepository = testUserRepository;
}
// 이 메서드 반환값이 Authentication 내부에 쏙 UserDeatils가 들어감
// 시큐리티 세션 = Authentication(내부 UserDetails)
// 시큐리티 세션 ( 내부 Authentication(내부 UserDetails) )
// loadUser... 애가 알아서 세션까지 다 넣어
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 파라미터가 username으로 넘어오는데 loginForm에서도 username으로 맞춰서 넘겨줘야함.
// username이 아닌 name으로 넘기도록 난 해놨으니 매칭되도록 SecurityConfig에서 .usernameParameter("name") 으로 해준거임.
TestUser testUser = testUserRepository.findByName(username);
if(testUser != null) {
return new PrincipalDetails(testUser);
}
return null;
}
}
loadUserByUsername의 파라미터명이 username이다. 그래서 로그인 폼에서 name="username"이 기본값이고 이 값으로 매칭을 시켜줘야하지만 usernameParameter("name")으로 변경해주었기에 name="name"으로 폼에서 넘겨줄 수 있었던 것이다.
username으로만 정보를 찾고 비밀번호는 어디서 대조하는것인가?
코드상 username만 비교하고 비밀번호를 대조하는 부분은 보이지 않아 비밀번호 처리는 어디서 이뤄지는지 궁금했다.
마침 비슷한 질문한 사람이 있어서 링크를 남겨놓는다.!
반응형
'Dev Log' 카테고리의 다른 글
| Spring Security 필터 (0) | 2024.01.17 |
|---|---|
| Spring Security 권한처리 (0) | 2024.01.16 |
| Spring Security 회원가입 및 BCrypt (0) | 2024.01.15 |
| Spring Security 설정 및 로그인 페이지로 보내기 (1) | 2024.01.15 |
| AccessToken, RefreshToken? (0) | 2024.01.11 |