spring-security-2일차

박상원·2024년 5월 13일

spring

목록 보기
15/15

오늘 공부를 하던 도중 의문이 하나 있었다.
에러를 처리하려고 error.html페이지를 만들었지만 에러가 발생했을 때 매핑해주는 코드는 만들지 않았다.
그리고 애플리케이션을 구동시킨 후 에러를 발생시켰는데 에러 발생 후 에러 페이지가 뜨는 것을 확인했다.
이유를 찾아봤는데 error.html을 만들어 놓으면 에러가 발생했을 때 자동으로 렌더링을 해준다는 것이었다.
그리고 또 하나 알아낸 사실은 Spring에서 security config를 설정할 때 loginPage의 기본 값이 /login이라도 적어주지 않으면 우리가 만든 login 페이지를 보여주는 것이 아닌 spring이 만들어준 페이지를 보여준다는 것이다.

Redis + Session


Redis 란

  • key-value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계현 데이터베이스 관리 시스템
  • 모든 데이터를 메모리로 불러와서 처리하는 메모리 기반 DBMS
  • Remote Dictionary Server

redis에서 key를 구별하기 위해 식별이름:과 같은 prefix를 붙여준다.
의존성 주입에 null을 넣어줄 경우 외부에 있는 빈을 알아서 주입해준다.
컨트롤러에서 리다이렉트를 할 때 redirectStrategy를 사용하면 상대경로 절대경로를 자동으로 계산해줘서 좋다.

Redis 설치

  • Linux (Ubuntu)
sudo apt-get install redis-server
  • MacOs
brew install redis

Redis 접속

redis-cli -p 6379

Redis Data Types

Strings

> set mykey somevalue
OK
> get mykey
"somevalue"

Lists

> rpush mylist A
(integer) 1
> rpush mylist B
(integer) 2
> lpush mylist first
(integer) 3
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"

Sets

> sadd myset 1 2 3
(integer) 3
> smembers myset
1. 3
2. 1
3. 2

Hashes

> hmset user:1000 username antirez birthyear 1977 verified 1
OK
> hget user:1000 username
"antirez"
> hget user:1000 birthyear
"1977"
> hgetall user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1977"
5) "verified"
6) "1"

Sorted Sets

> zadd hackers 1940 "Alan Kay"
(integer) 1
> zadd hackers 1957 "Sophie Wilson"
(integer) 1
> zadd hackers 1953 "Richard Stallman"
(integer) 1
> zadd hackers 1949 "Anita Borg"
(integer) 1
> zadd hackers 1965 "Yukihiro Matsumoto"
(integer) 1
> zadd hackers 1914 "Hedy Lamarr"
(integer) 1
> zadd hackers 1916 "Claude Shannon"
(integer) 1
> zadd hackers 1969 "Linus Torvalds"
(integer) 1
> zadd hackers 1912 "Alan Turing"
(integer) 1
> zrange hackers 0 -1
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
6) "Richard Stallman"
7) "Sophie Wilson"
8) "Yukihiro Matsumoto"
9) "Linus Torvalds"

Java에서 Redis 사용

  • Jedis 라이브러리
  • Lettuce 라이브러리

Redis를 세션 레포지토리로 써야 하는 이유

  • 웹 서버가 여러 대일 때
    • HttpSession을 그대로 쓸 수가 없다.
    • Authentication -> 세션에 저장됨

LoginSuccessHandler에서 authentication을 사용할 수 있는 이유는 로그인 관련 핸들러이기 때문이다.
redis의 keys는 금지어이다.
이유는 메모리에 있는 key를 전부 가져오는데 이렇게 되면 key가 별로 없을 때는 문제가 없지만, key가 많아질 경우 서버 장애를 불러올 수 있기 때문이다.

"SESSION".equals(session.getId()) 이 순서로 사용하는 이유는 만약 세션 아이디가 null일 경우 null 관련 예외를 발생시키기 때문이다.

Method Security


Method Security

  • 서비스 레이어 메서드에 보안 기능을 추가하는 기능
  • 설정별로 지원하는 annotation에 condition 구문을 만족하면 해당 메서드 실행 가능
  • condition 구문을 만족하지 못하면 AccessDeniedException 발생

설정하는 방법

@EnableMethodSecurity

