gpt4 book ai didi

spring - 使用 Spring Security oauth2 进行两因素身份验证

转载 作者:IT老高 更新时间:2023-10-28 13:50:11 31 4
gpt4 key购买 nike

我正在寻找如何使用 Spring Security OAuth2 实现两因素身份验证 (2FA) 的想法。要求是用户只需要对具有敏感信息的特定应用程序进行双重身份验证。这些 web 应用程序有自己的客户端 ID。

我想到的一个想法是“滥用”范围批准页面来强制用户输入 2FA 代码/PIN(或其他)。

示例流程如下所示:

在没有和使用 2FA 的情况下访问应用

  • 用户已注销
  • 用户访问不需要 2FA 的应用 A
  • 重定向到 OAuth 应用,用户使用用户名和密码登录
  • 重定向回应用 A 并且用户已登录
  • 用户访问同样不需要 2FA 的应用 B
  • 重定向到 OAuth 应用,重定向回应用 B,用户直接登录
  • 用户访问确实需要 2FA 的应用 S
  • 重定向到 OAuth 应用,用户需要额外提供 2FA token
  • 重定向回应用 S 并且用户已登录

使用 2FA 直接访问应用

  • 用户已注销
  • 用户访问确实需要 2FA 的应用 S
  • 重定向到 OAuth 应用,用户使用用户名和密码登录,用户需要额外提供 2FA token
  • 重定向回应用 S 并且用户已登录

你有其他想法如何解决这个问题吗?

最佳答案

这就是最终实现两因素身份验证的方式:

在spring安全过滤器之后为/oauth/authorize路径注册了一个过滤器:

@Order(200)
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
@Override
protected void afterSpringSecurityFilterChain(ServletContext servletContext) {
FilterRegistration.Dynamic twoFactorAuthenticationFilter = servletContext.addFilter("twoFactorAuthenticationFilter", new DelegatingFilterProxy(AppConfig.TWO_FACTOR_AUTHENTICATION_BEAN));
twoFactorAuthenticationFilter.addMappingForUrlPatterns(null, false, "/oauth/authorize");
super.afterSpringSecurityFilterChain(servletContext);
}
}

此过滤器检查用户是否尚未使用第二个因素进行身份验证(通过检查 ROLE_TWO_FACTOR_AUTHENTICATED 权限是否不可用)并创建一个 OAuth AuthorizationRequest被放入 session 中。然后用户被重定向到他必须输入 2FA 代码的页面:

/**
* 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;

@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;
} else {
request.getSession().removeAttribute(CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
}
}

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;
}
}

如果代码正确,处理输入 2FA 代码的 TwoFactorAuthenticationController 添加权限 ROLE_TWO_FACTOR_AUTHENTICATED 并将用户重定向回/oauth/authorize 端点。

@Controller
@RequestMapping(TwoFactorAuthenticationController.PATH)
public class TwoFactorAuthenticationController {
private static final Logger LOG = LoggerFactory.getLogger(TwoFactorAuthenticationController.class);

public static final String PATH = "/secure/two_factor_authentication";

@RequestMapping(method = RequestMethod.GET)
public String auth(HttpServletRequest request, HttpSession session, ....) {
if (AuthenticationUtil.isAuthenticatedWithAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)) {
LOG.info("User {} already has {} authority - no need to enter code again", ROLE_TWO_FACTOR_AUTHENTICATED);
throw ....;
}
else if (session.getAttribute(CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME) == null) {
LOG.warn("Error while entering 2FA code - attribute {} not found in session.", CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
throw ....;
}

return ....; // Show the form to enter the 2FA secret
}

@RequestMapping(method = RequestMethod.POST)
public String auth(....) {
if (userEnteredCorrect2FASecret()) {
AuthenticationUtil.addAuthority(ROLE_TWO_FACTOR_AUTHENTICATED);
return "forward:/oauth/authorize"; // Continue with the OAuth flow
}

return ....; // Show the form to enter the 2FA secret again
}
}

自定义 OAuth2RequestFactory 会从 session 中检索先前保存的 AuthorizationRequest(如果可用),如果在 session 中找不到,则返回它或创建一个新的。

/**
* If the session contains an {@link AuthorizationRequest}, this one is used and returned.
* The {@link com.example.TwoFactorAuthenticationFilter} saved the original AuthorizationRequest. This allows
* to redirect the user away from the /oauth/authorize endpoint during oauth authorization
* and show him e.g. a the page where he has to enter a code for two factor authentication.
* Redirecting him back to /oauth/authorize will use the original authorizationRequest from the session
* and continue with the oauth authorization.
*/
public class CustomOAuth2RequestFactory extends DefaultOAuth2RequestFactory {

public static final String SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME = "savedAuthorizationRequest";

public CustomOAuth2RequestFactory(ClientDetailsService clientDetailsService) {
super(clientDetailsService);
}

@Override
public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession(false);
if (session != null) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
if (authorizationRequest != null) {
session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
return authorizationRequest;
}
}

return super.createAuthorizationRequest(authorizationParameters);
}
}

此自定义 OAuth2RequestFactory 设置为授权服务器,如:

<bean id="customOAuth2RequestFactory" class="com.example.CustomOAuth2RequestFactory">
<constructor-arg index="0" ref="clientDetailsService" />
</bean>

<!-- Configures the authorization-server and provides the /oauth/authorize endpoint -->
<oauth:authorization-server client-details-service-ref="clientDetailsService" token-services-ref="tokenServices"
user-approval-handler-ref="approvalStoreUserApprovalHandler" redirect-resolver-ref="redirectResolver"
authorization-request-manager-ref="customOAuth2RequestFactory">
<oauth:authorization-code authorization-code-services-ref="authorizationCodeServices"/>
<oauth:implicit />
<oauth:refresh-token />
<oauth:client-credentials />
<oauth:password />
</oauth:authorization-server>

使用 java 配置时,您可以创建一个 TwoFactorAuthenticationInterceptor 而不是 TwoFactorAuthenticationFilter 并将其注册到 AuthorizationServerConfigurer

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig implements AuthorizationServerConfigurer {
...

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.addInterceptor(twoFactorAuthenticationInterceptor())
...
.requestFactory(customOAuth2RequestFactory());
}

@Bean
public HandlerInterceptor twoFactorAuthenticationInterceptor() {
return new TwoFactorAuthenticationInterceptor();
}
}

TwoFactorAuthenticationInterceptor 在其 preHandle 方法中包含与 TwoFactorAuthenticationFilter 相同的逻辑。

关于spring - 使用 Spring Security oauth2 进行两因素身份验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30319666/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com