네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'전체 글'에 해당되는 글 194건

[Spring] Assert


반응형

Assert는 단순히 if문을 줄이는 역할만 하는 것이 아니다. 프로젝트 규칙을 적용하고 공통을 재사용한다는 것에 큰 의미가 있다. 

 

Spring Assert를 사용하는 목적

Spring Assert는 인수를 검증하고 조건에 맞지 않는 경우 IllegalArgumentException 또는 IllegalStateException를 발생시킨다. 

이 부분은 조건문을 단순화하고 반복적인 코드를 줄이는 역할을 한다.

if (user == null) {
    throw new IllegalArgumentException("사용자 정보가 존재하지 않습니다.");
}
// 위 코드는 아래와 같이 바꿀 수 있다.
Assert.notEmpty(user, "사용자 정보가 존재하지 않습니다.");

 

Assert의 확장

값을 검증하는 방법을 여러 형태로 만들어 낼 수 있다. 또한 exception도 다양하게 사용할 수 있다. 그런데 프로젝트를 하다보면 값을 검증하는 방법을 통합하거나 예외의 사용을 제한할 필요가 있다. 

 

public class JiyoonAssert extends Assert {
 
    private static final String NUMBER_ALPHABET_PATTERN = "^[a-zA-Z\\d]+$";
    private static final String NUMERIC_PATTERN = "^[\\+\\-]{0,1}[\\d]+$";
    private static final String FLOAT_PATTERN = "^[\\+\\-]{0,1}[\\d]+[\\.][0-9]+$";
 
    /**
     * Assert a boolean expression, TRUE가 아닌 경우 사용자가 정의한 예외를 던진다.
     * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. 
     * <pre class="code">Assert.state(id == null, "The id property must not already be initialized", EbloInvalidRequestException.class);</pre>
     * @param expression a boolean expression
     * @param message the exception message to use if the assertion fails
     * @param exceptionClass
     * @throws IllegalStateException if {@code expression} is {@code false}
     */
    public static void state(boolean expression, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        if (!expression) {
            throwException(message, exceptionClass);
        }
    }
 
    /**
     * Assert a boolean expression, TRUE가 아닌 경우 사용자가 정의한 예외를 던진다.
     * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. 
     * <pre class="code">Assert.isTrue(user != null, "사용자 정보가 존재하지 않습니다.", EbloNotFoundException.class);</pre>
     * @param expression a boolean expression
     * @param message the exception message to use if the assertion fails
     * @param exceptionClass
     * @throws IllegalStateException if {@code expression} is {@code false}
     */
    public static void isTrue(boolean expression, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        if (!expression) {
            throwException(message, exceptionClass);
        }
    }
 
    /**
     * Assert that an object is {@code null}. 객체가 null이 아닌 경우 사용자가 정의한 예외를 던진다.
     * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. 
     * <pre class="code">Assert.isNull(user, "기존 사용자 정보가 존재합니다.", EbloInvalidRequestException.class);</pre>
     * @param expression a boolean expression
     * @param message the exception message to use if the assertion fails
     * @param exceptionClass
     * @throws IllegalStateException if {@code expression} is {@code false}
     */
    public static void isNull(@Nullable Object object, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        if (object != null) {
            throwException(message, exceptionClass);
        }
    }
 
    /**
     * null인 경우 사용자 정의 예외 발생
     * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. 
     * <pre class="code">Assert.notNull(user, "사용자 정보가 존재하지 않습니다.", EbloNotFoundException.class);</pre>
     * @param object
     * @param message
     * @param exceptionClass
     */
    public static void notNull(@Nullable Object object, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        if (object == null) {
            throwException(message, exceptionClass);
        }
    }
 
    /**
     * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생
     * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. 
     * <pre class="code">Assert.hasLength(value, "전달 받은 값이 빈값입니다.", EbloInvalidRequestException.class);</pre>
     * @param object
     * @param message
     * @param exceptionClass
     */
    public static void hasLength(@Nullable String text, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        if (!StringUtils.hasLength(text)) {
            throwException(message, exceptionClass);
        }
    }
 
    /**
     * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생
     * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. 
     * <pre class="code">Assert.hasText(value, "전달 받은 값이 빈값입니다.", EbloInvalidRequestException.class);</pre>
     * @param object
     * @param message
     * @param exceptionClass
     */
    public static void hasText(@Nullable String text, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        if (!StringUtils.hasText(text)) {
            throwException(message, exceptionClass);
        }
    }
 
