쌓고 쌓다
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만 비교하고 비밀번호를 대조하는 부분은 보이지 않아 비밀번호 처리는 어디서 이뤄지는지 궁금했다.
마침 비슷한 질문한 사람이 있어서 링크를 남겨놓는다.!
'프로그래밍 > spring' 카테고리의 다른 글
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 |
Resource, UrlResource? (1) | 2024.01.02 |
Comments