상세 컨텐츠

본문 제목

spring security 로그인 로그아웃 회원가입

Web/Spring boot

by whave 2022. 1. 9. 18:22

본문

패키지


Controller : 페이지 컨트롤러

Dto : (Data Transfer Object) 데이터 전송 객체 getter setter

Model : 데이터 객체

Repository : 데이터 저장소

Security : 보안 설정

Service : 프로그램 동작을 위한 함수들 정의

 

패키지 세부 내역


*

HomeController : 로그인 성공 이후의 페이지 컨트롤러

UserController : 로그인 로그아웃 페이지 컨트롤러

*

SignupRequestDto : 사용자의 회원가입 입력 정보 getter setter (DB에 저장할 정보)

*

Timestamped : 생성/변경 시간 업데이트

User : 유저 데이터 요소

UserRole : 유저 역할

*

UserRepository : DB접근

*

UserDetailsImpl : UserDetailsServiceImpl에서 조회된 User 객체 정보, 인증/인가 정보

(다음 그림에서 UserDetails역활)

UserDetailsServiceImpl : usernamedb 조회결과로 User객체를 생성 UserDetailsImpl에 넘김

출처 : 스파르타 코딩 클럽

WebSecurityConfig : 웹사이트 접근 권한 제어

*

UserService : 웹사이트 기능 구현 함수들

 

 

세부 코드 설명


*HomeController : 로그인 성공 후 랜딩되는 페이지 컨트롤러

package com.spring.security.controller;

import com.spring.security.security.UserDetailsImpl;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

public class HomeController {
    @GetMapping("/")
    public String home(Model model, @AuthenticationPrincipal UserDetailsImpl userDetails){
        model.addAttribute("username",userDetails.getUsername());
        return "index";
    }
}

로그인이 된 후 인증된 username정보가 담긴 Model 객체를 index.html Viewer에 전달하며 넘어간다.

 

HTTP : 클라이언트와 서버 간의 요청 응답 프로토콜이다. HTTP 메소드에 GETPOST가 있다.

GET : 전송할 때 필요한 데이터를 body에 담지 않고 쿼리스트링을 통해 전송. 쿼리스트링이란 url 끝에 ? 와 함께 name value를 붙여 보내는 것.

POST : 리소스 생성 및 변경을 위해 설계되었기 때문에 데이터를 HTTP body에 담아서 전송. GET의 쿼리스트링 전송 방법과 달리 눈에 보이지 않는다. 하지만 개발자 도구나 툴을 이용하면 데이터 확인이 가능하다.

 

@GetMapping : HTTP GET 요청 처리

 

Model객체 : Controller에서 생성한 데이터를 View로 전달할 때 사용하는 객체. addAttribute(“”,””) 메소드를 사용해서 Model 객체 셋팅

 

@AuthenticationPrincipal : @AuthenticationPrincipal 어노테이션을 통해 UserDetails 인터페이스를 구현한 SecurityUser 객체 속에 들어있는 principal 필드(인증 ID)를 가져올 수 있다.

Authentication : 인증 정보 인터페이스.

Principal : 인증 ID

설명 참고 : https://sas-study.tistory.com/363

 

*UserController

package com.spring.security.controller;

import com.spring.security.dto.SignupRequestDto;
import com.spring.security.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;


@Controller
public class UserController {

    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

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

    @GetMapping("/user/login/error")
    public String loginError(Model model){
        model.addAttribute("loginError",true);
        return "login";
    }

    //회원가입페이지
    @GetMapping("/user/signup")
    public String signup(){
        return "signup";
    }

    //회원가입 요청 처리
    @PostMapping("/user/signup")
    public String registerUser(SignupRequestDto requestDto){
        userService.registerUser(requestDto);
        return "redirect:/";
    }

}

@Autowired : 각 상황에 맞는 loC컨테이너 안에 존재하는 Bean객체를 자동으로 주입

 

-       의존성 주입

필요한 객체를 직접 생성하는 것이 아닌 외부로 부터 필요한 객체를 받아서 사용하는 것

즉, 서비스가 사용할 의존객체를 직접 만들어서 사용 하는 것이 아니라

어떤 장치(생성자등)을 통해서 주입을 받아서 사용하는 방법

 

-       스프링 IoC 컨테이너를 사용하는 이유

이 컨테이너 안에 있는 객체들을 Bean들이라고 함

IoC기능을 제공하는 Bean들을 담고 있기 때문에 