    /**
     * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생
     * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. 
     * <pre class="code">Assert.notEmpty(array, "전달 받은 값이 빈값입니다.", EbloInvalidRequestException.class);</pre>
     * @param object
     * @param message
     * @param exceptionClass
     */
    public static void notEmpty(@Nullable Object[] array, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        if (ObjectUtils.isEmpty(array)) {
            throwException(message, exceptionClass);
        }
    }
 
    /**
     * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생
     * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. 
     * <pre class="code">Assert.notEmpty(collection, "전달 받은 값이 빈값입니다.", EbloInvalidRequestException.class);</pre>
     * @param object
     * @param message
     * @param exceptionClass
     */
    public static void notEmpty(@Nullable Collection<?> collection, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        if (CollectionUtils.isEmpty(collection)) {
            throwException(message, exceptionClass);
        }
    }
 
    /**
     * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생
     * 사용자 정의 예외는 AbstractEbloBaseException.class를 상속 받아 구현해야 한다. 
     * <pre class="code">Assert.notEmpty(map, "전달 받은 값이 빈값입니다.", EbloInvalidRequestException.class);</pre>
     * @param object
     * @param message
     * @param exceptionClass
     */
    public static void notEmpty(@Nullable Map<?, ?> map, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        if (CollectionUtils.isEmpty(map)) {
            throwException(message, exceptionClass);
        }
    }
 
    private static void throwException(String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        try {
            throw exceptionClass.getDeclaredConstructor( String.class ).newInstance( message );
        } catch (Exception e) {
            e.printStackTrace();
            throw new EbloSystemException("예외 처리 중 오류가 발생했습니다. "+e.getMessage());
        }
    }
    
    /**
     * 값이 영문 알파벳, 숫자가 아닌 경우 예외 발생 
     * @param object
     * @param message
     */
    public static void isAlphaNumber(String object, String message) {
        isMatched(object, NUMBER_ALPHABET_PATTERN, message, EbloInvalidRequestException.class);
    }
 
    /**
     * 값이 영문 알파벳, 숫자가 아닌 경우 사용자 정의 예외 발생 
     * @param object
     * @param message
     * @param exceptionClass
     */
    public static void isAlphaNumber(String object, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        isMatched(object, NUMBER_ALPHABET_PATTERN, message, exceptionClass);
    }
    
    /**
     * 값이 숫자가 아닌 경우 예외 발생 
     * @param object
     * @param message
     */
    public static void isNumeric(String object, String message) {
        isMatched(object, NUMERIC_PATTERN, message, EbloInvalidRequestException.class);
    }
    
    /**
     * 값이 숫자가 아닌 경우 사용자 정의 예외 발생 
     * @param object
     * @param message
     * @param exceptionClass
     */
    public static void isNumeric(String object, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        isMatched(object, NUMERIC_PATTERN, message, exceptionClass);
    }
    
    /**
     * 값이 float, double이 아닌 경우 예외 발생 
     * @param object
     * @param message
     */
    public static void isFloat(String object, String message) {
        isMatched(object, FLOAT_PATTERN, message, EbloInvalidRequestException.class);
    }
    
    /**
     * 값이 float, double이 아닌 경우 사용자 정의 예외 발생 
     * @param object
     * @param message
     * @param exceptionClass
     */
    public static void isFloat(String object, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        isMatched(object, FLOAT_PATTERN, message, exceptionClass);
    }
    
    /**
     * 패턴 매치, 해당 패턴과 일치 하지 않는 경우 사용자 정의 예외 발생 
     * @param object
     * @param pattern
     * @param message
     * @param exceptionClass
     */
    public static void isMatched(String object, String pattern, String message, final Class<? extends AbstractEbloBaseException> exceptionClass) {
        if(object == null || "".equalsIgnoreCase(object)) return;
        
        if(!object.matches(pattern)) {
            throwException(message, exceptionClass);
        }
    }
 
    /**
     * 패턴 매치, 해당 패턴과 일치 하지 않는 경우 예외 발생 
     * @param object
     * @param pattern
     * @param message
     */
    public static void isMatched(String object, String pattern, String message) {
        if(object == null || "".equalsIgnoreCase(object)) return;
        
        if(!object.matches(pattern)) {
            throwException(message, EbloInvalidRequestException.class);
        }
    }
    
}
@Test
public void test() {
    JiyoonAssert.isTrue( 1 > 0, "test");
}

 

 

 

 

 출처 

https://eblo.tistory.com/63

 

Spring boot - Assert 사용 예제

1. Overview Assert는 단순히 if문을 줄이는 역할만 하는 것은 아닙니다. 프로젝트 규칙을 적용하고 공통을 재사용한다는 것에 큰 의미가 있습니다. 복잡한 기술이 필요한 것도 아니고 누구나 쉽게

