오늘 공부를 하던 도중 의문이 하나 있었다.
에러를 처리하려고 error.html페이지를 만들었지만 에러가 발생했을 때 매핑해주는 코드는 만들지 않았다.
그리고 애플리케이션을 구동시킨 후 에러를 발생시켰는데 에러 발생 후 에러 페이지가 뜨는 것을 확인했다.
이유를 찾아봤는데 error.html을 만들어 놓으면 에러가 발생했을 때 자동으로 렌더링을 해준다는 것이었다.
그리고 또 하나 알아낸 사실은 Spring에서 security config를 설정할 때 loginPage의 기본 값이 /login이라도 적어주지 않으면 우리가 만든 login 페이지를 보여주는 것이 아닌 spring이 만들어준 페이지를 보여준다는 것이다.
redis에서 key를 구별하기 위해 식별이름:과 같은 prefix를 붙여준다.
의존성 주입에 null을 넣어줄 경우 외부에 있는 빈을 알아서 주입해준다.
컨트롤러에서 리다이렉트를 할 때 redirectStrategy를 사용하면 상대경로 절대경로를 자동으로 계산해줘서 좋다.
sudo apt-get install redis-server
brew install redis
redis-cli -p 6379
> set mykey somevalue
OK
> get mykey
"somevalue"
> 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"
> sadd myset 1 2 3
(integer) 3
> smembers myset
1. 3
2. 1
3. 2
> 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"
> 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"
LoginSuccessHandler에서 authentication을 사용할 수 있는 이유는 로그인 관련 핸들러이기 때문이다.
redis의 keys는 금지어이다.
이유는 메모리에 있는 key를 전부 가져오는데 이렇게 되면 key가 별로 없을 때는 문제가 없지만, key가 많아질 경우 서버 장애를 불러올 수 있기 때문이다.
"SESSION".equals(session.getId()) 이 순서로 사용하는 이유는 만약 세션 아이디가 null일 경우 null 관련 예외를 발생시키기 때문이다.
@EnableMethodSecurity
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
// ...
}
// 단순 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 프로토콜은 인증을 위한 프로토콜이 아니고 권한을 위한 프로토콜이다.

클라이언트는 사진 주인에게 허락을 요청하고 허락 증표를 받아서 사용자에게 동의를 받는 서버에게 증표를 주고 토큰을 받아서 자원을 가지고 있는 서버에게 토큰을 주고 자원을 받아오는 흐름을 가지고 있다.
code grant는 코드 값을 넘겨준다.
implicit grant는 암묵적으로 허락을 하는 것이기 때문에 바로 토큰을 넘겨준다.
resource owner password credentials grant는 사용자의 id, password를 넘겨준다.
client credentials grant는 client의 자격증명을 넘겨준다.

요청이 브라우저를 통해 갔다가 응답이 브라우저를 타고 오는 것을 redirect base로 넘어간다고 말한다.
authorization 요청에서 state값은 내가 보낸 요청에 해당하는 응답인지 판단하기 위해 사용하는 값이다.
GET
https://api.instagram.com/oauth2.0/authorize
?response_type=code
&state={random_string}
&client_id={client_id}
&redirect_uri={redirect_uri}
GET
http://application
?code={code}
&state={random_string}
POST
https://api.instagram.com/oauth2.0/token
grant_type=authorization_code
&code={code}
&client_id={client_id}
&client_secret={client_secret}
{
"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="
}
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 속성 이름
}
}
}
public enum CommonOAuth2Provider {
GOOGLE { /*...*/ },
GITHUB { /*...*/ },
FACEBOOK { /*...*/ },
OKTA { /*...*/ };
}
private ClientRegistration googleClientRegistration() {
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.build();
}
public interface ClientRegistrationRepository {
ClientRegistration findByRegistrationId(String registrationId);
}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(/* ... */);
}
}
public class OAuth2AuthorizedClient implements Serializable {
private final ClientRegistration clientRegistration;
private final String principalName;
private final OAuth2AccessToken accessToken;
private final OAuth2RefreshToken refreshToken;
// ...
}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public OAuth2AuthorizedClientService auth2AuthorizedClientService() {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository());
}
}
http.oauth2Login()
.clientRegistrationRepository(clientRegistrationRepository())
.authorizedClientService(authorizedClientService());