Spring OAuth2多因素身份验证实施的完整代码已上传到a file sharing site that you can download by clicking on this link。下面的说明解释了如何使用该链接在任何计算机上重新创建当前问题。 提供500点赏金。
<小时/> 当前错误:
当用户尝试使用the Spring Boot OAuth2 app from the link in the preceding paragraph中的双因素身份验证进行身份验证时,会触发错误。当应用程序应该提供第二页时要求用户输入密码以确认用户的身份,该过程中会出现错误。
鉴于null客户端正在触发此错误,问题似乎是如何在Spring Boot OAuth2中将ClientDetailsService
连接到Custom OAuth2RequestFactory
。
entire debug log can be read at a file sharing site by clicking on this link。日志中的完整堆栈跟踪仅包含对应用程序中实际代码的一个引用,该代码行为:
AuthorizationRequest authorizationRequest =
oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request));
调试日志中抛出的错误是:
org.springframework.security.oauth2.provider.NoSuchClientException:
No client with requested id: null
<小时/> 错误时控制流量:
我创建了以下流程图,以说明@James' suggested implementation中多因素身份验证请求的预期流程:
在上面的流程图中,当前错误是在用户名和&amp;密码查看和 GET / secure / two_factor_authenticated 步骤。
此OP的解决方案的范围仅限于FIRST PASS 1.)通过/oauth/authorize
端点,然后2.)通过{{返回/oauth/authorize
端点1}}。
因此,我们只想解析TwoFactorAuthenticationController
,同时还要证明客户已成功授予NoSuchClientException
ROLE_TWO_FACTOR_AUTHENTICATED
。鉴于随后的步骤是锅炉板,只要用户输入 SECOND PASS,流量就可以在 SECOND PASS 进入POST /secure/two_factor_authenticated
时明显中断。 成功完成 FIRST PASS 的所有工件。只要我们在此成功解决 FIRST PASS , SECOND PASS 就可以成为一个单独的问题。
<小时/> 相关代码EXCERPTS:
以下是CustomOAuth2RequestFactory
的代码,我尝试设置连接:
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
重新设置计算机上的问题:
您可以通过以下简单步骤在几分钟内在任何计算机上重新创建问题:
1。)下载zipped version of the app from a file sharing site by clicking on this link。
2。)键入以下内容解压缩应用: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;
}
}
3。)通过导航到tar -zxvf oauth2.tar(1).gz
然后输入authserver
来启动oauth2/authserver
应用。
4.。)导航到mvn spring-boot:run
,然后输入resource
oauth2/resource
应用
5.。)导航到mvn spring-boot:run
,然后输入ui
oauth2/ui
应用
6。)打开Web浏览器并导航到mvn spring-boot:run
7。)点击http : // localhost : 8080
,然后输入Login
作为用户,输入Frodo
作为密码,然后点击提交。 这将触发上面显示的错误。
您可以通过以下方式查看完整的源代码:
a。)将maven项目导入IDE,或者
b。)在解压缩的目录中导航并使用文本编辑器打开。
注意:上面文件共享链接中的代码是the Spring Boot OAuth2 GitHub sample at this link和suggestions for 2 Factor Authentication offered by @James at this link的组合。对Spring Boot GitHub示例的唯一更改是MyRing
应用程序,特别是authserver/src/main/java
和authserver/src/main/resources/templates
。
<小时/> 解决问题:
authserver
,解决了FilterConfigurationBean
。但OP询问如何通过图表中的控制流完成 FIRST PASS ,以获得 500点奖金。
然后我通过在NoSuchClientException
中设置ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED
来缩小问题范围,如下所示:
Users.loadUserByUername()
这消除了配置客户端和资源的需要,因此当前问题仍然很窄。但是,下一个障碍是Spring Security拒绝用户对@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);
}
的请求。 需要进一步更改以通过控制流程完成FIRST PASS,以便/security/two_factor_authentication
可以SYSO POST /secure/two_factor_authentication
?
答案 0 :(得分:3)
该项目需要进行批次修改以实现所描述的流程,而不仅仅是单个问题的范围。这个答案将仅关注如何解决:
org.springframework.security.oauth2.provider.NoSuchClientException:否 请求id的客户端:null
在Spring Boot授权服务器中运行时尝试使用#include
和SecurityWebApplicationInitializer
bean。
发生此异常的原因是WebApplicationInitializer
instances are not run by Spring Boot。这包括在部署到独立Servlet容器的WAR中可以工作的任何Filter
子类。因此,Spring Boot会因AbstractSecurityWebApplicationInitializer
注释而创建您的过滤器,忽略您的@Bean
,并将过滤器应用于所有网址。同时,您只希望将过滤器应用于您尝试传递给AbstractSecurityWebApplicationInitializer
的网址。
相反,要将Servlet过滤器应用于Spring Boot中的特定URL,您应该define a FilterConfigurationBean
。对于尝试将自定义addMappingForUrlPatterns
应用于TwoFactorAuthenticationFilter
的问题中描述的流程,如下所示:
/oauth/authorize