#3 [스프링 스터디] 쇼핑몰 만들기 프로젝트 - Spring Security

myeonji·2022년 1월 7일
2

쇼핑몰 프로젝트 할 때 세션과 로그인 기능을 사용하기 위해,
membermanage 프로젝트에서 로그인/로그아웃/회원가입 기능 을 도입하는 연습을 해보았습니다.
https://github.com/rladuswl/MemberManage

Spring Security를 이용하여 회원가입 및 로그인, 로그아웃 도입

  1. Spring Security를 이용하기 위해서는 build.gradle에 가서 dependencies 안에 아래 dependency를 추가해야 합니다.
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'
  1. application.properties에도 Spring Security를 설정해주어야 합니다.
spring.security.user.name=root
spring.security.user.password=root
spring.security.user.roles=ADMIN

  • config 패키지 - auth 패키지 - PrincipalDetails.java, PrincipalDetailsService.java
  • SecurityConfig.java
  • controller 패키지 - AuthController.java
  • entity 패키지 - User.java
  • repository 패키지 - UserRepository 인터페이스
  • service 패키지 - AuthService.java
  • web.dto.auth 패키지 - SignupDto.java
  1. User Entity
package com.example.membermanage.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.time.LocalDateTime;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity // DB에 테이블 자동 생성
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(unique = true) // username 중목 안됨
    private String username;
    private String password;
    private String name;
    private String email;

    private String role; // 권한

    private LocalDateTime createDate; // 날짜

    @PrePersist // DB에 INSERT 되기 직전에 실행. 즉 DB에 값을 넣으면 자동으로 실행됨
    public void createDate() {
        this.createDate = LocalDateTime.now();
    }
}
  1. UserRepository
package com.example.membermanage.repository;

import com.example.membermanage.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import javax.servlet.http.HttpSession;

// <Entity, Entity-id>
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
    User findByUsername(String username);
}
  1. config 설정
    • PrincipalDetails
package com.example.membermanage.config.auth;

import com.example.membermanage.entity.User;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

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

@Data
public class PrincipalDetails implements UserDetails {

    private static final long serialVersionUID = 1L;

    private User user;

    public PrincipalDetails(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        Collection<GrantedAuthority> collector = new ArrayList<>();
        collector.add(() -> { return user.getRole();}); // 람다식

        return collector;
    }


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

    @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;
    }
}
  • PrincipalDetailsService
package com.example.membermanage.config.auth;

import com.example.membermanage.entity.User;
import com.example.membermanage.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;

@RequiredArgsConstructor
@Service
public class PrincipalDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

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

        User userEntity = userRepository.findByUsername(username);

        if(userEntity == null) {
            return null;
        } else {
            return new PrincipalDetails(userEntity);
        }
    }
}
  1. SecurityConfig
package com.example.membermanage.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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@EnableWebSecurity // 해당 파일로 시큐리티 활성화
@Configuration // IoC 등록
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder encoder() {
        // DB 패스워드 암호화
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//      super.configure(http); // 이 코드 삭제하면 기존 시큐리티가 가진 모든 기능 비활성화
        http.csrf().disable(); // csrf 토큰 비활성화 코드

        http.authorizeRequests()
                .antMatchers("/", "/main/**").authenticated() // 이 주소로 시작되면 인증이 필요
                .anyRequest().permitAll() // 그게 아닌 모든 주소는 인증 필요 없음
                .and()
                .formLogin()
                .loginPage("/signin") // 인증필요한 주소로 접속하면 이 주소로 이동시킴
                .loginProcessingUrl("/signin") // 스프링 시큐리티가 로그인 자동 진행 POST방식으로 로그인 진행
                .defaultSuccessUrl("/member/list"); // 로그인이 정상적이면 "/" 로 이동
    }
}
  1. AuthService
package com.example.membermanage.service;

import com.example.membermanage.entity.User;
import com.example.membermanage.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpSession;
import javax.transaction.Transactional;

@RequiredArgsConstructor
@Service
public class AuthService {
    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    @Transactional // Write(Insert, Update, Delete)
    public User signup(User user) {
        String rawPassword = user.getPassword();
        String encPassword = bCryptPasswordEncoder.encode(rawPassword);
        user.setPassword(encPassword);
        user.setRole("ROLE_USER");;

        User userEntity = userRepository.save(user);
        return userEntity;
    }
}
  • BCrypt를 이용하여 패스워드 암호화 적용, 신규 회원의 기본 Role 값 적용
  1. AuthController
package com.example.membermanage.controller;

import com.example.membermanage.entity.User;
import com.example.membermanage.service.AuthService;
import com.example.membermanage.web.dto.auth.SignupDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;

import javax.servlet.http.HttpServletRequest;

@RequiredArgsConstructor
@Controller
public class AuthController {

    private final AuthService authService;

    @GetMapping("/signin")
    public String SigninForm() {
        return "signin";
    }

    @GetMapping("/signup")
    public String SignupForm() {
        return "signup";
    }

    @PostMapping("/signup")
    public String signUp(User user) {

        User newUser = user; //새로운 유저 받음

        User userEntity = authService.signup(user);
        System.out.println(userEntity);

        return "signin";
    }

    // 로그인성공 창에서 로그아웃 버튼
    //@RequestMapping(value="logout", method = RequestMethod.GET)
    @GetMapping("/logout")
    public String logout(HttpSession session) throws Exception {
        authService.logout(session);
        return "redirect:/signin";
    }
}
  1. SignupDto
package com.example.membermanage.web.dto.auth;

import com.example.membermanage.entity.User;
import lombok.Data;

@Data
public class SignupDto {
    private String username;
    private String password;
    private String email;
    private String name;

    public User toEntity() {
        return User.builder()
                .username(username)
                .password(password)
                .email(email)
                .name(name)
                .build();
    }
}

스프링 기초

  • 스프링이란
  • Servlet과 Spring 차이
  • MVC 패턴이란
  • ORM
  • 스프링 흐름
  • 스프링 IoC 와 DI
  • JPA 개념
  • JPA 패턴 내용 (N+1 같은 것)
  • Thymeleaf
  • Dependency 설명

프론트엔드 부분

  • 부트스트랩
  • 백엔드와 통신 원리

CS 부분

  • 쿠키
  • 세션
  • 인터넷 통신
  • IP, TCP/UDP, PORT, DNS
  • URI, URL
  • HTTP 메시지와 API
  • HTTP 상태코드
  • 전송방식
profile
DBA, 경제 그리고 고냥이

1개의 댓글

comment-user-thumbnail
2024년 1월 1일

잘보고가요~

답글 달기