[Spring Boot] 로그인 기능 구현 (3) - 스프링 시큐리티 로그인

2024. 1. 7. 23:54· Spring Boot
목차
  1. 1. 스프링 시큐리티 (Spring Security) 란 ?
  2. 2. 스프링 시큐리티 설정
  3. 2-1. build.gradle
  4. 2-2. SecurityConfig ( + BCryptPasswordEncoder )
  5. 2-3. CustomUserDetails
  6. 2-4. CustomUserDetailsService
  7. 3. 로그인 프로젝트에 적용
  8. 3-1. config > SecurityConfig
  9. 3-2. domain > dto > CustomUserDetails
  10. 3-3. service > CustomUserDetailsService
  11. 3-4. service > MemberService
  12. 3-5. controller > SecurityLoginController
  13. 4. 실행결과

2024.01.07 - [Spring Boot] - [Spring Boot] 로그인 기능 구현 (0) - 공통 기능, 코드 구현

 

[Spring Boot] 로그인 기능 구현 (0) - 공통 기능, 코드 구현

0. 상황 설명 여러가지 방법을 사용해서 로그인 기능을 구현하려 함 공통인 기능과 코드는 이 게시글에서 모두 정리 방법마다 다른 코드는 각 게시물에서 따로 정리 1. 프로젝트 버전 정보 / DB 스

blogan99.tistory.com

 

1. 스프링 시큐리티 (Spring Security) 란 ?

  • 스프링에서 제공하는 프레임워크, 애플리케이션에 인증, 인가 기능을 제공함
  • 동작 원리
    • 로그인 시도 시, 인증 필터가 작동하여 사용자의 id, 비밀번호를 가져옴
    • 인증 필터는 id, 비밀번호를 Authentication 객체에 담아서 AuthenticationManager에게 전달
    • AuthenticationManager는 UserDetailsService를 호출해서 사용자의 정보를 불러옴
    • 이때 UserDetailsService는 DB에 접근하여 정보를 가져오고 이 정보를 UserDetails 객체에 담아서 반환함
  • 비밀번호는 PasswordEncoder를 통해서 암호화됨

 

2. 스프링 시큐리티 설정

 

2-1. build.gradle

// dependencies 추가

implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

 

 

