Spring

Spring Security (login)

동곤일상 2025. 7. 2. 17:44
반응형

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에 매핑될것임