@EnableMethodSecurity 옵션

  • prePostEnabled - @PreAuthorize/@PostAuthorize
    • default
    • 가장 강력한 옵션을 제공: 추천
  • securedEnabled - @Secured
    • 단순 권한체크만 가능
  • jsr250Enabled - JSR-250 annotation
    • 자바 표준이나 잘 사용하지 않음

@EnableMethodSecurity(prePostEnabled = true)

설정

@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    // ...
}

적용

  • @PreAuthorize 메서드 실행 전에 권한 체크
  • @PostAuthorize 메서드 실행 후 권한 체크

Ex.) @EnabledMethodSecurity(prePostEnabled = true)

// 단순 Role 체크
@PreAuthorize("hasRole('ROLE_WRITE')")
public void addBook(Book book);

// 반환 객체로 비교하는 것도 가능
@PostAuthorize("returnObject.writer == authentication.name")
public Book getBook(Long id);

// 인자나 인자의 속성의 비교하는 것도 가능
@PreAuthorize ("hasRole('ROLE_WRITE') && #book.writer == authentication.name")
@Transactional
public void changeBook(Book book);

// bean 자체를 주입받는 것도 가능 w/SpEL
// cf.) "?." = Null safety
@PreAuthorize("@bookRepository.findOne(#id)?.writer == authentication?.name")
@Transactional
public void deleteBookById(Long id);

method security에서 method 위에 annotation을 붙일 때는 method가 public이여야 한다.
이유는 public이 아니면 proxy 객체를 만들지 못하기 때문이다.

OAuth 기본 개념

OAuth

  • 인터넷 사용자가
    • 다른 웹사이트에 있는 자신의 사용자 정보를
      • 비밀번호를 직접 제공하지 않고도
    • 특정 웹 사이트나 애플리케이션에게 접근을 허용해줄 수 있도록 해주는
  • 접근 위임을 위한 개방형 표준

OAuth 프로토콜은 인증을 위한 프로토콜이 아니고 권한을 위한 프로토콜이다.

Role

  • Resource owner - 자원에 대한 접근을 허가해줄 수 있는 주체
  • Resource server - 자원을 호스팅하는 서버
  • Client - Resource Server에서 제공하는 자원을 사용하는 애플리케이션
  • Authorization Server
    • 사용자의 동의를 받아서 권한을 부여하는 서버
    • 일반적으로 Resource Server와 같은 URL 하위에 있는 경우가 많음

인스타그램과 내 애플리케이션을 예로 들자면

  • resource owner : 인스타그램 사용자
  • resource server : 인스타그램 서버
  • client : 인스타그램 자원을 활용하는 내 애플리케이션
  • authorization server : 인스타그램 서버

Protocol Flow


클라이언트는 사진 주인에게 허락을 요청하고 허락 증표를 받아서 사용자에게 동의를 받는 서버에게 증표를 주고 토큰을 받아서 자원을 가지고 있는 서버에게 토큰을 주고 자원을 받아오는 흐름을 가지고 있다.

Grant Type

  • Authorization Code Grant
    • response_type=code
  • Implicit Grant
    • response_type=token
  • Resource Owner Password Credentials Grant
    • grant_type=password&username=&password=
  • Client Credentials Grant
    • grant_type=client_credentials

code grant는 코드 값을 넘겨준다.
implicit grant는 암묵적으로 허락을 하는 것이기 때문에 바로 토큰을 넘겨준다.
resource owner password credentials grant는 사용자의 id, password를 넘겨준다.
client credentials grant는 client의 자격증명을 넘겨준다.

Authorization Code Grant

요청이 브라우저를 통해 갔다가 응답이 브라우저를 타고 오는 것을 redirect base로 넘어간다고 말한다.
authorization 요청에서 state값은 내가 보낸 요청에 해당하는 응답인지 판단하기 위해 사용하는 값이다.

(3) Authorization 요청

GET
https://api.instagram.com/oauth2.0/authorize
    ?response_type=code
    &state={random_string}
    &client_id={client_id}
    &redirect_uri={redirect_uri}

(7) Authorization Grant

GET
http://application
    ?code={code}
    &state={random_string}

(8) Access Token 요청

POST
https://api.instagram.com/oauth2.0/token

grant_type=authorization_code
&code={code}
&client_id={client_id}
&client_secret={client_secret}

Access Token 응답

