Spring Security (login)
1) 로그인 관련 Security 동작
login.html의 body부분만
(username과 password를 폼으로 전송하는 방식)
<form th:action="@{loginProc}" method="post" name="loginForm">
<input type="text" name="username" placeholder="id입력하세요" style="height: 70px; width: 200px; font-size: 20px" /><br>
<input type="password" name="password" placeholder="pw입력하세요" style="height: 70px; width: 200px; font-size: 20px"/><br>
<input type="submit" value="login" style="height: 70px; width: 100px;"/>
</form>
먼저 cofig관련클래스인SecurityConfig의 filterChain메서드의 login부분만으로 설명
//커스텀로그인
//loginPage("/login")로 인해 인증되지 않은 사용자가 보호된 URL에 접근하면 /login으로 리다이렉트됩니다.
http.formLogin((a)->a.loginPage("/login") //로그인요청
.loginProcessingUrl("/loginProc")
.defaultSuccessUrl("/my",true) //로그인성공 시 호출
/*
(로그인성공의 기준 : 권한 과 userDetail구현체 두개 다 검사 현재 로그인은 권한은 상관없으니 userDetail구현체 존재의 유무만 확인)
true : 무조건my페이지요청
false : 로그인 전에 요청하던 페이지가있는 경우 해당페이지로감 그 외는 /my
*/
.permitAll()
)
.exceptionHandling((e) -> e
.accessDeniedPage("/accessDenied") // 권한 부족 시 이동할 페이지
);
로그인인증이 성공하면
/my로 매핑된다
해당 메서드에loginProcessingUrl("/loginProc")을 유심히보자
해당 요청이 들어오면 security가 이를 가로채서 로그인처리를 시작함
(이 과정에서 bean으로 등록된 UserDetailService의 구현체가 자동호출)
UserDetailService의 구현체 CustomUserDetailService
package springSecurity2.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import lombok.extern.log4j.Log4j;
import lombok.extern.log4j.Log4j2;
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;
import springSecurity2.dto.CustomUserDetails;
import springSecurity2.entity.UserEntity;
import springSecurity2.repository.UserRepository;
@Service
@RequiredArgsConstructor
@Log4j2
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
/*
springSecurity에서 로그인시 호출하는 메서드
username : 입력한 id값
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity userData = userRepository.findByUsername(username);
if(userData != null) {
//Spring Security가 이를 사용해 인증(예: 비밀번호 검증)을 진행하도록 합니다.
return new CustomUserDetails(userData);
}
else{
log.error("아이디오류");
//spring security에 인증에 실패했음을 전달
throw new UsernameNotFoundException("USER NOT FOUND");
}
}
}
repository에 findByUsername메서드를 만들어준다
(JPA에서 자동으로 매개변수로 넘어온 username을 이용해 컬럼조회를 실행함)
반환값이 있다면 spring Security가 알아볼 수 있게
CustomUserDetail(UsetDetail의 구현체)를 생성
반환값이 없다면 UsernameNotFoundException예외를 던져
Spring Security에 사용자가 존재하지 않음을 알립니다
HomeController의 my 매핑부분
@Controller
@RequestMapping("/")
@RequiredArgsConstructor
public class HomeController {
@GetMapping("my")
public String my(Model model) {
String id = SecurityContextHolder.getContext().getAuthentication().getName();
//SecurityContextHolder.getContext().getAuthentication() : 사용자정보,권한정보
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//authorities : 권한목록조회
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
//iter : 권한목록 순회
Iterator<? extends GrantedAuthority> iter = authorities.iterator();
GrantedAuthority auth = iter.next();//등록된 권한 조회
String role = auth.getAuthority();//권한이름 문자열로 반환
model.addAttribute("msg","/my로 접근 : id="+id+" \n\n role : "+role);
return "home";
}
}
id : 인증에성공한 CustomUserDetails(로그인한 사용자정보)의 name
role : 사용자의 권한(문자열)
해당 role을 기반으로 security를 활용한 접근제한이 가능하다
auth는 AuthorizeHttpRequestsConfigurer 객체로, 요청별 접근 제어를 설정하는 데 사용됩니다.
requestMatchers("url").hasRole("admin") : url의 접근은 권한이 admin인 사람만 가능(UserDetail에 역할반영)
requestMatchers("url").hasAnyRole("admin","USER) : url의접근은 admin or User만 접근 가능하다
anyRequest().authenticated) : 그 외 모든요청은 인증받아야함
http.authorizeHttpRequests((auth)->auth
.requestMatchers("/","/login","/home","/join","/joinProc").permitAll() //home은 누구나 접근가능하다
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/my/*").hasAnyRole("ADMIN","USER")
.anyRequest().authenticated());
한번 해당 filter의 동작을 확인하기위해
user로 로그인한 후 /admin에 접근해볼게요
(로그인을 아예하지않고 admin에 접근하는경우는 loginPage에 의해 login페이지로 이동함)
(권한부족은 다른 개념임)
권한 부족에 대한 설정
http.formLogin에
.exceptionHandling((e) -> e
.accessDeniedPage("/accessDenied") // 권한 부족 시 이동할 페이지
다음설정으로 인해 내가설정해둔 accessDenied에 매핑될것임