이 콘텐츠는 오래되었으며 레거시 OAuth 스택을 사용하고 있습니다. Spring Security의 최신 OAuth 지원을 살펴보십시오 .
1. 개요
2. 메이븐 설정
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
3. 아이디 토큰
openid
라는 새로운 OAuth2 범위를 사용할 것입니다 .그러면 액세스 토큰에 " id_token " 이라는 추가 필드가 생깁니다 .
id_token는
(우리의 경우 구글에서) ID 공급자의 서명이 사용자에 대한 식별 정보를 포함하는 JWT (JSON 웹 토큰)입니다.마지막으로server(Authorization Code)
와묵시적
흐름은 모두id_token
을 얻는 데 가장 일반적으로 사용되는 방법입니다 . 이 예에서는서버 흐름
을 사용 합니다 .3. OAuth2 클라이언트 설정
@Configuration
@EnableOAuth2Client
public class GoogleOpenIdConnectConfig {
@Value("${google.clientId}")
private String clientId;
@Value("${google.clientSecret}")
private String clientSecret;
@Value("${google.accessTokenUri}")
private String accessTokenUri;
@Value("${google.userAuthorizationUri}")
private String userAuthorizationUri;
@Value("${google.redirectUri}")
private String redirectUri;
@Bean
public OAuth2ProtectedResourceDetails googleOpenId() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setScope(Arrays.asList("openid", "email"));
details.setPreEstablishedRedirectUri(redirectUri);
details.setUseCurrentUri(false);
return details;
}
@Bean
public OAuth2RestTemplate googleOpenIdTemplate(OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(googleOpenId(), clientContext);
}
}
다음은
application.properties입니다
.google.clientId=<your app clientId>
google.clientSecret=<your app clientSecret>
google.accessTokenUri=https://www.googleapis.com/oauth2/v3/token
google.userAuthorizationUri=https://accounts.google.com/o/oauth2/auth
google.redirectUri=http://localhost:8081/google-login
참고:
- 먼저 Google 개발자 콘솔 에서 Google 웹 앱에 대한 OAuth 2.0 자격 증명을 가져와야 합니다 .
- id_token 을 얻기 위해 openid 범위 를 사용 했습니다 .
- 또한 id_token ID 정보 에 사용자 이메일을 포함 하기 위해 추가 범위 이메일 을 사용했습니다 .
- 리디렉션 URI http://localhost:8081/google-login 은 Google 웹 앱에서 사용되는 것과 동일합니다.
4. 사용자 정의 OpenID 연결 필터
OpenIdConnectFilter
를 만들어야 합니다.public class OpenIdConnectFilter extends AbstractAuthenticationProcessingFilter {
public OpenIdConnectFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(new NoopAuthenticationManager());
}
@Override
public Authentication attemptAuthentication(
HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
OAuth2AccessToken accessToken;
try {
accessToken = restTemplate.getAccessToken();
} catch (OAuth2Exception e) {
throw new BadCredentialsException("Could not obtain access token", e);
}
try {
String idToken = accessToken.getAdditionalInformation().get("id_token").toString();
String kid = JwtHelper.headers(idToken).get("kid");
Jwt tokenDecoded = JwtHelper.decodeAndVerify(idToken, verifier(kid));
Map<String, String> authInfo = new ObjectMapper()
.readValue(tokenDecoded.getClaims(), Map.class);
verifyClaims(authInfo);
OpenIdConnectUserDetails user = new OpenIdConnectUserDetails(authInfo, accessToken);
return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
} catch (InvalidTokenException e) {
throw new BadCredentialsException("Could not obtain user details from token", e);
}
}
}
다음은 간단한
OpenIdConnectUserDetails입니다
.public class OpenIdConnectUserDetails implements UserDetails {
private String userId;
private String username;
private OAuth2AccessToken token;
public OpenIdConnectUserDetails(Map<String, String> userInfo, OAuth2AccessToken token) {
this.userId = userInfo.get("sub");
this.username = userInfo.get("email");
this.token = token;
}
}
참고:
- Spring Security JwtHelper는 디코딩 id_token .
- id_token은 항상 사용자의 고유 식별자인 " sub" 필드를 포함 합니다.
- id_token 은 요청에 이메일 범위를 추가함에 따라 " email " 필드 도 포함합니다 .
4.1. ID 토큰 확인
decodeAndVerify ()
방법JwtHelper를
으로부터 발췌 정보에id_token,
또한이를 확인하기 위해.이를 위한 첫 번째 단계는 문서에 지정된 인증서 중 하나로 서명되었는지 확인하는 것 입니다.이러한 변경 사항은 하루에 한 번 정도이므로 라는 유틸리티 라이브러리를 사용 하여 읽을 것입니다.<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.3.0</version>
</dependency>
인증서가 포함된 URL을
application.properties
파일에 추가해 보겠습니다 .google.jwkUrl=https://www.googleapis.com/oauth2/v2/certs
이제 이 속성을 읽고
RSAVerifier
객체를 빌드할 수 있습니다 .@Value("${google.jwkUrl}")
private String jwkUrl;
private RsaVerifier verifier(String kid) throws Exception {
JwkProvider provider = new UrlJwkProvider(new URL(jwkUrl));
Jwk jwk = provider.get(kid);
return new RsaVerifier((RSAPublicKey) jwk.getPublicKey());
}
마지막으로 디코딩된 ID 토큰의 클레임도 확인합니다.
public void verifyClaims(Map claims) {
int exp = (int) claims.get("exp");
Date expireDate = new Date(exp * 1000L);
Date now = new Date();
if (expireDate.before(now) || !claims.get("iss").equals(issuer) ||
!claims.get("aud").equals(clientId)) {
throw new RuntimeException("Invalid claims");
}
}
verifyClaims ()
메소드는 ID 토큰이 구글에서 발급 한 것을 확인하고이 만료 아니에요.5. Security 구성
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private OAuth2RestTemplate restTemplate;
@Bean
public OpenIdConnectFilter openIdConnectFilter() {
OpenIdConnectFilter filter = new OpenIdConnectFilter("/google-login");
filter.setRestTemplate(restTemplate);
return filter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new OAuth2ClientContextFilter(),
AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(OpenIdConnectFilter(),
OAuth2ClientContextFilter.class)
.httpBasic()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/google-login"))
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
}
참고:
- OAuth2ClientContextFilter 다음에 맞춤형 OpenIdConnectFilter를 추가 했습니다.
- 간단한 Security 구성을 사용하여 사용자를 " /google-login "으로 리디렉션 하여 Google의 인증을 받았습니다.
6. 사용자 컨트롤러
@Controller
public class HomeController {
@RequestMapping("/")
@ResponseBody
public String home() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
return "Welcome, " + username;
}
}
샘플 응답(앱 권한을 승인하기 위해 Google로 리디렉션한 후):
Welcome, example@gmail.com
7. 샘플 OpenID 연결 프로세스
인증 요청
을 보낼 것입니다 .https://accounts.google.com/o/oauth2/auth?
client_id=sampleClientID
response_type=code&
scope=openid%20email&
redirect_uri=http://localhost:8081/google-login&
state=abc
응답(
사용자 승인 후
)은 다음으로 리디렉션됩니다.http://localhost:8081/google-login?state=abc&code=xyz
다음 으로 Access Token과 id_token에 대한
코드
를 교환할 것입니다 .POST https://www.googleapis.com/oauth2/v3/token
code=xyz&
client_id= sampleClientID&
client_secret= sampleClientSecret&
redirect_uri=http://localhost:8081/google-login&
grant_type=authorization_code
다음은 샘플 응답입니다.
{
"access_token": "SampleAccessToken",
"id_token": "SampleIdToken",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "SampleRefreshToken"
}
마지막으로 실제
id_token
의 정보는 다음과 같습니다.{
"iss":"accounts.google.com",
"at_hash":"AccessTokenHash",
"sub":"12345678",
"email_verified":true,
"email":"example@gmail.com",
...
}
따라서 토큰 내부의 사용자 정보가 자체 애플리케이션에 ID 정보를 제공하는 데 얼마나 유용한지 즉시 알 수 있습니다.