[SpringBoot]스프링부트 쇼핑몰 프로젝트 with JPA (Spring security)

hwa.haha·2022년 11월 17일
0

SpringBoot

목록 보기
2/7
post-thumbnail

애플리케이션을 만들기 위해서는 보통 인증/인가 등의 보안이 필요합니다.
스프링 시큐리티는 스프링기반의 애플리케니션을 위한 보안 솔루션을 제공합니다.

pom.xml 추가

   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

SecurityConfig 추가

package com.shop.shop.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;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

    }

    @Bean
    public PasswordEncoder passwordEncoder(){

        return new BCryptPasswordEncoder();
    }
}

Member(Entity 패키지)

@Table(name = "member")
@Getter
@Setter
@ToString

public class Member {

    @Id
    @Column(name ="member_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private  Long id;

    private String name;

    @Column(unique = true)
    private String email;

    private  String password;

    private  String address;

    @Enumerated(EnumType.STRING)
    private  Role role;

    public  static  Member createMember(MemberFormDto memberFormDto, PasswordEncoder passwordEncoder){

        Member member = new Member();
        member.setName(memberFormDto.getName());
        member.setEmail(memberFormDto.getEmail());
        member.setAddress(memberFormDto.getAddress());
        String password = passwordEncoder.encode(memberFormDto.getPassword());
        member.setPassword(password);
        member.setRole(Role.ADMIN);

        return  member;
    }


}

MemberFormDto(dto패키지)


package com.shop.shop.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class MemberFormDto {
    private String name;

    private  String email;

    private  String password;

    private  String address;
}

MemberRepository(repository 패키지추가)

package com.shop.shop.repository;

import com.shop.shop.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member,Long> {

    Member findByEmail(String email);
}

MemberService(service 패키지추가)

package com.shop.shop.service;


import com.shop.shop.entity.Member;
import com.shop.shop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
//final이나 @NonNUll이 붙은 팔드에 생성자를 생설해줍니다.빈에 생성자가 1개이고 생성자의 파라미터 타입이 빈으로 등록이 가능하다면
//@Autowired 어노테이션 없이 의존성 주입이 가능합니다.
public class MemberService  {


    private final MemberRepository memberRepository;


    public Member saveMember(Member member){
        validateDuplicateMember(member);
        return memberRepository.save(member);
    }

    public  void validateDuplicateMember(Member member){
        Member findMember = memberRepository.findByEmail(member.getEmail());
        if (findMember != null){
            throw  new IllegalStateException("이미 가입된 회원 입니다.");
        }
    }
}

MemberService 파일에서 Ctrl+ shift+ T 테스트가 생김

MemberServiceTest(회원가입)

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@Transactional
@TestPropertySource(locations= "classpath:application-test.properties")
class MemberServiceTest {

    @Autowired
    MemberService memberService;

    @Autowired
    PasswordEncoder passwordEncoder;

    public Member createMember(){
        MemberFormDto memberFormDto = new MemberFormDto();
        memberFormDto.setEmail("test@email.com");
        memberFormDto.setName("홍길동");
        memberFormDto.setAddress("가산");
        memberFormDto.setPassword("1234");

        return  Member.createMember(memberFormDto,passwordEncoder);
    }

    @Test
    @DisplayName("회원가입 테스트")
    public void  saveMemberTest(){
        Member member = createMember();
        Member savedMember = memberService.saveMember(member);

        assertEquals(member.getEmail(),savedMember.getEmail());
        assertEquals(member.getName(),savedMember.getName());
        assertEquals(member.getAddress(),savedMember.getAddress());
        assertEquals(member.getPassword(),savedMember.getPassword());
        assertEquals(member.getRole(),savedMember.getRole());
    }

회원가입결과

MemberServiceTest(회원중복)

 @Test
    @DisplayName("회원중복테스트")
    public  void saveDuplicatieMember(){
        Member member1 = createMember();
        Member member2 = createMember();
        memberService.saveMember(member1);

        Throwable e = assertThrows(IllegalStateException.class,()->{memberService.saveMember(member2);});

        assertEquals("이미 가입된 회원 입니다.",e.getMessage());
    }

회원중복결과

MemberController

package com.shop.shop.controller;

import com.shop.shop.dto.MemberFormDto;
import com.shop.shop.entity.Member;
import com.shop.shop.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
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 org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;
    private final PasswordEncoder passwordEncoder;


