관리 메뉴

bright jazz music

스프링시큐리티 인 액션 2-3 본문

Framework/Spring Security

스프링시큐리티 인 액션 2-3

bright jazz music 2022. 11. 21. 15:06

UserDetailsService와 PasswordEncoder 사용방법 실습한다.

 

UserDetailsService형식의 맞춤형 빈을 정의해서 스프링 시큐리티에 있는 기본 구성요소를 재정의하는 방법을 배운다,

 

예제에서는 inMemoryUserDetailsManager를 구현하여 이용함. 이는UserDetailsService보다 복잡하지만 일단 사용함.  이 구현은 메모리에 자격증명을 저장해서 스프링 시큐리티가 요청을 인증할 때에 이용할 수 있게 한다. (실제 운영에는 사용하지 않는다. 예제나 개념 증명 poc 용으로 적합하다. 다른 부분을 구현하지 않아도 된다)

 

 

1. 구성 클래스 정의

package com.example.ssiach2ex1;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
public class ProjectConfig {
    @Bean //메서드가 반환한 인스턴드를 스프링 컨텍스트에 추가하도록 스프링에 지시
    public UserDetailsService userDetailsService() {
//        var userDetailsService = new InMemoryUserDetailsManager(); var는  ava11이상부터 가능. var는 구문을 간소화하고 세부 정보를 감춘다.
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        return userDetailsService;

    }
}

사용자가 없고, PasswordEncoder가 없어서  엔드포인트에 접근 불가.

 

1.자격증명(사용자 이름 및 암호)이 있는 사용자를 하나 이상 만든다.
2.사용자를 UserDetailsService에서 관리하고 추가한다.
3.주어진 암호를 UserDetailsService가 저장하고 관리하는 암호를 이용해 검증하는 PasswordEncoder 형식의 빈을 정의

 

 

package com.example.ssiach2ex1;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
public class ProjectConfig {
    @Bean //메서드가 반환한 인스턴드를 스프링 컨텍스트에 추가하도록 스프링에 지시
    public UserDetailsService userDetailsService() {
//        var userDetailsService = new InMemoryUserDetailsManager(); var는  ava11이상부터 가능. var는 구문을 간소화하고 세부 정보를 감춘다.
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        //여기까지만 쓰고 구동하면 사용자가 없고, PasswordEncoder가 없어서  엔드포인트에 접근 불가.
        //자격증명(사용자 이름 및 암호)이 있는 사용자를 하나 이상 만든다.
        //사용자를 UserDetailsService에서 관리하고 추가한다.
        //주어진 암호를 UserDetailsService가 저장하고 관리하는 암호를 이용해 검증하는 PasswordEncoder 형식의 빈을 정의
        
        //User클래스는 org.springframework.security.core.userdetails 패키지에 존재하며, 사용자를 나타내는 객체를 만드는 빌더 구현이다.
        User user = (User) User.withUsername("john")
                .password("12345")
                .authorities("read")
                .build();
        
        userDetailsService.createUser(user);
        //사용자까지 만들었지만 아직 PasswordEncoder가 없음.
        //기본  UserDetails서비스를 이용하면 PasswordEncoder도 자동 구성되지만 재정의하면 PasswordEncoder도 다시 정의해야 함.

               
        return userDetailsService;
       
    }
}

 

 

