首页 > 解决方案 > 通过扩展 WebSecurityConfigurerAdapter,如何使用自定义身份验证逻辑构造配置

问题描述

我正在使用 okta 进行身份验证。我们公司的 okta 禁用了“默认”授权服务器。所以现在我不能使用'okta-spring-security-starter'来简单地验证从url标头传递的令牌:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class OktaOAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/health").permitAll()
                .anyRequest().authenticated()
                .and()
                .oauth2ResourceServer().jwt();

        http.cors();

        Okta.configureResourceServer401ResponseBody(http);

    }
}

所以我需要点击 okta introspect 端点(https://developer.okta.com/docs/reference/api/oidc/#introspect)来验证。所以我想知道我可以将此过程集成到WebSecurityConfigurerAdapter. 也许是这样的???:

import com.okta.spring.boot.oauth.Okta;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class OktaOAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/health").permitAll()
                .anyRequest().authenticated()
                .and()
                /*add something there*/

        http.cors();


    }
}

我看到了类似覆盖 AuthenticationProvider(带有 Spring Security 和 Java Config 的自定义身份验证提供程序),并使用 httpbasic auth。如果我使用 .oauth2ResourceServer().jwt(),我可以做类似的事情吗?

我的想法是覆盖身份验证提供程序并在提供程序中,点击 okta introspect 端点,这会工作吗???

标签: javaspring-bootspring-securityoktaokta-api

解决方案


我不使用 Okta,因此我不知道它是如何工作的。但我有两个假设:

  • 每个请求的 Authorization 标头中都包含一个 accessToken
  • 您向 ${baseUrl}/v1/introspect 发出 POST 请求,它会以 true 或 false 回答您,以表明 accessToken 是否有效

考虑到这两个假设,如果我必须手动实现自定义安全逻辑身份验证,我将执行以下步骤:

  • 注册并实施CustomAuthenticationProvider
  • 添加过滤器以从请求中提取访问令牌

注册自定义身份验证提供程序:

// In OktaOAuth2WebSecurityConfig.java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(customAuthenticationProvider());
}

@Bean
CustomAuthenticationProvider customAuthenticationProvider(){
    return new CustomAuthenticationProvider();
}

自定义身份验证提供者:

public class CustomAuthenticationProvider implements AuthenticationProvider {

private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationProvider.class);

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    logger.debug("Authenticating authenticationToken");
    OktaTokenAuthenticationToken auth = (OktaTokenAuthenticationToken) authentication;
    String accessToken = auth.getToken();

    // You should make a POST request to ${oktaBaseUrl}/v1/introspect
    // to determine if the access token is good or bad

    // I just put a dummy if here

    if ("ThanhLoyal".equals(accessToken)){
        List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("USER"));
        logger.debug("Good access token");
        return new UsernamePasswordAuthenticationToken(auth.getPrincipal(), "[ProtectedPassword]", authorities);
    }
    logger.debug("Bad access token");
    return null;
}

@Override
public boolean supports(Class<?> clazz) {
    return clazz == OktaTokenAuthenticationToken.class;
}

}

要注册过滤器以从请求中提取 accessToken:

// Still in OktaOAuth2WebSecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .addFilterAfter(accessTokenExtractorFilter(), UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests().anyRequest().authenticated();
            // And other configurations

}

@Bean
AccessTokenExtractorFilter accessTokenExtractorFilter(){
    return new AccessTokenExtractorFilter();
}

和它自己的过滤器:

public class AccessTokenExtractorFilter extends OncePerRequestFilter {

private static final Logger logger = LoggerFactory.getLogger(AccessTokenExtractorFilter.class);

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    logger.debug("Filtering request");
    Authentication authentication = getAuthentication(request);
    if (authentication == null){
        logger.debug("Continuing filtering process without an authentication");
        filterChain.doFilter(request, response);
    } else {
        logger.debug("Now set authentication on the request");
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }
}

private Authentication getAuthentication(HttpServletRequest request) {
    String accessToken = request.getHeader("Authorization");
    if (accessToken != null){
        logger.debug("An access token found in request header");
        List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("USER"));
        return new OktaTokenAuthenticationToken(accessToken, authorities);
    }

    logger.debug("No access token found in request header");
    return null;
}

}

我在这里上传了一个简单的项目供您参考:https ://github.com/MrLoyal/spring-security-custom-authentication

这个怎么运作:

  • AccessTokenExtractorFilter 放在 UsernamePasswordAuthenticationFilter 之后,这是 Spring Security 的默认过滤器
  • 一个请求到达,上面的过滤器从中提取 accessToken 并放在 SecurityContext 中
  • 稍后,AuthenticationManager 调用 AuthenticationProvider(s) 来验证请求。这种情况下,调用了 CustomAuthenticationProvider

顺便说一句,您的问题应该包含spring-security标签。

更新 1:关于 AuthenticationEntryPoint

AnAuthenticationEntryPoint声明当未经身份验证的请求到达时要做什么(在我们的例子中,当请求不包含有效的“授权”标头时要做什么)。

在我的 REST API 中,我只是向客户端响应 401 HTTP 状态代码。

// CustomAuthenticationEntryPoint
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    response.reset();
    response.setStatus(401);
    // A utility method to add CORS headers to the response
    SecUtil.writeCorsHeaders(request, response);
}

LoginUrlAuthenticationEntryPoint如果配置了一个,Spring会将用户重定向到登录页面。

因此,如果您想将未经身份验证的请求重定向到 Okta 的登录页面,您可以使用 AuthenticationEntryPoint。


推荐阅读