{
    "access_token_secret":"4XIFI6WV08rPbgzl",
    "state":null,
    "expires_in":"1209600",
    "token_type":"Bearer",
    "access_token":"AAAA4NV74n+ns623iceBuM1MfWzb3iq5kNfC4cMZ0A5T5pGjwfMF5b/Yj+LyDBqK8+/jvVTIKHbzM47wFw8qUf0fTCw4+/ko+hSgAGClMLykvaDJ7/0B/OxlMmkml2hJPQp96DqV6AqkvAw4niLVho14Izga2c5IksQOjTTv5L8keM4yTplN50xzGzYJpV1vmj3NGWtPKHaJL3fLVa41hvZmxOw00edQeOYAw/vhLW7iQDuJjpTciWgmgEUd9if7EL+tqIJmE6fRSH42b4aNOX5XgiaEr6hUDisUoHG5kqOd",
    "refresh_token":"AAAAb7F2pdU0FZuxHreJDA2TnpkyB5fw8vwRwdaQaMaEKDwlspr/LIsbLGHdW85lVuJ3OaNDguDnGx5+FZeZaIbBTDRBcaneT9WlrDNORX8eyVf5bgL6RrENn5tKhjdjQlmsXxH1wCJDUa2J2QtQOHRTxAg="
}

Spring Security OAuth


ClientRegistration

  • Oauth 2.0 / OpenID Connect 1.0 provider에 등록하기 위한 client 정보
public final class ClientRegistration {
    private String registrationId;   // ClientRegistration을 구분하는 unique id
    private String clientId;         // oauth2 client_id
    private String clientSecret;     // oauth2 client_secret
    private AuthorizationGrantType authorizationGrantType;  // grant type
    private String redirectUriTemplate; // authorization server -> application

    public class ProviderDetails {
        private String authorizationUri;                // provider authorization endpoint uri
        private String tokenUri;                        // provider access token uri

        public class UserInfoEndpoint {
            private String uri;                                 // UserInfo endpoint uri
            private AuthenticationMethod authenticationMethod;  // header, form, query
            private String userNameAttributeName;               // UserInfo identifier 속성 이름
        }
    }
}

CommonOAuth2Provider

  • provide ClientRegistration builder for pre-defined OAuth2 providers
  • google, github, facebook 등
public enum CommonOAuth2Provider {
  GOOGLE { /*...*/ },
  GITHUB { /*...*/ },
  FACEBOOK { /*...*/ },
  OKTA { /*...*/ };
}

Ex.)

private ClientRegistration googleClientRegistration() {
  return CommonOAuth2Provider.GOOGLE.getBuilder("google")
    .clientId("google-client-id")
    .clientSecret("google-client-secret")
    .build();
}

ClientRegistrationRepository

  • repository for OAuth 2.0 / OpenID Connect 1.0 ClientRegistration
public interface ClientRegistrationRepository {
  ClientRegistration findByRegistrationId(String registrationId);
}

InMemoryClientRegistrationRepository

  • default implementation of ClientRegistrationRepository
  • store ClientRegistrations in-memory
@Configuration
@EnableWebSecurity
public class SecurityConfig  extends WebSecurityConfigurerAdapter {
    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        return new InMemoryClientRegistrationRepository(/* ... */);
    }
}

OAuth2AuthorizedClient

  • resource owner가 authorization grant를 해 준 client의 정보 추상화
public class OAuth2AuthorizedClient implements Serializable {
  private final ClientRegistration clientRegistration;
  private final String principalName;
  private final OAuth2AccessToken accessToken;
  private final OAuth2RefreshToken refreshToken;

  // ...
}

OAuth2AuthorizedClientService

  • OAuth2AuthorizedClient 관리 기능
  • cf.) OAuth2AccessToken

InMemoryOAuth2AuthorizedClientService

  • default implementation of OAuth2AuthorizedClientService
  • store OAuth2AuthorizedClients in-memory
@Configuration
@EnableWebSecurity
public class SecurityConfig  extends WebSecurityConfigurerAdapter {
    @Bean
    public OAuth2AuthorizedClientService auth2AuthorizedClientService() {
        return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository());
    }
}

OAuth2Login

설정

http.oauth2Login()
    .clientRegistrationRepository(clientRegistrationRepository())
    .authorizedClientService(authorizedClientService());

0개의 댓글