스프링 MVC 애플리케이션을 작성하고 스프링 보안 OAuth2로 보안을 설정하려고 노력 중이며 공급자가 Google입니다. 보안 및 양식 로그인없이 웹 앱을 사용할 수있었습니다. 그러나 Google에서 OAuth를 사용할 수는 없습니다. 비 스프링 시큐리티 앱으로 작업하기 위해 콜백 등을 얻을 수 있기 때문에 구글 앱 셋업은 괜찮습니다. OAuth2를 내가 OAuth2를 허가는받을 수있는 리디렉션 예외를 던질 작성한리디렉션 루프에서 스프링 보안 OAuth2 (google) 웹 앱
@Configuration
@EnableOAuth2Client
class ResourceConfiguration {
@Autowired
private Environment env;
@Resource
@Qualifier("accessTokenRequest")
private AccessTokenRequest accessTokenRequest;
@Bean
public OAuth2ProtectedResourceDetails googleResource() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("google-app");
details.setClientId(env.getProperty("google.client.id"));
details.setClientSecret(env.getProperty("google.client.secret"));
details.setAccessTokenUri(env.getProperty("google.accessTokenUri"));
details.setUserAuthorizationUri(env.getProperty("google.userAuthorizationUri"));
details.setTokenName(env.getProperty("google.authorization.code"));
String commaSeparatedScopes = env.getProperty("google.auth.scope");
details.setScope(parseScopes(commaSeparatedScopes));
details.setPreEstablishedRedirectUri(env.getProperty("google.preestablished.redirect.url"));
details.setUseCurrentUri(false);
details.setAuthenticationScheme(AuthenticationScheme.query);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
return details;
}
private List<String> parseScopes(String commaSeparatedScopes) {
List<String> scopes = newArrayList();
Collections.addAll(scopes, commaSeparatedScopes.split(","));
return scopes;
}
@Bean
public OAuth2RestTemplate googleRestTemplate() {
return new OAuth2RestTemplate(googleResource(), new DefaultOAuth2ClientContext(accessTokenRequest));
}
@Bean
public AbstractAuthenticationProcessingFilter googleAuthenticationFilter() {
return new GoogleOAuthentication2Filter(new GoogleAppsDomainAuthenticationManager(), googleRestTemplate(), "https://accounts.google.com/o/oauth2/auth", "http://localhost:9000");
}
}
사용자 정의 인증 필터를 다음과 같이 자원이 보호
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns:sec="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<sec:http use-expressions="true" entry-point-ref="clientAuthenticationEntryPoint">
<sec:http-basic/>
<sec:logout/>
<sec:anonymous enabled="false"/>
<sec:intercept-url pattern="/**" access="isFullyAuthenticated()"/>
<sec:custom-filter ref="oauth2ClientContextFilter" after="EXCEPTION_TRANSLATION_FILTER"/>
<sec:custom-filter ref="googleAuthenticationFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
</sec:http>
<b:bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"/>
<sec:authentication-manager alias="alternateAuthenticationManager">
<sec:authentication-provider>
<sec:user-service>
<sec:user name="user" password="password" authorities="DOMAIN_USER"/>
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</b:beans>
다음과 같이
내 보안 설정입니다 다음과 같이
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
try {
logger.info("OAuth2 Filter Triggered!! for path {} {}", request.getRequestURI(), request.getRequestURL().toString());
logger.info("OAuth2 Filter hashCode {} request hashCode {}", this.hashCode(), request.hashCode());
String code = request.getParameter("code");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
logger.info("Code is {} and authentication is {}", code, authentication == null ? null : authentication.isAuthenticated());
// not authenticated
if (requiresRedirectForAuthentication(code)) {
URI authURI = new URI(googleAuthorizationUrl);
logger.info("Posting to {} to trigger auth redirect", authURI);
String url = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + oauth2RestTemplate.getAccessToken();
logger.info("Getting profile data from {}", url);
// Should throw RedirectRequiredException
oauth2RestTemplate.getForEntity(url, GoogleProfile.class);
// authentication in progress
return null;
} else {
logger.info("OAuth callback received");
// get user profile and prepare the authentication token object.
String url = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + oauth2RestTemplate.getAccessToken();
logger.info("Getting profile data from {}", url);
ResponseEntity<GoogleProfile> forEntity = oauth2RestTemplate.getForEntity(url, GoogleProfile.class);
GoogleProfile profile = forEntity.getBody();
CustomOAuth2AuthenticationToken authenticationToken = getOAuth2Token(profile.getEmail());
authenticationToken.setAuthenticated(false);
Authentication authenticate = getAuthenticationManager().authenticate(authenticationToken);
logger.info("Final authentication is {}", authenticate == null ? null : authenticate.isAuthenticated());
return authenticate;
}
} catch (URISyntaxException e) {
Throwables.propagate(e);
}
return null;
}
필터 체인 시퀀스는 다음과 같습니다.
o.s.b.c.e.ServletRegistrationBean - Mapping servlet: 'dispatcherServlet' to [/]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'metricFilter' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'oauth2ClientContextFilter' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'googleOAuthFilter' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'org.springframework.security.filterChainProxy' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'applicationContextIdFilter' to: [/*]
o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'webRequestLoggingFilter' to: [/*]
Google로 리디렉션하면 정상적으로 작동하며 콜백이 필터에 전달되어 인증에 성공합니다. 그러나 그 후에 요청은 리다이렉트하게되고 필터가 다시 호출됩니다 (요청은 동일합니다, hasCode를 체크했습니다). 두 번째 호출에서 SecurityContext의 인증은 null입니다. 첫 번째 인증 호출의 일부로 Authentication 객체가 보안 컨텍스트에 채워 졌으므로 왜 사라지나요? 스프링 보안과 처음으로 작업 중이므로 초보자 실수가있을 수 있습니다.
"Google의 토큰 형식과 Spring에서 예상하는 형식이 일치하지 않습니다." 그걸 조금 설명 할 수 있니? Spring은 토큰 값에 대한 가정을하지 않는다 (단지 불투명 한 문자열이다). –
N.B. 2.0.3으로 업그레이드하고 'RestTemplate'에 대한 세션 범위 사용을 중단해야합니다. –
@DaveSyer 차이점이 몇 가지 있습니다. checkToken에 대한 응답에서 Google은 'client_id'를 'issued_to'로, 'user_name'을 'user_id'로 보냅니다. 토큰에 여러 범위가있는 경우 Google의 응답에는 공백으로 구분 된 범위가 있고 문자열 모음이 아닙니다. 다른 범위는 scope.split ("")을 사용하여 추출해야합니다. 여기에 [https://github.com/skate056/spring-security-oauth2-google/blob/master/src/main/java/com/rst/oauth2/google/security] 클래스가 있습니다. /GoogleAccessTokenConverter.java) – Saket