2-2. SecurityConfig ( + BCryptPasswordEncoder )

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    // 시큐리티 필터 메서드
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{

        http
                .authorizeHttpRequests((auth) -> auth
                        // 인가 동작 순서 : 위에서 부터 아래로 순서대로 ! 따라서 순서 유의 (anyRequest 특히)
                        
                        .requestMatchers("/", "/login", "/join").permitAll()
                        .requestMatchers("/admin").hasRole(Role.ADMIN.name())
                        // ** : 와일드카드
                        .requestMatchers("/my/**").hasAnyRole(Role.ADMIN.name(), Role.USER.name())
                        .anyRequest().authenticated()
                );

        // 로그인 설정
        http
                .formLogin((auth) -> auth.loginPage("/login")
                        .loginProcessingUrl("/loginProc")
                        .permitAll()
                );
        
        // 로그아웃 URL 설정
        http
                .logout((auth) -> auth
                        .logoutUrl("/logout")
                );



        // csrf : 사이트 위변조 방지 설정 (스프링 시큐리티에는 자동으로 설정 되어 있음)
        // csrf기능 켜져있으면 post 요청을 보낼때 csrf 토큰도 보내줘야 로그인 진행됨 !
        // 개발단계에서만 csrf 잠시 꺼두기
        http
                .csrf((auth) -> auth.disable());

        return http.build();
    }

    // BCrypt password encoder를 리턴하는 메서드
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){

        return new BCryptPasswordEncoder();
    }
}
  • http.authorizeHttpRequests( (auth) -> auth    : 각 url 별로 인가 설정
    • .permitAll() : 로그인 하지 않아도 모든 사용자가 접근 가능
    • .authenticated() : 로그인만 한다면 모든 사용자가 접근 가능
    • .denyAll() : 로그인을 하더라도 모든 사용자가 접근 불가
    • .hasRole() : 로그인 이후에 특정 role이 있어야 접근 가능
    • .hasAnyRole() : hasRole과 같지만 여러개의 role 설정 가능
    • .requestMatchers() : 특정 URL에 대한 설정
    • .anyRequest() : 위에서 처리하지 않은 나머지 경로에 대한 처리
  • http.formLogin( (auth) -> auth    : 로그인 설정
    • .loginPage() : 로그인이 발생하는 페이지 URL
    • .loginProcessingUrl() : 프론트단 (html)에서 넘어온 로그인 정보를 해당 경로로 넘기면 스프링 시큐리티가 받아서 자동으로 로그인 진행
  • http.logout( (auth) -> auth    : 로그아웃 설정
    • .logoutUrl() : 로그아웃이 발생하는 페이지 URL

 

2-3. CustomUserDetails

  • 직접 로그인 컨트롤러를 구현하지 않아도 스프링 시큐리티가 요청을 가로채서 자동으로 로그인을 진행함
  • 이에 따른 설정을 해줘야 함
  • UserDetails 인터페이스 상속 => 오버라이드 필수
public class CustomUserDetails implements UserDetails {

    private final User user;
    public CustomUserDetails(User user) {
        this.user = user;
    }

    // 현재 user의 role을 반환 (ex. "ROLE_ADMIN" / "ROLE_USER" 등)
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collection = new ArrayList<>();
        collection.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
            	// 앞에 "ROLE_" 접두사 필수 !
                return "ROLE_" + user.getRole().name();
            }
        });

        return collection;
    }

    // user의 비밀번호 반환
    @Override
    public String getPassword() {
        return user.getPassword();
    }

    // user의 username 반환
    @Override
    public String getUsername() {
        return user.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;
    }
}
  • 밑에 4개의 메서드는 따로 설정할 수 있지만 해당 구현에서는 따로 설정하지 않고 true로 고정
    • isAccountNonExpired() : 계정 만료 여부 => true : 만료 X
    • isAccountNonLocked() : 계정 잠김 여부 => true : 잠김 X
    • isCredentialsNonExpired() : 비밀번호 만료 여부 => true : 만료 X
    • isEnabled() : 계정 사용 가능 여부 => true : 사용 가능 O

 

2-4. CustomUserDetailsService

  • 스프링 시큐리티로 로그인 진행 시 기존처럼 Member 객체가 아닌 CustomUserDetails(member) 가 필요함
  • 이를 구현해 줄 서비스 단
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

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

        User user = userRepository.findByUsername(username);
        if(user != null) {
            return new CustomUserDetails(user);
        }
        return null;
    }
}

 

3. 로그인 프로젝트에 적용

  • 위에 언급한 설정들을 현 프로젝트에 맞춰 수정 및 적용
  • 로그인을 제외한 나머지 기능, 라이브러리 등은 아래 게시글 참고

2024.01.07 - [Spring Boot] - [Spring Boot] 로그인 기능 구현 (0) - 공통 기능, 코드 구현

 

[Spring Boot] 로그인 기능 구현 (0) - 공통 기능, 코드 구현

0. 상황 설명 여러가지 방법을 사용해서 로그인 기능을 구현하려 함 공통인 기능과 코드는 이 게시글에서 모두 정리 방법마다 다른 코드는 각 게시물에서 따로 정리 1. 프로젝트 버전 정보 / DB 스

blogan99.tistory.com

 