$ curl -v -u john:12345 http://localhost:8080/hello
*   Trying 127.0.0.1:8080...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to localhost (127.0.0.1) port 8080 (#0)
* Server auth using Basic with user 'john'
> GET /hello HTTP/1.1
> Host: localhost:8080
> Authorization: Basic am9objoxMjM0NQ==
> User-Agent: curl/7.80.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 400
< Content-Type: text/plain;charset=UTF-8
< Connection: close
<
{ [62 bytes data]
100    62    0    62    0     0    669      0 --:--:-- --:--:-- --:--:--   681Bad Request
This combination of host and port requires TLS.

* Closing connection 0

 

 

 

package com.example.ssiach2ex1;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
public class ProjectConfig {
    @Bean //메서드가 반환한 인스턴드를 스프링 컨텍스트에 추가하도록 스프링에 지시
    public UserDetailsService userDetailsService() {
//        var userDetailsService = new InMemoryUserDetailsManager(); var는  ava11이상부터 가능. var는 구문을 간소화하고 세부 정보를 감춘다.
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        //여기까지만 쓰고 구동하면 사용자가 없고, PasswordEncoder가 없어서  엔드포인트에 접근 불가.
        //자격증명(사용자 이름 및 암호)이 있는 사용자를 하나 이상 만든다.
        //사용자를 UserDetailsService에서 관리하고 추가한다.
        //주어진 암호를 UserDetailsService가 저장하고 관리하는 암호를 이용해 검증하는 PasswordEncoder 형식의 빈을 정의

        //User클래스는 org.springframework.security.core.userdetails 패키지에 존재하며, 사용자를 나타내는 객체를 만드는 빌더 구현이다.
        User user = (User) User.withUsername("john")
                .password("12345")
                .authorities("read")
                .build();

        userDetailsService.createUser(user);
        //사용자까지 만들었지만 아직 PasswordEncoder가 없음.
        //기본  UserDetails서비스를 이용하면 PasswordEncoder도 자동 구성되지만 재정의하면 PasswordEncoder도 다시 정의해야 함.

        return userDetailsService;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

}

 

 

$ curl -v -u john:12345 https://localhost:8080/hello --insecure
*   Trying 127.0.0.1:8080...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to localhost (127.0.0.1) port 8080 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [193 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [155 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [28 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [892 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: C=KR; ST=Some-State; O=Internet Widgits Pty Ltd
*  start date: Nov 21 01:17:29 2022 GMT
*  expire date: Nov 21 01:17:29 2023 GMT
*  issuer: C=KR; ST=Some-State; O=Internet Widgits Pty Ltd
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* Server auth using Basic with user 'john'
} [5 bytes data]
> GET /hello HTTP/1.1
> Host: localhost:8080
> Authorization: Basic am9objoxMjM0NQ==
> User-Agent: curl/7.80.0
> Accept: */*
>
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [50 bytes data]
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Set-Cookie: JSESSIONID=409AD2CBAC90032EEFBA3248066C656C; Path=/; Secure; HttpOnly
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< Strict-Transport-Security: max-age=31536000 ; includeSubDomains
< X-Frame-Options: DENY
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 7
< Date: Tue, 22 Nov 2022 01:01:39 GMT
<
{ [7 bytes data]
100     7  100     7    0     0    113      0 --:--:-- --:--:-- --:--:--   116Hello!!
* Connection #0 to host localhost left intact

 

 

package com.example.ssiach2ex1;

import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        String username = authentication.getName();
        String password = String.valueOf(authentication.getCredentials());

        if("john".equals(username) && "12345".equals(password)) {
            return new UsernamePasswordAuthenticationToken(username, password, Arrays.asList());
        }else {
            throw new AuthenticationCredentialsNotFoundException("Error!");
        }

    }

    @Override
    public boolean supports(Class<?> authenticationType) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authenticationType);
    }
}

 

 

package com.example.ssiach2ex1;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        var userDetailsService = new InMemoryUserDetailsManager();
        var user = User.withUsername("john")
                .password("12345")
                .authorities("read")
                .build();

        userDetailsService.createUser(user);

        auth.userDetailsService(userDetailsService)
                .passwordEncoder(NoOpPasswordEncoder.getInstance());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic();
        http.authorizeRequests()
                .anyRequest().authenticated();
    }

}

 

----------------

 

package com.example.ssiach2ex1;

import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;

import java.util.ArrayList;
import java.util.Arrays;

public class CustomerAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        
        String username = authentication.getName();
        String password = String.valueOf(authentication.getCredentials());
        
        if("john".equals(username) && "12345".equals(password)){
            return new UsernamePasswordAuthenticationToken(username, password, Arrays.asList());
        }else {
            throw new AuthenticationCredentialsNotFoundException("Error!");
        }
    }

    @Override
    public boolean supports(Class<?> authenticationType) {
        return UsernamePasswordAuthenticationToken
                .class
                .isAssignableFrom(authenticationType);
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

Comments