@Entity

엔티티 클래스 애노테이션

데이터베이스에 저장(persist)할 자바 객체를 정의

- 다양한 애노테이션을 이용해 보다 자세한 테이블 스키마 정보를 표현

- 애노테이션으로 표현한 스키마 정보와 실제 테이블 스키마가 완벽히 일치해야 할 필요는 없음

- 하나의 도메인(domain)으로 간주

 

@Entity 클래스 안에서 사용되는 주요 JPA 애노테이션

- @Table, @Index, @UniqueConstraint: 테이블 기본 정보와 인덱스, unique 키를 설정

- @Id, @GeneratedValue: primary key 설정

- @Column: 각 컬럼 설정

- @Enumerated: enum 을 처리하는 방법을 설정

- @Transient: 특정 필드를 DB 영속 대상에서 제외

- @OneToOne, @OneToMany, @ManyToOne, @ManyToMany: 연관 관계 설정

- @MappedSuperClass: 상속을 이용한 공통 필드 정의

- @Embedded, @Embeddable: 클래스 멤버를 이용한 공통 필드 정의

- @DataTimeFormat: 스프링에서 제공하는 애노테이션, 날짜 입력의 포맷을 지정

 

 

@Entity: JPA엔티티의 lifecycle event를 활용한 Auditing(생성자, 생성일자, 수정자, 수정일자) 테크닉

JPA엔티티에 생성일시, 수정일시 같이 일정하게 작성하는 메타데이터를 처리 가능

- @PrePersist

- @PostPersist

- @PreRemove

- @PostRemove

- @PreUpdate

- @PostUpdate

- @PostLoad

 

@Entity: Spring JPA Auditing 애노테이션

엔티티의 생성일시, 수정일시, 생성자, 수정자를 자동으로 관리해주는 애노테이션

설정

- @EnableJpaAuditing

- @EntityListeners(AuditingEntityListener.class)

활용

- @CreatedBy

- @CreatedDate

- @LastModifiedBy

- @LastModifiedDate

 

'스프링' 카테고리의 다른 글

spring boot 에서 google login을 사용해보았다.  (0) 2021.12.18
Spring 웹 계층  (0) 2021.12.14
스프링 서버 단에서 데이터 처리하는 방식  (0) 2021.11.27
Auditing @CreatedDate @LastModifiedDate  (0) 2021.11.27
h2 console  (0) 2021.11.27

로그인 기능을 사용하기 위해서 oauth2 를 임포트 합니다.

compile('org.springframework.boot:spring-boot-starter-oauth2-client')

깃 이그노어 파일에 아래와같이 인증 키 정보가 담긴 application-oauth 파일을 제거합니다.

인증 키의 노출은 보안에 위험하기 때문입니다.

application-oauth.properties

application.properties에서 oauth 사용을 허락합니다.

spring.profiles.include=oauth

index.mutache에 아래에 해당되는 파일을 추가합니다. 

                {{#userName}}
                    Logged in as: <span id="user">{{userName}}</span>
                    <a href="/logout" class="btn btn-info active" role="button">Logout</a>
                {{/userName}}
                {{^userName}}
                    <a href="/oauth2/authorization/google"
                       class="btn btn-success active" role="button">Google Login</a>
                {{/userName}}

{{}} 문법은 머스테치 문법입니다. 컨트롤러에서 index.mustache 파일을 호출할때 userName 정보를 index.mustache파일에 넘겨주게 되면 로그인한 유저 정보와 logout 버튼을 나타내고, userName이 넘어오지않게 된다면 {{^userName}}이 실행되게 됩니다. ^표식이 들어가면 없을때 안의 값을 반환합니다.

 

 

package com.bell_bell.book.springboot.config.auth;

import com.bell_bell.book.springboot.config.auth.dto.OAuthAttributes;
import com.bell_bell.book.springboot.config.auth.dto.SessionUser;
import com.bell_bell.book.springboot.domain.user.User;
import com.bell_bell.book.springboot.domain.user.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpSession;
import java.util.Collections;

@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    private final UserRepository userRepository;
    private final HttpSession httpSession;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = delegate.loadUser(userRequest);

        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
                .getUserInfoEndpoint().getUserNameAttributeName();

        OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());
        User user = saveOrUpdate(attributes);
        httpSession.setAttribute("user", new SessionUser(user));

        return new DefaultOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),
                attributes.getAttributes(),
                attributes.getNameAttributeKey());

    }


    private User saveOrUpdate(OAuthAttributes attributes) {
        User user = userRepository.findByEmail(attributes.getEmail())
                .map(entity -> entity.update(attributes.getName(), attributes.getPicture()))
                .orElse(attributes.toEntity());

        return userRepository.save(user);
    }
}