    @GetMapping("/new")
    public  String memberForm(Model model){
        model.addAttribute("memberFormDto",new MemberFormDto());
        return "/member/memberForm";
    }

    @PostMapping("/new")
    public  String memberForm(MemberFormDto memberFormDto){

        Member member = Member.createMember(memberFormDto,passwordEncoder);
        memberService.saveMember(member);

        return  "redirect:/";
    }
}

MainController

package com.shop.shop.controller;


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MainController {

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

/회원가입폼 불러오기(Get)

/회원가입후 메인페이지 이동(Post)

MySQL 데이터 확인

메인페이지 이동

MemberController 회원가입 유효성

  @PostMapping("/new") //@Valid  검증하려는 객체에 선언  BindingResult
    public  String memberForm(@Valid MemberFormDto memberFormDto,
                              BindingResult bindingResult,Model model){

        if (bindingResult.hasErrors()){
            return "member/memberForm";
        }

        try {
            Member member = Member.createMember(memberFormDto,passwordEncoder);
            memberService.saveMember(member);
        }catch (IllegalStateException e){
            model.addAttribute("errorMessage",e.getMessage());
            return "member/memberForm";
        }
        return  "redirect:/";
    }
}

로그인/로그아웃

UserDaetailService 인터페이스는 데이터 베이스에서 회원정보를 가져오는 역할을 담당합니다.

MemberService


@RequiredArgsConstructor
//final이나 @NonNUll이 붙은 팔드에 생성자를 생설해줍니다.빈에 생성자가 1개이고 생성자의 파라미터 타입이 빈으로 등록이 가능하다면
//@Autowired 어노테이션 없이 의존성 주입이 가능합니다.

public class MemberService implements UserDetailsService {
 //MemberService가 UserDetailService를 구현한다.


//생략


 @Override //시큐리티 매니저에게 위임하는데 눈에안보인다.
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
    //UserDetailService 인터페이스의 loadUserByUsername()메소드를 오버라이딩합니다. 로그인할 유저의 email을 파라미터로 전달받습니다.
  
       Member member = memberRepository.findByEmail(email);

       if(member == null){
           throw new UsernameNotFoundException(email);
       }
       
       return User.builder() //UserDetail을 구현하고 있는 User 객체를 반환해 줍니다.
     // User 객체를 생성하기 위해서 생성자로 회원의 이메일, 비밀번호,role을 파라미터로 넘겨줍니다.
                  .username(member.getEmail())
                  .password(member.getPassword())
                  .roles(member.getRole().toString())
                  .build();
    }

🧨 로그인후

페이지 권한설정

itemController

package com.shop.shop.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class itemController {

    @GetMapping("/admin/item/new")
    public String itemForm(){
        return "/item/itemForm";
    }
}

CustomAuthenticationEntryPoint


package com.shop.shop.config;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        if("XMLHttpRequest".equals(request.getHeader("x-requested-with"))){
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
        }else{
            response.sendRedirect("/members/login");
        }


    }
}

SecurityConfig

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/members/login")
                .defaultSuccessUrl("/")
                .usernameParameter("email")
                .failureUrl("/members/login/error")
                .and()
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/members/logout"))
                .logoutSuccessUrl("/");

        http.authorizeRequests()
                .mvcMatchers("/","/members/**","/item/**","/images/**").permitAll()
                .mvcMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated();

        http.exceptionHandling()
                .authenticationEntryPoint(new CustomAuthenticationEntryPoint());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/css/**","/js/**","/img/**");
    }

Member(권한 ADMIN)

  public  static  Member createMember(MemberFormDto memberFormDto, PasswordEncoder passwordEncoder){

        Member member = new Member();
        member.setName(memberFormDto.getName());
        member.setEmail(memberFormDto.getEmail());
        member.setAddress(memberFormDto.getAddress());
        String password = passwordEncoder.encode(memberFormDto.getPassword());
        member.setPassword(password);
        member.setRole(Role.ADMIN);

        return  member;
    }

로그인시 보여지는 화면(권한 ADMIN)

profile
개발자로 차근차근

0개의 댓글