首页 > 解决方案 > Spring Security - 使用带有 Auth0 预身份验证的自定义数据库授权

问题描述

我目前正在使用 Spring Security 开发 Spring Boot REST 应用程序。我的工作场所使用 Auth0(提供用户管理的外部第三方服务)进行身份验证,并要求我在此应用程序中实现它。身份验证发生在用 React 编写的前端应用程序中。前端应用程序显示登录表单并将用户名和密码发送到 Auth0,Auth0 验证凭据并在验证用户时返回 JWT 令牌。

之后,前端应用程序将从我的应用程序调用 REST 服务,并在Authorize标头中传递 JWT 令牌。Spring Security 使用 Auth0 插件验证此令牌并允许执行请求。我已经测试了这么多可以按预期工作。代码如下:

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import com.auth0.spring.security.api.JwtWebSecurityConfigurer;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
    
    @Value(value = "${auth0.apiAudience}")
    private String apiAudience;
    @Value(value = "${auth0.issuer}")
    private String issuer;
    
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:8080"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        configuration.setAllowCredentials(true);
        configuration.addAllowedHeader("Authorization");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors();
        JwtWebSecurityConfigurer  //Auth0 provided class performs per-authentication using JWT token
            .forRS256(apiAudience, issuer)
            .configure(http)
            .authorizeRequests()
            .antMatchers(HttpMethod.GET, "/Test/public").permitAll()
            .antMatchers(HttpMethod.GET, "/Test/authenticated").authenticated();
    }
    
}

现在,一旦完成此身份验证,我观察到安全上下文中的主体会使用来自 Auth0 的用户 ID 进行更新。我已经通过这段代码片段验证了这一点:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String name = authentication.getName(); // Returns the Auth0 user id.

我希望做的下一步是使用此用户 ID 将用户与我现有数据库模式中的角色和权限相匹配。因此,我需要实现一个自定义的授权机制,该机制也插入到 Spring Security 中。换句话说,用户的角色必须在(预)认证完成后不久加载到安全上下文中。我该如何实施?是否有一些类需要扩展或实现某些接口?

标签: javaspringspring-bootspring-mvcspring-security

解决方案


我认为您正在寻找的是AuthenticationProvider界面。以下是我如何处理身份验证的两个示例:

道认证

@Component
public class DaoAdminAuthenticationProvider extends DaoAuthenticationProvider {
private static final Logger LOG = 
LoggerFactory.getLogger(DaoAdminAuthenticationProvider.class);

private final AdminUserRepository adminUserRepository;

public DaoAdminAuthenticationProvider(AdminUserRepository adminUserRepository, DaoAdminUserDetailsService daoAdminUserDetailsService) {
    this.adminUserRepository = adminUserRepository;
    setPasswordEncoder(new BCryptPasswordEncoder(11));
    this.setUserDetailsService(daoAdminUserDetailsService);
}

@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {

    AdminUser adminUser = adminUserRepository.findByEmail(auth.getName());
    if (adminUser == null) {
        LOG.info("Invalid username or password");
        throw new BadCredentialsException("Invalid username or password");
    }

    Authentication result = super.authenticate(auth);
    return new UsernamePasswordAuthenticationToken(adminUser, result.getCredentials(), result.getAuthorities());
}

@Override
public boolean supports(Class<?> authentication) {
    return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}

JwtAuthenticationProvider

@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOG = 
LoggerFactory.getLogger(JwtAuthenticationProvider.class);

private static final String EX_TOKEN_INVALID = "jwt.token.invalid";

private final JwtTokenService jwtTokenService;

@SuppressWarnings("unused")
public JwtAuthenticationProvider() {
    this(null);
}

@Autowired
public JwtAuthenticationProvider(JwtTokenService jwtTokenService) {
    this.jwtTokenService = jwtTokenService;
}

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

    try {
        String token = (String) authentication.getCredentials();
        String username = jwtTokenService.getUsernameFromToken(token);

        return jwtTokenService.validateToken(token)
                .map(aBoolean -> new JwtAuthenticatedProfile(username))
                .orElseThrow(() -> new TokenException(EX_TOKEN_INVALID));

    } catch (JwtException ex) {
        LOG.error("Invalid JWT Token");
        throw new TokenException(EX_TOKEN_INVALID);
    }
}

@Override
public boolean supports(Class<?> authentication) {
    return JwtAuthentication.class.equals(authentication);
}
}

其他类,如JwtTokenService等。我也实现了。但关于你的问题,我认为答案是使用AuthenticationProvider界面。


推荐阅读