위 클래스 CustomOAuth2UserService에는 loadUser와 saveOrUpdate 메소드가 존재합니다.

loadUser 메소드는 구글 로그인 이후 호출되게 됩니다. 해당 클래스는SecurityConfig 에서 호출합니다. loadUser가 호출되면 loadUser 내부에는 saveOrUpdate가 실행되게 됩니다. userRequest에는 유저 이름과 아이디에 관한 정보가 포함되 있는 것으로 보이며 해당 정보를 saveOrUpdate 함수에게 전달합니다. saveOrUpdate 는 userRepository에게 해당 유저가 존재하는지 여부를 물어보고 존재 유무는 해당 유저의 이메일로 유저를 찾게 됩니다. orElse의 기능으로 인해서 해당 유저가 없다면 attributes.toEntity() 호출로 인해서 유저 클래스에 값을 넘기게 되고 해당 유저는 save 되는 것으로 보입니다. 

 

그냥 간단하게 요약하자면 구글 로그인이 진행되면 해당 클래스에 있는 메소드를 통해서 httpSession에 유저 정보를 저장하고 User table에 유저 정보를 저장하는 거로 보면 됩니다.

 

import com.bell_bell.book.springboot.domain.user.Role;
import com.bell_bell.book.springboot.domain.user.User;
import lombok.Builder;
import lombok.Getter;

import java.util.Map;

@Getter
public class OAuthAttributes {
    private Map<String, Object> attributes;
    private String nameAttributeKey;
    private String name;
    private String email;
    private String picture;

    @Builder
    public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey, String name,
                           String email, String picture) {
        this.attributes = attributes;
        this.nameAttributeKey = nameAttributeKey;
        this.name = name;
        this.email = email;
        this.picture = picture;
    }

    public static OAuthAttributes of(String registrationId, String userNameAttributeName,
                                     Map<String, Object> attributes) {
        return ofGoogle(userNameAttributeName, attributes);
    }

    private static OAuthAttributes ofGoogle(String userNameAttributeName, Map<String, Object> attributes) {
        return OAuthAttributes.builder()
                .name((String) attributes.get("name"))
                .email((String) attributes.get("email"))
                .picture((String) attributes.get("Picture"))
                .attributes(attributes)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    public User toEntity() {
        return User.builder()
                .name(name)
                .email(email)
                .picture(picture)
                .role(Role.GUEST)
                .build();
    }
}

위 클래스는 dto에 속하는 클래스로 각 계증간 유저 정보를 주고 받을 때 사용됩니다.

toEntity를 자세히 보면 .role(Role.GUEST)로 되어있습니다. 이는 가입할 때 기본 권한을 GUEST로 주기 위해서 role 빌더 값에는 Role.GEUSET를 사용합니다.

 

package com.bell_bell.book.springboot.config.auth.dto;

import com.bell_bell.book.springboot.domain.user.User;
import lombok.Getter;

import java.io.Serializable;

@Getter
public class SessionUser implements Serializable {
    private String name;
    private String email;
    private String picture;

    public SessionUser(User user) {
        this.name = user.getName();
        this.email = user.getEmail();
        this.picture = user.getPicture();
    }
}