3-1. config > SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    // 시큐리티 필터 메서드
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{

        http
                .authorizeHttpRequests((auth) -> auth
                        .requestMatchers("/security-login", "/security-login/login", "/security-login/join").permitAll()
                        .requestMatchers("/security-login/admin").hasRole(MemberRole.ADMIN.name())
                        .requestMatchers("/security-login/info").hasAnyRole(MemberRole.ADMIN.name(), MemberRole.USER.name())
                        .anyRequest().authenticated()
                );

        http
                .logout((auth) -> auth
                        .logoutUrl("/security-login/logout")
                );

        http
                .formLogin((auth) -> auth.loginPage("/security-login/login")
                        .loginProcessingUrl("/security-login/loginProc")
                        .failureUrl("/security-login/login")
                        .defaultSuccessUrl("/security-login")
                        .usernameParameter("loginId")
                        .passwordParameter("password")

                        .permitAll()
                );


        http
                .csrf((auth) -> auth.disable());

        return http.build();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){


        return new BCryptPasswordEncoder();
    }
}
  • 로그인 설정에서 몇 가지 속성 추가
    • .failureUrl() : 로그인 실패 시 이동할 URL
    • .defaultSuccessUrl() : 로그인 성공 시 이동할 URL
    • .usernameParameter("loginId") : 현 프로젝트에서는 username으로 로그인 x => loginId로 로그인 하므로 username 파라미터를 loginId로 수정
    • .passwordParameter("password") : 기본값이 password 이지만 써줌
  • 나머지 부분은 프로젝트에 맞게 URL만 수정

 

3-2. domain > dto > CustomUserDetails

// 다른 부분은 동일

    @Override
    public String getUsername() {
        return member.getLoginId();
    }
  • username 파라미터를 loginId 로 수정 => getUsername() 메서드는 loginId를 반환하게 수정

 

3-3. service > CustomUserDetailsService

// 다른 부분은 동일

Member member = memberRepository.findByLoginId(username);
  • loadUserByUsername 메서드는 username을 매개변수로 받음
  • 따라서 MemberRepository 내의 findByLoginId 메서드를 통해 반환받은 member 객체 저장

 

3-4. service > MemberService

    // BCryptPasswordEncoder 를 통해서 비밀번호 암호화 작업 추가한 회원가입 로직
    public void securityJoin(JoinRequest joinRequest){
        if(memberRepository.existsByLoginId(joinRequest.getLoginId())){
            return;
        }

        joinRequest.setPassword(bCryptPasswordEncoder.encode(joinRequest.getPassword()));

        memberRepository.save(joinRequest.toEntity());
    }
  • BCryptPasswordEncoder를 사용하여 비밀번호 암호화

 

3-5. controller > SecurityLoginController

@Controller
@RequiredArgsConstructor
@RequestMapping("/security-login")
public class SecurityLoginController {

    private final MemberService memberService;

    @GetMapping(value = {"", "/"})
    public String home(Model model) {
        
        model.addAttribute("loginType", "security-login");
        model.addAttribute("pageName", "스프링 시큐리티 로그인");

        String loginId = SecurityContextHolder.getContext().getAuthentication().getName();

        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();

        Member loginMember = memberService.getLoginMemberByLoginId(loginId);

        if (loginMember != null) {
            model.addAttribute("name", loginMember.getName());
        }

        return "home";
    }

    @GetMapping("/join")
    public String joinPage(Model model) {

        model.addAttribute("loginType", "security-login");
        model.addAttribute("pageName", "스프링 시큐리티 로그인");

        // 회원가입을 위해서 model 통해서 joinRequest 전달
        model.addAttribute("joinRequest", new JoinRequest());
        return "join";
    }

    @PostMapping("/join")
    public String join(@Valid @ModelAttribute JoinRequest joinRequest,
                       BindingResult bindingResult, Model model) {

        model.addAttribute("loginType", "security-login");
        model.addAttribute("pageName", "스프링 시큐리티 로그인");

        // 비밀번호 암호화 추가한 회원가입 로직으로 회원가입
        memberService.securityJoin(joinRequest);

        // 회원가입 시 홈 화면으로 이동
        return "redirect:/security-login";
    }

    @GetMapping("/login")
    public String loginPage(Model model) {

        model.addAttribute("loginType", "security-login");
        model.addAttribute("pageName", "스프링 시큐리티 로그인");

        model.addAttribute("loginRequest", new LoginRequest());
        return "login";
    }