(IoC란 객체가 내부적으로 조작할 객체를 직접 생성하지 않고 외부로부터 주입받는 기법)

출처 : https://galid1.tistory.com/512

 

*SignupRequestDto

package com.spring.security.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class SignupRequestDto {
    private String username;
    private String password;
    private String email;
    private boolean admin=false;
    private String adminToken="";
}

@Getter : get메소드 역할. 접근자

@Setter : set메소드 역할. 설정자

public class TestVO {
    
    @Getter @Setter
    private String name;
    private String tel;
}

한 필드레벨에서만 접근자/설정자를 생성

@Getter
@Setter
public class TestVO {
 
    private String name;
    private String tel;
}

클래스레벨에서 모든필드에 적용되는 접근자/설정자를 생성

 

*Timestamped

package com.spring.security.model;

import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)// 생성/변경 시간을 자동으로 업데이트합니다.
public abstract class Timestamped {

    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime modifiedAt;
}

@MappedSuperclass : 객체 입장에서 DB 테이블 들에 공통적으로 쓰여야 하는 속성일 때 사용

다음 그림에서 id와 name 컬럼이 동일하게 각 테이블에서 사용된다. 이때 id와 name을 공통 매핑 정보 BaseEntity로 두고 상속받아 사용한다.

(DB 테이블에 반영되는 것이 아니다. 두번째 사진을 보면 테이블 각각에 id, name 정보가 존재하는데 BaseEntity기준으로 볼때 공통 상속 속성을 가지고 있다는 것이다.)

출처 : https://ict-nroo.tistory.com/129

@EntityListeners : 특정 시점에 로직을 처리하여 생성/변경 시간 자동으로 업데이트한다.

 

*User

package com.spring.security.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;

@Getter
@Setter
@NoArgsConstructor//기본 생성자 생성
@Entity
public class User extends Timestamped{
    public User(String username, String password,String email,UserRole role){
        this.username=username;
        this.password = password;
        this.email = email;
        this.role = role;
        this.kakaoId=null;
    }


    @GeneratedValue(strategy = GenerationType.AUTO)//id가 자동으로 생성 및 증가
    @Id
    private Long id;

    //null값 불가
    @Column(nullable = false)
    private String username;
    @Column(nullable = false)
    private String password;
    @Column(nullable = false)
    private String email;

    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING)
    private UserRole role;
}

@NoArgsConstructor : 파라미터가 없는 기본 생성자 만들어준다.

@AllArgsConstructor : 모든 필드 값을 파라미터로 받는 생성자를 만들어준다.

@RequiredArgsConstructor : final이나 @NonNull인 필드(공란이 되면 안되는 필수적으로 값이 있어야 하는 필드) 값만 파라미터로 받는 생성자를 만들어준다.

@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
public class User {
  private Long id;
  @NonNull
  private String username;
  @NonNull
  private String password;
  private int[] scores;
}

 

User user1 = new User();
User user2 = new User("dale", "1234");
User user3 = new User(1L, "dale", "1234", null);

 

@Entity : DB 테이블과 1:1로 매칭되는 객체 단위이며 Entity 객체의 인스턴스 하나가 테이블에서 하나의 레코드 값을 의미하며 테이블과 매핑할 클래스에 붙여줘야한다.

@Entity 속성

출처 : https://gwonbookcase.tistory.com/37

@Entity가 붙은 클래스에는 다음 제약사항이 필요하다.

  1. 필드에 final, enum, interface, class를 사용할 수 없다.
  2. 생성자중 기본 생성자가 반드시 필요하다.

@GeneratedValue(strategy = GenerationType.AUTO) : JPA 자동생성 기본키

> strategy = GenerationType.IDENTITY) : 기본키 생성 데이터베이스에 위임 atuo_increment

@Enumerated : enum(열거형)의 값을 Entity필드 값으로 사용하기 위해 사용

> EnumType.ORDINAL : enum 순서 값을 DB에 저장

> EnumType.STRING : enum 이름을 DB에 저장

enum Gender {

    MALE,

    FEMALE;

}

@Enumerated(value=EnumType.ORDINAL)

private Gender gender; -> gender.MALE값을 DB에 저장할 경우 순서 1이 저장된다.

 

@Enumerated(value=EnumType.STRING)

private Gender gender; -> gender.MALE값을 DB에 저장할 경우 “MALE”문자열이 저장된다.

 

*UserRole

package com.spring.security.model;

public enum UserRole {
    USER, //사용자권한
    ADMIN //관리자권한
}