위 클래스가 사용되는 곳을 살펴보겠습니다.

        httpSession.setAttribute("user", new SessionUser(user));

위와 같이 사용되고 있고 httpSession에 유저 정보를 저장 시키게 하기 위해서 사용 되는 것으로 보입니다.

 

httpSession에 유저 정보를 저장할 때 왜 User 클래스 대신 SessionUser를 사용하는 걸까요? httpSession에 User를 담기위해서는 Serialize(직렬화)를 사용해야 되는데 User를 직렬화 하게 되면 나중에 자식 엔티티가 생겨서 관계를 가지게 되면 직렬화 대상에 자식까지 포함되어서 성능 이슈, 부수 효가가 발생할 수 있기 때문입니다. 

 

다음으로 아래 클래스 정보를 알아보겠습니다.

package com.bell_bell.book.springboot.config.auth;

import com.bell_bell.book.springboot.domain.user.Role;
import lombok.RequiredArgsConstructor;
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;

@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final CustomOAuth2UserService customOAuth2UserService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .headers().frameOptions().disable()
                .and()
                    .authorizeRequests()
                    .antMatchers("/", "/css/**", "/images/**",
                            "/js/**", "/h2-console/**").permitAll()
                    .antMatchers("/api/v1/**").hasRole(Role.USER.name())
                    .anyRequest().authenticated()
                .and()
                    .logout()
                        .logoutSuccessUrl("/")
                .and()
                    .oauth2Login()
                        .userInfoEndpoint()
                            .userService(customOAuth2UserService);
    }
}

1. @EnableWebSecurity

 - Spring Security 설정들을 활성화시켜 줍니다.

 

2. csrf().disable().headers().frameOptions().disable()

- h2-console 화면을 사용하기 위해 해당 옵션들을 disable 합니다. 

 

3. authorizeRequests

- URL별 권한 관리를 설정하는 옵션의 시작지점입니다. 

- authorizeRequests가 선언되어야만 antMatchers 옵션을 사용할 수 있습니다. 

 

4. andMachers

- url을 지정하고 해당되는 url에게 전체 열람부터 특정 권한을 할당합니다. 

- 권한 관리 대상을 지정하는 옵션입니다.

- URL, HTTP 메소드별로 관리가 가능합니다.

- "/"등 지정된 URL들은 permitAll()옵션을 통해 전체 열람 권한을 주었습니다.

- "/api/v1/**" 주소를 가진 api는 USER 권한을 가진 사람만 가능하도록 했습니다.

 

5. anyRequest

- 설정된 값들 이외 나머지 URL들을 나타냅니다. 

- 여기서는 authenticated()을 추가하여 나머지 URL들은 모두 인증된 사용자들에게 만 허용하게 합니다.

- 인증된 사용자 즉, 로그인한 사용자들을 이야기합니다.

 

6. logout().logoutSuccessUrl("/")

 - 로그아웃 기능에 대한 여러 설정의 진입점입니다.

 - 로그아웃 성공 시 / 주소로 이동합니다.

 

7. oauth2Login

 - OAuth 2 로그인 기능에 대한 여러 설정의 진입점입니다. 

 

8. userInfoEndpoint

 - OAuth2  로그인 성공 이후 사용자 정보를 가져올 때의 설정들을 담당합니다. 

 

9. userService

 - 소셜 로그인 성공 시 후속 조치를 진행할 UserService 인터페이스의 구현체를 등록합니다. 리소스 서버(즉, 소셜 서비스들)에서 사용자 정보를 가져온 상태에서 추가로 진행하고자 하는 기능을 명시할 수 있습니다. 

'스프링' 카테고리의 다른 글

@Entity  (0) 2022.02.01
Spring 웹 계층  (0) 2021.12.14
스프링 서버 단에서 데이터 처리하는 방식  (0) 2021.11.27
Auditing @CreatedDate @LastModifiedDate  (0) 2021.11.27
h2 console  (0) 2021.11.27