    @GetMapping("/info")
    public String memberInfo(Authentication auth, Model model) {
    
    
        model.addAttribute("loginType", "security-login");
        model.addAttribute("pageName", "스프링 시큐리티 로그인");

        Member loginMember = memberService.getLoginMemberByLoginId(auth.getName());

        model.addAttribute("member", loginMember);
        return "info";
    }
    
    @GetMapping("/admin")
    public String adminPage(Model model) {

        model.addAttribute("loginType", "security-login");
        model.addAttribute("pageName", "스프링 시큐리티  로그인");


        return "admin";
    }
}
  • @PostMapping("/login"), info 에서의 로그인 인증 삭제 : 스프링 시큐리티가 자동으로 인증
  • admin 에서의 role 체크 부분 삭제 : 스프링 시큐리티가 자동으로 인가
  • memberInfo 의 매개변수로 Authentication auth 를 받음
    • auth.getName() : 위의 설정을 토대로 현재 로그인 한 계정의 loginId 반환

 

4. 실행결과

  • 홈 화면

  • 로그인 후 홈 화면

  • 마이 페이지

  • 관리자 계정으로 로그인 후 관리자 페이지

  • MySQL로 비밀번호 암호화 확인

 

저작자표시 변경금지 (새창열림)

'Spring Boot' 카테고리의 다른 글

[Spring Boot] 로그인 기능 구현 (4) - 스프링 시큐리티 사용 JWT 로그인  (1) 2024.01.13
[Spring Boot] 스프링으로 엑셀 파일 읽기  (1) 2024.01.08
[Spring Boot] 로그인 기능 구현 (2) - 세션 로그인  (0) 2024.01.07
[Spring Boot] 로그인 기능 구현 (1) - 쿠키 로그인  (0) 2024.01.07
[Spring Boot] 로그인 기능 구현 (0) - 공통 기능, 코드 구현  (2) 2024.01.07
  1. 1. 스프링 시큐리티 (Spring Security) 란 ?
  2. 2. 스프링 시큐리티 설정
  3. 2-1. build.gradle
  4. 2-2. SecurityConfig ( + BCryptPasswordEncoder )
  5. 2-3. CustomUserDetails
  6. 2-4. CustomUserDetailsService
  7. 3. 로그인 프로젝트에 적용
  8. 3-1. config > SecurityConfig
  9. 3-2. domain > dto > CustomUserDetails
  10. 3-3. service > CustomUserDetailsService
  11. 3-4. service > MemberService
  12. 3-5. controller > SecurityLoginController
  13. 4. 실행결과
'Spring Boot' 카테고리의 다른 글
  • [Spring Boot] 로그인 기능 구현 (4) - 스프링 시큐리티 사용 JWT 로그인
  • [Spring Boot] 스프링으로 엑셀 파일 읽기
  • [Spring Boot] 로그인 기능 구현 (2) - 세션 로그인
  • [Spring Boot] 로그인 기능 구현 (1) - 쿠키 로그인
공대생안씨
공대생안씨
전자공학과 학부생의 코딩 일기
티스토리
|
로그인
공대생안씨
공대생의 코딩 일기
공대생안씨
글쓰기
|
관리
전체
오늘
어제
  • All Categories (153)
    • Spring Boot (46)
      • JPA (7)
      • Lombok (2)
    • Java (21)
    • DevOps (3)
      • CI,CD (8)
      • Monitoring (2)
    • Database (7)
      • MySQL (5)
      • MongoDB (1)
      • H2 (1)
    • Trouble Shooting (5)
    • FE (4)
    • IntelliJ (3)
    • Git (3)
    • Algorithm (41)

블로그 메뉴

  • 홈
  • 태그
  • Github

공지사항

인기 글

hELLO · Designed By 정상우.v4.2.2
공대생안씨
[Spring Boot] 로그인 기능 구현 (3) - 스프링 시큐리티 로그인
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.