enum : 열거형 자료형

 

 

*UserRepository

package com.spring.security.repository;

import com.spring.security.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User,Long> {
    Optional<User> findByUsername(String username);
}

Repository 인터페이스에 JpaRepository<Entity,기본키 타입> extends(상속)시켜줌

Optional<T:엔티티> : null 값을 가질 수 있는 경우에 사용. nullPointException이 발생하는 걸 방지

 

*UserDetailsImpl

package com.spring.security.security;

import com.spring.security.model.User;
import org.hibernate.cache.spi.entry.CollectionCacheEntry;
import org.hibernate.cache.spi.entry.StructuredCacheEntry;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Collections;

public class UserDetailsImpl implements UserDetails {
    private final User user;

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

    public User getUser(){
        return user;
    }
    @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;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities(){
        return Collections.emptyList();
    }

}

UserDetails 를 상속(implements)받아 구현하는 클래스. 인증/인가에 사용

 

UserDetails : Spring Security에서 사용자의 정보를 담는 인터페이스. 이 인터페이스를 구현하게 되면 spring security에서 인증/인가 작업을 할 때 여기에 있는 사용자 정보(SecurityUser)를 이용한다.

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

 

 

*UserDetailsServiceImpl

package com.spring.security.security;

import com.spring.security.model.User;
import com.spring.security.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
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
public class UserDetailsServicelmpl implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
        User user=userRepository.findByUsername(username)
                .orElseThrow(()->new UsernameNotFoundException("Can't find"+username));

        return new UserDetailsImpl(user);
    }
}

UserDetailsService인터페이스 구현한 클래스. DB에서 유저 정보를 가지고 오는 역활

> loadUserByUsername() : DB에서 유저 정보를 불러오늘 역할을 하는 메소드. CustomUserDetails형으로 사용자 정보를 가지고 온다. 정보의 유/무에 따라 예외 혹은 사용자 정보를 리턴

 

->UserDetailsService인터페이스로 DB에서 조회된 회원 정보를 UserDetails에 담아 AuthenticationManager에서 인증/인가 작업을 하는데 사용하는 것이 Spring Security 로그인 처리 과정이다.

 

@Service : 루트 컨테이너에 Bean 객체로 생성

 

자세한 설명 참조 : https://codevang.tistory.com/258

 

*WebSecurityConfig

package com.spring.security.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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;

@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder encodePassword(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception{
        return super.authenticationManagerBean();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.headers().frameOptions().disable();

        http.authorizeRequests()
                .antMatchers("/user/**").permitAll()
                //image폴더를 login없이 허용
                .antMatchers("/images/**").permitAll()
                //css폴더를 login없이 허용
                .antMatchers("/css/**").permitAll()
                //그 외 모든 요청은 인증과정 필요
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/user/login")
                .failureUrl("/user/login/error")
                .defaultSuccessUrl("/")
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/user/logout")
                .permitAll();
    }
}

권한에 따라 경로를 제한한다.

-antMatchers : 경로설정할때 여러 경로를 다루게 되는데 특수한 경우를 위에 두고 아래쪽으로 갈수록 일반적인 경우를 두어야 경로가 꼬이거나 무한루프 발생가 발생 안함

> /** : 모든 디렉토리와 파일을 의미

> /images/** : images디렉토리 밑에 있는 모든 디렉토리와 파일을 의미)

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

.formLogin()//Form 로그인 인증 기능이 작동함

.loginPage("/login.html")//사용자 정의 로그인 페이지

.failureUrl("/login.html?error=true")// 로그인 실패 후 이동 페이지

.defaultSuccessUrl("/home")//로그인 성공 후 이동 페이지

.permitAll(); //사용자 정의 로그인 페이지 접근 권한 승인

.logout()//로그아웃 처리

.logoutSuccessUrl("/login")//로그아웃 성공 후 이동페이지

 

http.csrf() : 기본 활성화

http.csrf().disabled() : 비활성화

출처 : https://to-dy.tistory.com/72?category=720806  

csrf : 사이트 간 요청 위조

csrf Filter : 요청 시 전달되는 토큰 값과 서버에 저장된 실제 값과 비교한 후 일치하지 않으면 요청 실패 처리

스피링 시큐리티 : https://to-dy.tistory.com/72?category=720806 이 블로그 참고 (설명이 좋음)

'Web > Spring boot' 카테고리의 다른 글

spring boot 소셜 카카오 로그인 로그아웃  (0) 2022.01.09

관련글 더보기