해당 내용은 스프링 부트와 AWS로 혼자 구현하는 웹서비스 책 내용을 복습한 내용입니다.

 

API를 만들기 위해 총 3개의 클래스가 필요합니다.

* Request 데이터를 받을 Dto

* API 요청을 받을 Controller

* 트랜잭션, 도메인 기능 간의 순서를 보장하는 Service

tip ) service는 비지니스 로직을 처리하지 않습니다. 그 역할은 Domain이 실행합니다.

 

* Web Layer

  * 흔히 사용하는 컨트롤러(@Controller)와 JSP/Freemaker 등의 뷰 템플릿 영역입니다.

  * 이외에도 필터(@Filter), 인터셉터, 컨트롤러 어드바이스(@ControllerAdvice) 등 외부 요청과 응답에 대한 전반적인 영역을 이야기합니다.

 

* Service Layer

  * @Service에 사용되는 서비스 영역입니다.

  * 일반적으로 Controller와 Dao의 중간 영역에서 사용됩니다.

  * @Transactional이 사용되어야 하는 영역이기도 합니다.

 

* Repository Layer

  * Database와 같이 데이터 저장소에 접근하는 영역입니다.

  * 기존에 개발하셨던 분들이라면 Dao(Data Access Object) 영역으로 이해하시면 쉬울것입니다.

 

* Dtos

  * Dto(Data Transfer Object)는 계층간 데이터를 교환하기 위한 객체를 이야기하며 Dtos는 이들의 영역을 얘기합니다.

  * 예를 들어 뷰 템플릿 엔진에서 사용될 객체나 Repository Layer에서 결과로 넘겨준 객체 등이 이들을 이야기합니다.

 

* Domain Model

  * 도메인이라 불리는 개발 대상을 모든 사람이 동일한 관점에서 이해할 수 있고 공유할 수 있도록 단순화시킨 것을 도      메인 모델이라고 합니다.

 

  * 이를테면 택시 앱이라고 하면 배차, 탑승, 요금 등이 모두 도메인이 될 수 있습니다.

  * @Entity를 사용해보신 분들은 @Entity가 사용된 영역 역시 도메인 모델이라고 이해 해주시면 됩니다.

  * 다만, 무조건 데이터베이스의 테이블과 관계가 있어야만 하는 것은 아닙니다.

  * VO처럼 값 객체들도 이 영역에 해당하기 떄문입니다.

 

 

 

'스프링' 카테고리의 다른 글

@Entity  (0) 2022.02.01
spring boot 에서 google login을 사용해보았다.  (0) 2021.12.18
스프링 서버 단에서 데이터 처리하는 방식  (0) 2021.11.27
Auditing @CreatedDate @LastModifiedDate  (0) 2021.11.27
h2 console  (0) 2021.11.27

'스프링' 카테고리의 다른 글

spring boot 에서 google login을 사용해보았다.  (0) 2021.12.18
Spring 웹 계층  (0) 2021.12.14
Auditing @CreatedDate @LastModifiedDate  (0) 2021.11.27
h2 console  (0) 2021.11.27
form과 button  (0) 2021.11.21

위와 같이 특정 모델에 위 속성을 추가한 후

main 메소드가 있는 곳에 @EnableJPaAuditing 어노테이션을 추가해주면 행을 추가할 때마다 @CreatedDate 이 있는 속성 의 값이 자동으로 들어가고 @LastModifiedDate 어노테이션이 있는 속성에는 update시점의 시간을 자동으로 기록하게 된다.

'스프링' 카테고리의 다른 글

Spring 웹 계층  (0) 2021.12.14
스프링 서버 단에서 데이터 처리하는 방식  (0) 2021.11.27
h2 console  (0) 2021.11.27
form과 button  (0) 2021.11.21
스프링 프로젝트 base Url 설정  (1) 2021.11.12

h2의 경로를 콘솔로 확인하기 위해서 강의에서 처럼 아래와 같이 해보았지만 되지않았다.

