[SPRING] OAuth2 다중 요소 인증에서 null 클라이언트
SPRINGOAuth2 다중 요소 인증에서 null 클라이언트
스프링 OAuth2 다중 요소 인증 구현을위한 완벽한 코드가이 링크를 클릭하여 다운로드 할 수있는 파일 공유 사이트에 업로드되었습니다. 아래 지침은 링크를 사용하여 모든 컴퓨터에서 현재 문제를 재현하는 방법을 설명합니다. 500 포인트 현상금이 제공됩니다.
현재 오류 :
사용자가 이전 단락의 링크에서 Spring Boot OAuth2 앱의 두 가지 요소 인증을 사용하여 인증을 시도하면 오류가 발생합니다. 이 오류는 앱에서 사용자의 신원을 확인하는 핀 코드를 묻는 두 번째 페이지를 제공해야하는 시점에서 발생합니다.
null 클라이언트가이 오류를 발생 시킨다면 문제는 SpringBoot OAuth2에서 ClientDetailsService를 사용자 정의 OAuth2RequestFactory에 연결하는 방법 인 것 같습니다.
이 링크를 클릭하면 파일 공유 사이트에서 전체 디버그 로그를 읽을 수 있습니다. 로그의 전체 스택 추적에는 실제로 앱에있는 코드에 대한 참조가 하나만 포함되어 있으며 그 코드 행은 다음과 같습니다.
AuthorizationRequest authorizationRequest =
oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request));
디버그 로그에서 발생하는 오류는 다음과 같습니다.
org.springframework.security.oauth2.provider.NoSuchClientException:
No client with requested id: null
오류가 발생하면 제어 흐름 :
@James의 제안 된 구현에서 다중 요소 인증 요청의 의도 된 흐름을 설명하기 위해 다음 순서도를 만들었습니다.
앞의 플로우 차트에서 Username & Password View와 GET / secure / two_factor_authenticated 단계 사이의 어느 시점에서 현재 오류가 발생합니다.
이 OP에 대한 해결책은 범위가 1에서 / oauth / authorize 끝 점을 통과 한 다음 2)는 TwoFactorAuthenticationController를 통해 / oauth / authorize 끝점으로 돌아 오는 첫 번째 단계로 제한됩니다.
따라서 NoSuchClientException을 해결하고 POST / secure / two_factor_authenticated에서 클라이언트가 ROLE_TWO_FACTOR_AUTHENTICATED를 성공적으로 부여 받았음을 보여 주기만하면됩니다. 후속 단계가 보일러 플레이트 인 경우 사용자가 첫 번째 패스를 성공적으로 완료 한 모든 아티팩트와 함께 두 번째 패스를 입력하는 경우 흐름이 두 번째 패스 항목에서 CustomOAuth2RequestFactory로 명백하게 파손되는 것이 허용됩니다. SECOND PASS는 여기에서 첫 번째 단락을 성공적으로 해결하는 한 별도의 질문이 될 수 있습니다.
관련 코드 발췌 문장 :
AuthorizationServerConfigurerAdapter의 코드는 다음과 같습니다. 여기서 연결을 설정하려고합니다.
@Configuration
@EnableAuthorizationServer
protected static class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired//ADDED AS A TEST TO TRY TO HOOK UP THE CUSTOM REQUEST FACTORY
private ClientDetailsService clientDetailsService;
@Autowired//Added per: https://stackoverflow.com/questions/30319666/two-factor-authentication-with-spring-security-oauth2
private CustomOAuth2RequestFactory customOAuth2RequestFactory;
//THIS NEXT BEAN IS A TEST
@Bean CustomOAuth2RequestFactory customOAuth2RequestFactory(){
return new CustomOAuth2RequestFactory(clientDetailsService);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource("keystore.jks"), "foobar".toCharArray()
)
.getKeyPair("test");
converter.setKeyPair(keyPair);
return converter;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("acme")//API: http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/config/annotation/builders/ClientDetailsServiceBuilder.ClientBuilder.html
.secret("acmesecret")
.authorizedGrantTypes("authorization_code", "refresh_token", "password")
.scopes("openid");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints//API: http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.html
.authenticationManager(authenticationManager)
.accessTokenConverter(jwtAccessTokenConverter())
.requestFactory(customOAuth2RequestFactory);//Added per: https://stackoverflow.com/questions/30319666/two-factor-authentication-with-spring-security-oauth2
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer//API: http://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerSecurityConfigurer.html
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
다음은 오류를 유발하는 위의 코드가 포함 된 TwoFactorAuthenticationFilter의 코드입니다.
package demo;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
//This class is added per: https://stackoverflow.com/questions/30319666/two-factor-authentication-with-spring-security-oauth2
/**
* Stores the oauth authorizationRequest in the session so that it can
* later be picked by the {@link com.example.CustomOAuth2RequestFactory}
* to continue with the authoriztion flow.
*/
public class TwoFactorAuthenticationFilter extends OncePerRequestFilter {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private OAuth2RequestFactory oAuth2RequestFactory;
//These next two are added as a test to avoid the compilation errors that happened when they were not defined.
public static final String ROLE_TWO_FACTOR_AUTHENTICATED = "ROLE_TWO_FACTOR_AUTHENTICATED";
public static final String ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED = "ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED";
@Autowired
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService);
}
private boolean twoFactorAuthenticationEnabled(Collection<? extends GrantedAuthority> authorities) {
return authorities.stream().anyMatch(
authority -> ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED.equals(authority.getAuthority())
);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Check if the user hasn't done the two factor authentication.
if (AuthenticationUtil.isAuthenticated() && !AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)) {
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request));
/* Check if the client's authorities (authorizationRequest.getAuthorities()) or the user's ones
require two factor authenticatoin. */
if (twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) ||
twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities())) {
// Save the authorizationRequest in the session. This allows the CustomOAuth2RequestFactory
// to return this saved request to the AuthenticationEndpoint after the user successfully
// did the two factor authentication.
request.getSession().setAttribute(CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME, authorizationRequest);
// redirect the the page where the user needs to enter the two factor authentiation code
redirectStrategy.sendRedirect(request, response,
ServletUriComponentsBuilder.fromCurrentContextPath()
.path(TwoFactorAuthenticationController.PATH)
.toUriString());
return;
}
}
filterChain.doFilter(request, response);
}
private Map<String, String> paramsFromRequest(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
params.put(entry.getKey(), entry.getValue()[0]);
}
return params;
}
}
컴퓨터 문제 재현 :
다음과 같은 간단한 단계를 수행하면 몇 분 안에 모든 컴퓨터에서 문제를 재현 할 수 있습니다.
1.)이 링크를 클릭하여 파일 공유 사이트에서 응용 프로그램의 압축 된 버전을 다운로드하십시오.
2.) 다음을 입력하여 응용 프로그램의 압축을 풉니 다. tar -zxvf oauth2.tar (1) .gz
3.) oauth2 / authserver로 이동 한 다음 mvn spring-boot :를 입력하여 authserver 응용 프로그램을 실행하십시오.
4.) oauth2 / resource로 이동 한 다음 mvn을 입력하여 리소스 앱을 시작합니다. spring-boot : run
5.) oauth2 / ui로 이동 한 다음 mvn spring-boot :를 입력하여 UI 응용 프로그램을 시작합니다
6.) 웹 브라우저를 열고 http : // localhost : 8080으로 이동하십시오
7.) 로그인을 클릭 한 다음 사용자로 Frodo를 입력하고 암호로 MyRing을 입력하고 제출을 클릭하십시오. 위의 오류가 트리거됩니다.
다음과 같은 방법으로 전체 소스 코드를 볼 수 있습니다.
a.) maven 프로젝트를 IDE로 가져 오거나,
b.) 압축 해제 된 디렉토리를 탐색하고 텍스트 편집기로여십시오.
참고 : 위 파일 공유 링크의 코드는이 링크에있는 Spring Boot OAuth2 GitHub 샘플과이 링크에서 @James가 제공 한 2 팩터 인증에 대한 제안입니다. Spring 부트 GitHub 샘플의 유일한 변경 사항은 authserver 응용 프로그램, 특히 authserver / src / main / java 및 authserver / src / main / resources / templates에 있습니다.
문제를 파악하기 : Per @ AbrahamGrief의 제안에 따라 FilterConfigurationBean을 추가하여 NoSuchClientException을 해결했습니다. 그러나 OP는 500 포인트 현상금에 대한 다이어그램의 제어 흐름을 통해 첫 번째 패스를 완료하는 방법을 묻습니다.
그런 다음 Users.loadUserByUername ()에서 ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED를 다음과 같이 설정하여 문제를 좁혔습니다.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password;
List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
if (username.equals("Samwise")) {//ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED will need to come from the resource, NOT the user
auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_HOBBIT, ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED");
password = "TheShire";
}
else if (username.equals("Frodo")){//ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED will need to come from the resource, NOT the user
auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_HOBBIT, ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED");
password = "MyRing";
}
else{throw new UsernameNotFoundException("Username was not found. ");}
return new org.springframework.security.core.userdetails.User(username, password, auth);
}
이렇게하면 클라이언트와 리소스를 구성 할 필요가 없으므로 현재의 문제가 좁은 상태로 유지됩니다. 그러나 다음 번로드 블록은 Spring Security가 / security / two_factor_authentication에 대한 사용자의 요청을 거부한다는 것입니다. POST / secure / two_factor_authentication이 SYSO ROLE_TWO_FACTOR_AUTHENTICATED 될 수 있도록 제어 흐름을 통해 FIRST PASS를 완성하기 위해 더 많은 변경이 필요합니까?
해결법
-
==============================
1.설명 된 흐름을 구현하기 위해 프로젝트에 필요한 수정이 많이 필요합니다. 단일 질문의 범위를 벗어나지 않아야합니다. 이 답변은 해결 방법에만 초점을 맞 춥니 다.
설명 된 흐름을 구현하기 위해 프로젝트에 필요한 수정이 많이 필요합니다. 단일 질문의 범위를 벗어나지 않아야합니다. 이 답변은 해결 방법에만 초점을 맞 춥니 다.
스프링 부트 인증 서버에서 실행되는 동안 SecurityWebApplicationInitializer와 필터 빈을 사용하려고 할 때.
이 예외가 발생하는 이유는 WebApplicationInitializer 인스턴스가 SpringBoot에 의해 실행되지 않기 때문입니다. 여기에는 독립 서블릿 컨테이너에 배포 된 WAR에서 작동하는 모든 AbstractSecurityWebApplicationInitializer 하위 클래스가 포함됩니다. 따라서 Spring Boot는 @Bean 어노테이션으로 인해 필터를 만들고, AbstractSecurityWebApplicationInitializer를 무시하고 모든 URL에 필터를 적용합니다. 그 사이에 addMappingForUrlPatterns에 전달하려는 URL에만 필터를 적용해야합니다.
대신 Spring Boot에서 특정 URL에 서블릿 필터를 적용하려면 FilterConfigurationBean을 정의해야한다. / oauth / authorize에 사용자 정의 TwoFactorAuthenticationFilter를 적용하려고하는 질문에 설명 된 흐름의 경우 다음과 같이 표시됩니다.
@Bean public FilterRegistrationBean twoFactorAuthenticationFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(twoFactorAuthenticationFilter()); registration.addUrlPatterns("/oauth/authorize"); registration.setName("twoFactorAuthenticationFilter"); return registration; } @Bean public TwoFactorAuthenticationFilter twoFactorAuthenticationFilter() { return new TwoFactorAuthenticationFilter(); }
from https://stackoverflow.com/questions/36899386/null-client-in-oauth2-multi-factor-authentication by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] Spring FileUpload의 문제점 (0) | 2019.02.02 |
---|---|
[SPRING] 쿼리 문자열 매개 변수를 JavaBean에 매핑 할 수 없습니다 (Spring 4 및 Datatables 사용). (0) | 2019.02.02 |
[SPRING] 폴더 안에있을 때 MessageSource에 대한 ResourceBundle을 찾을 수 없습니다. (0) | 2019.02.02 |
[SPRING] 런타임시 새로운 사용자를 Spring Security에 추가하는 방법 (0) | 2019.02.02 |
[SPRING] Spring Framework를 사용하여 OPTIONS 요청에 CORS 사용 (0) | 2019.02.02 |