애플리케이션을 만들기 위해서는 보통 인증/인가 등의 보안이 필요합니다.
스프링 시큐리티는 스프링기반의 애플리케니션을 위한 보안 솔루션을 제공합니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
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();
}
}
@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;
}
}
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;
}
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);
}
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 테스트가 생김
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());
}
@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());
}
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:/";
}
}
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";
}
}
MySQL 데이터 확인
메인페이지 이동
@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 인터페이스는 데이터 베이스에서 회원정보를 가져오는 역할을 담당합니다.
@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();
}
🧨 로그인후
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";
}
}
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");
}
}
}
@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/**");
}
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;
}