강의에서는 파일 이름이 application.yml 이였지만 최근에는 application.properties로 바뀌면서 뭔가 바뀐 듯 하다. 

그래서 인터넷 검색을 통해 알게 되었는데 application.properties에서 아래와 같이 해보았다.

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

다행이 아래와 같이 경로를 확인 후 강의를 계속 들을 수 있었다.

 

<form method="post" action="/board/post">
	<button type="submit" name="register">등록</button>
</form>

코드를 짜면서 위와같이 form 안에 button 을 생성하여 사용해보았다.

재밌는 점은 버튼을 클릭하게 되면 button 의 name이 파라미터로 적용되서 url이

/board/post?register 와 같이 된다는 것이다.

'스프링' 카테고리의 다른 글

Auditing @CreatedDate @LastModifiedDate  (0) 2021.11.27
h2 console  (0) 2021.11.27
스프링 프로젝트 base Url 설정  (1) 2021.11.12
스프링 프로젝트 기본 설정하기  (0) 2021.11.12
이클립스 패키지 프리젠테이션  (0) 2021.11.10

 

진행중인 프로젝트를 우클릭 한 후 Properties를 선택한다.

 

Web Project Settings를 선택한 다음 Context root를 위와같이 /로 변경한다.

 

그다음에 톰캣 서버를 우클릭 후 clean을 눌러준다음 테스트하면

월래는 /mvc가 달려있었는데 사라진 것을 확인할 수 있다.

'스프링' 카테고리의 다른 글

h2 console  (0) 2021.11.27
form과 button  (0) 2021.11.21
스프링 프로젝트 기본 설정하기  (0) 2021.11.12
이클립스 패키지 프리젠테이션  (0) 2021.11.10
Spring Legacy Project 를 처음 만들며 느낀점  (1) 2021.11.07

스프링 프로젝트에 필요한 기본 설정은 한글을 읽게 하기 위해 인코딩 방식을 UTF-8 방식을 쓰게끔 만드는 것이다. 

상당 메뉴에 Window -> Preferences를 선택한다.

상단메뉴의 Window -> Preferences
General -> Workspace -> Text file encoding(UTF-8)
원하는 프로젝트의 인코딩 방식이 UTF-8이 되었는지 체크한다.
자바 버전을 1.8로 설정한다.

src -> main -> weaapp -> WEB-INF -> web.xml 파일을 열고 </servlet-mapping> 태그 아래에 아래의 코드를 추가한다.

<!--  문자 인코딩  시작 -->
<filter>
  <filter-name>encodingFilter</filter-name>
  <filter-class>
    org.springframework.web.filter.CharacterEncodingFilter
  </filter-class>
  <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
  </init-param>
  <init-param>
    <param-name>forceEncoding</param-name>
    <param-value>true</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>encodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
<!--  문자 인코딩  끝 -->

src -> main -> webapp -> WEB-INF -> views -> home.jsp 파일을 열고 <html>태그 위에 아래 코드를 추가한다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

pom.xml에서 자바 버전 1.6을 1.8로 바꾼다.

 

'스프링' 카테고리의 다른 글

h2 console  (0) 2021.11.27
form과 button  (0) 2021.11.21
스프링 프로젝트 base Url 설정  (1) 2021.11.12
이클립스 패키지 프리젠테이션  (0) 2021.11.10
Spring Legacy Project 를 처음 만들며 느낀점  (1) 2021.11.07

com.spring.mvc 패키지 하위 경로에 controller 패키지를 생성 후 그안에 controller 클래스를 넣기 위해서 위와같이 패키지 표기 형식을 Hierarchical로 변환 해주어야 한다.

'스프링' 카테고리의 다른 글

h2 console  (0) 2021.11.27
form과 button  (0) 2021.11.21
스프링 프로젝트 base Url 설정  (1) 2021.11.12
스프링 프로젝트 기본 설정하기  (0) 2021.11.12
Spring Legacy Project 를 처음 만들며 느낀점  (1) 2021.11.07

+ Recent posts