eblo.tistory.com

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

https://frogand.tistory.com/190

 

지난 포스팅에 @CurrentUser란 커스텀 어노테이션을 만들면서 @Target, @Retention을 사용했습니다.

 

meta-annotation

메타 어노테이션은 다른 어노테이션에서도 사용되는 어노테이션을 말하며, 커스텀 어노테이션을 생성할 때 주로 생성된다.

 

@Target

@Target은 자바 컴파일러가 어노테이션이 어디에 적용될지 결정하기 위해 사용한다.

예를들어 Spring의 @Service 어노테이션의 ElementType.Type은 해당 어노테이션은 타입 선언 시 사용한다는 의미다.

 

ElementType.PACKAGE : 패키지 선언

ElementType.TYPE : 타입 선언

ElementType.ANNOTATION_TYPE : 어노테이션 타입 선언

ElementType.CONSTRUCTOR : 생성자 선언

ElementType.FIELD : 멤버 변수 선언

ElementType.LOCAL_VARIABLE : 지역 변수 선언

ElementType.METHOD : 메서드 선언

ElementType.PARAMETER : 전달 인자 선언

ElementType.TYPE_PARAMETER : 전달인자 타입 선언

ElementType.TYPE_USE : 타입 선언

 

@Retention

@Retention은 어노테이션이 실제로 적용되고 유지되는 범위를 의미한다.

 

RetentionPolicy.RUNTIME : 컴파일 이후에도 JVM에 의해 계속 참조가 가능하다. 주로 리플렉션이나 로깅에 많이 사용한다.

RetentionPolicy.CLASS : 컴파일러가 클래스를 참조할 때까지 유효하다.

RetentionPolicy.SOURCE : 컴파일 전까지만 유효한다. 즉, 컴파일 이후에는 사라지게 된다.

 

 

 출처 

https://sanghye.tistory.com/39

 

[Spring] Meta Annotation 이란?(@Target, @Retention)

Spring 에서는 Anntotation 사용에 대한 기능을 상당히 많이 제공하고 있습니다. 주로 사용하는 @Controller, @Service, @Repostiroy 등 많은 Annotation 이 존재합니다. 해당 Annotion 은 각 기능에 필요한 만큼..

sanghye.tistory.com

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

redirect를 사용하여 파라미터를 넘겨줘야하는 경우가 있다.

RedirectAttributes 인터페이스를 통해 전달해주는데

파라미터를 담어서 넘겨줄 때 사용하는 함수는 총 3개로 

- addAttribute

- addAllAttributes

- addFlashAttribute

이다.

 

addAttribute

흔히 사용하는 addAttribute는 해당 페이지로 리다이렉트할 때 값을 넘겨주는 용도로 사용한다.

addAttribute를 사용하면 URL 뒤에 붙어 값이 유지가 된다.

 

addFlashAttribute

하지만 addFlashAttribute로 전달한 값은 URL에 존재하지 않는다. 일회성으로 URL에 붙지 않고 세션 후 재지정 요청이 들어오면 값이 사라지는 휘발성 성질을 가지고 있다.

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

@AuthenticationPrincipal

 

@GetMapping("/")
public String index(Model model, Principal principal) {

    if (principal == null) {
        model.addAttribute("message", "Spring security");
    } else {
        model.addAttribute("message", "Hello, " + principal.getName());
    }
    
    return "index";
}

로그인한 사용자의 정보를 파라미터로 받아오고 싶을 때, Java 표준 Principal 객체를 받아서 사용한다. Java 표준 Principal 객체는 name 정보만 참조할 수 있다.

 

@AuthenticationPrincipal 어노테이션을 사용하면 UserDetailsService에서 Return한 객체를 파라미터로 직접 받아 사용할 수 있다.

@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal User user) {

    if (user == null) {
        model.addAttribute("message", "Spring security");
    } else {
        model.addAttribute("message", "Hello, " + user.getName());
    }
    
    return "index";
}

현재 로그인한 사용자의 정보를 참조하고 싶을 때 도메인의 User를 나타내는 객체 (Account)를 직접 사용하고 싶다는 요구사항이 있다면?

 

Adapter 클래스

- UserDetailsService에서 리턴하는 타입을 변경하면, Controller에서 @AuthenticationPrincipal로 받아올 수 있는 객체가 변경된다.

- 이때 사용할 수 있는 방법은 두가지이다.

1. Account 객체를 직접 리턴하기

2. Account 객체를 감싸는 Adapter 클래스를 사용하기

 

Account 객체를 직접 리턴하는 방법은 Account 객체가 UserDetails를 구현해야 한다.

