본문 바로가기
SPRINGBOOT

[spring security] 7. 로그인 검증(인증), UserDetails, UserDetailsService

by 정공자씨 2024. 1. 11.

 

로그인 검증(인증)


 

[ 시큐리티를 통해 인증을 진행하는 방법 ]

 

Login 페이지(loginform.html)를 통해 아이디, 비밀번호를 POST 요청 시, 스프링 시큐리티 인증 절차를 거쳐 로그인 진행

  • DB(데이터베이스)에 저장된 회원 정보를 조회하여
  • 비밀번호를 검증하고
  • 서버 세션 저장소에 해당 아이디에 대한 session을 저장

이때, 로그인 회원 정보를 검증하기 위해서는 UserDetailService , UserDetails 클래스 구현 필요

 

 

 

 

 

구조도


 

 

  • 로그인 회원 정보를 검증하기 위해서는 UserDetailService , UserDetails 클래스 구현 필요
    • 해당 클래스는 인터페이스이므로, 이를 상속받아 구현하는 클래스 필요
  • DB(데이터베이스)의 회원 정보를 가져와, 로그인된 정보를 비교하여 검증 가능

 

 


UserDetailsService 

 

1. 의미

- Spring Security에서 유저의 정보를 가져오는 인터페이스

- 사용자가 로그인을 할 때 기본 회원인지 인증 하고, 인증이 완료되면 로그인한 상태의 사용자의 세션을 유지
시켜주기 위한 인터페이스

- 인증에 성공할 시
▷ 해당 회원에 대한 session에 대한 정보가 생성되고
▷  Authentication 객체나 @AuthenticationPrincipal 애노테이션을 통해서 session에 들어있는 사용자 정보를 사용할 수 있음
▷ 즉, 이 메서드가 정상적으로 완료되어 UserDetails 정보가 반환되면 인증에 성공한 것

- 기본 오버라이드 메서드

▷DB에서 유저 정보를 불러오는 중요한 메서드로, 로그인한 회원이 기본 회원인지 아닌지 인증하는 메서드

 

 

2. 코드 구현하기

  • UserDetailsService 인터페이스 상속받는 CustomUserDetailsService 클래스를 생성
  • security config 설정( http.formLogin() )을 한다면, 로그인 시(loginform.html)에
    • Spring Security에서 username 정보를 넘겨줄 것이고
    • loadUserByUsername 메서드에서 username을 매개변수로 받아서, DB에 저장되어 있는 유저 정보를 가져옴
package com.example.securitytest.service;

import com.example.securitytest.dto.CustomUserDetails;
import com.example.securitytest.entity.UserEntity;
import com.example.securitytest.repository.UserRepository;
import lombok.RequiredArgsConstructor;
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;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService { 

    // DB에 username이 존재하는지 검증하기 위해서, UserRepository와 연결
    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //사용자 로그인 시, spring secutiry에서 유저정보를 가져와 username을 매개변수로 넣어줌
        UserEntity entity = userRepository.findByUsername(username);

        if( entity == null ) { 
             throw new UsernameNotFoundException(username);
        }

        // entity가 존재(DB에 데이터 있음), CustomUserDetails에 회원 데이터 전달
        return new CustomUserDetails(entity);
    }
}

 

 


UserRepository

package com.example.securitytest.repository;

import com.example.securitytest.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<UserEntity, Integer> {

    UserEntity findByUserName(String username);

}

 

 

UserEntity

package com.example.securitytest.entity;

import jakarta.persistence.*;
import lombok.Data;

@Entity // table과 연결 - Entity를 기반으로 테이블을 만들어주는 hibernate 설정
@Data
public class UserEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // id가 자동으로 생성 
    private int id;

    @Column(unique = true) // username이 중복되어 저장되지 않도록 설정
    private String username;

    private String password;

    private String role; // 권한 저장: 일반인(USER), 관리자(ADMIN)


}

 


UserDetails 

1. 의미

- Spring Security가 사용자 정보를 알 수 있도록 사용자에 대한 정보를 담는 인터페이스
- 이 인터페이스를 구현하면, Spring Security에서 구현한 클래스를 사용자 정보로 인식하고 인증 작업 진행
- 즉, UserDetails 인터페이스는 VO(DTO) 역할을 함


- 인터페이스 구현 시, 기본 오버라이드 메서드

 

 

2. 코드 구현하기

  • UserDetails 인터페이스를 상속받는 CustomUserDetails 클래스 생성
    • Spring Security의 기본 UserDetails로는 필요한 정보를 모두 담을 수 없기 때문에 CustomUserDetails를 구현하여 사용
  • userDetailService로부터 전달받은 데이터( return new CustomUserDetails(entity) )
package com.example.securitytest.dto;

import com.example.securitytest.entity.UserEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails { //dto와 같은 역할을 함

    private final UserEntity userEntity;

    @Override // 사용자의 특정 권한: Role값에 대해 반환
    public Collection<? extends GrantedAuthority> getAuthorities() {
        
        Collection<GrantedAuthority> collection = new ArrayList<>();

        collection.add(new GrantedAuthority() {

            @Override
            public String getAuthority() {
                return userEntity.getRole();
            }
        });

        return collection;
    }

    @Override
    public String getPassword() {
        return userEntity.getPassword();
    }

    @Override
    public String getUsername() {
        return userEntity.getUsername();
    }


    @Override
    public boolean isAccountNonExpired() {
        return true; // 만료되지 않음
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

 

 

MainController

SecurityContextHolder에서 UserDetails(사용자 정보) 불러오기 
package com.example.securitytest.controller;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.Collection;
import java.util.Iterator;

@Controller
public class MainController {

    @GetMapping("/")
    public String main(Model m) {

        //SecurityContextHolder에서 UserDetails 불러오기

        // session에 담겨있는 로그인한 사용자의 특정한 이름 값
        String id = SecurityContextHolder.getContext().getAuthentication().getName();

        // session에 담겨있는 로그인한 사용자의 특정한 role 값
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        Iterator<? extends GrantedAuthority> iter = authorities.iterator();
        GrantedAuthority auth = iter.next();
        String role = auth.getAuthority();

        // view에서 보여줄 데이터 저장
        m.addAttribute("id", id);
        m.addAttribute("role", role);

        return "main";
    }
}

 

 

 

 

 

 


관리자 권한으로 관리자 페이지 접근하는 경우

  • 관리자로 회원가입을 하고, 관리자 권한으로 /admin경로에 접근
1. 해당 역할(role) 값을 관리자(admin)으로 회원가입을 진행하고
  • role = ROLE_ADMIN으로 설정



2. 관리자 권한으로 로그인





3. 관리자 홈페이지에 접근(/admin)


 

 

관리자 권한이 없는 자가 관리자 페이지 접근하는 경우


1. 일반 사용자가 로그인 하고




  • role = ROLE_USER (사용자) 으로 설정


2. 관리자 페이지 접근한 경우



 

 

 

 

출처

https://programmer93.tistory.com/68

https://to-dy.tistory.com/86