도메인 객체는 특정 기술에 종속되지 않도록 개발하는 것이 좋다.

 

AccountAdapter.java

@Getter
public class AccountAdapter extends User {

    private Account account;

    public AccountAdapter(Account account) {
        super(account.getUserId(), account.getPassword(), authorities(account.getRoles()));
        this.account = account;
    }

    private static Collection<? extends GrantedAuthority> authorities(Set<AccountRole> roles) {
        return roles.stream()
                .map(r -> new SimpleGrantedAuthority(r.getRole().authority()))
                .collect(Collectors.toSet());
    }
}

- User 클래스를 상속받는다.

- AccountAdapter의 멤버는 오로지 Account 객체만 존재한다.

- 생성자 내부에서 User 클래스의 생성자를 호출하여 username, password, role을 세팅한다.

 

User 클래스를 상속받는 이유는?

- UserDetailsService에서 리턴하는 객체는 UserDetails 타입이어야 한다.

- UserDetails를 구현하는 User 클래스를 상속받는 방식으로 사용한다.

 

@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account account = accountRepository.findByUserId(username);
        if (account == null) {
            throw new UsernameNotFoundException("user not found");
        }

        return new AccountAdapter(account);
    }

 

AccountAdapter 사용하기

@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal AccountAdapter accountAdapter) {

    if (accountAdapter == null) {
        model.addAttribute("message", "Spring security");
    } else {
        model.addAttribute("message", "Hello, " + accountAdapter.getAccount().getUserId());
    }
    
    return "index";
}

accountAdapter.getAccount()로 Account 객체를 참조하지 말고 Account 객체를 직접 사용하도록 하자.

 

Account 객체 직접 사용하기

@AuthenticationPrincipal은 SpEL을 지원한다.

SpEL을 사용해서 Adapter 클래스가 아닌 Account 객체를 직접 가져올 수 있다.

@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal(expression = "#this == 'anonymousUser ? null : account") Account account) {

    if (account == null) {
        model.addAttribute("message", "Spring security");
    } else {
        model.addAttribute("message", "Hello, " + account.getUserId());
    }
    
    return "index";
}

만약 현재 참조중인 객체가 AnonymousAuthenticaionFilter에 의해 생성된 Authentication인 경우 null을 반환하고, 아니라면 AccountAdapter 객체로 간주하고 Account 객체를 반환한다.

 

@CurrentUser

SpEL을 사용해서 직접 Account 객체를 가져오긴 했지만 해당 코드가 너무 길다.

- 커스텀 어노테이션을 생성하여 해결하자

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
public @interface CurrentUser {
}
@GetMapping("/")
public String index(Model model, @CurrentUser Account account) {

    if (account == null) {
        model.addAttribute("message", "Spring security");
    } else {
        model.addAttribute("message", "Hello, " + account.getUserId());
    }
    
    return "index";
}

 

 

 정리 

- @AuthenticationPrincipal을 사용하여 UserDetailsService에서 리턴한 객체를 컨트롤러의 파라미터로 직접 참조할 수 있다.

- 만약 도메인의 User를 표현하는 클래스(Account)를 직접 참조하고 싶다면 Adapter 클래스를 사용하자

 

 출처 

https://ncucu.me/137

 

Spring Security - @AuthenticationPrincipal

Spring Security - @AuthenticationPrincipal @AuthenticationPrincipal 로그인한 사용자의 정보를 파라메터로 받고 싶을때 기존에는 다음과 같이 Principal 객체로 받아서 사용한다. 하지만 이 객체는 SecurityCo..

ncucu.me

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

Deprecated

기존에는 WebSecurityConfigurerAdapter를 상속받아 설정을 오버라이딩 하는 방식이었는데 바뀐 방식에서는 상속받아 오버라이딩하지 않고 모두 Bean으로 등록합니다.

 

변경 전 코드

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final SecurityHandler securityHandler;

    @Bean
    public SHA256CodeEncoder sha256CodeEncoder() {
        return new SHA256CodeEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement(session -> session.invalidSessionUrl("/account/login")).headers().frameOptions().sameOrigin()
                // .. 생략
                .addFilterAt(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

}

변경 후 코드

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {

    private final SecurityHandler securityHandler;

    @Bean
    public SHA256CodeEncoder sha256CodeEncoder() {
        return new SHA256CodeEncoder();
    }

    // 변경된 방식은 SecurityFilterChain을 빈으로 등록하는 방식
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.headers().frameOptions().sameOrigin()
               // ... 생략 ...

        return http.build(); // Bean으로 등록해서 스프링 컨테이너가 관리할 수 있도록 변경
    }
}
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !