You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改Spring Boot的AppUsersController以支持Auth0 JWT注册登录?

问题描述

我有一个基于Spring Boot 3.0.4的REST API,原本使用HTTP Basic认证,现在切换为Auth0的JWT认证。已通过Spring Boot OAuth2 Resource Server完成JWT认证配置,安全配置代码如下:

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfiguration {

    public static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
            new AntPathRequestMatcher("/users/**", "POST"),
            new AntPathRequestMatcher("/error/**")
    );

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .cors(Customizer.withDefaults())
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(PUBLIC_URLS).permitAll()
                        .anyRequest().authenticated())
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
                .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

已在Auth0控制台创建API,并在application.properties中添加配置:

spring.security.oauth2.resourceserver.jwt.issuer-uri=https://<my-domain-here>.us.auth0.com/
spring.security.oauth2.resourceserver.jwt.audiences=localhost:8000
auth0.audience=localhost:8000

我遵循了Auth0官方指南,但指南仅覆盖端点安全配置,未说明客户端如何获取访问令牌,以及服务器如何将用户与令牌关联。

现有AppUsersController适配原HTTP Basic认证,用于注册和获取当前用户:

@RestController
@RequestMapping(value = "users", produces = MediaType.APPLICATION_JSON_VALUE)
public class AppUsersController {

    private final AppUserRepository repository;
    private final PasswordEncoder encoder;

    public AppUsersController(AppUserRepository repository, PasswordEncoder encoder) {
        this.repository = repository;
        this.encoder = encoder;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public AppUserInfoDto register(@RequestBody @Valid AppUserRegistrationDto registration) {

        String username = registration.email();

        if (repository.findByUsername(username).isPresent()) {
            throw new UsernameAlreadyExistsException(username);
        }

        AppUser user = repository.save(registration.toUser(encoder));

        return AppUserInfoDto.fromAppUser(user);
    }

    @GetMapping
    public AppUserInfoDto current(@AuthenticationPrincipal User user) {
        return repository
                .findByUsername(user.getUsername())
                .map(AppUserInfoDto::fromAppUser)
                .orElseThrow(AppUserNotFoundException::new);
    }
}

同时有UserDetailsService实现:

@Service
public class AppUserDetailsService implements UserDetailsService {

    private final AppUserRepository userRepository;

    public AppUserDetailsService(AppUserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        AppUser user = userRepository
                .findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("The username " + username + " was not found"));

        return User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .authorities(Collections.emptyList())
                .build();
    }
}

目前多数指南要么是自签名JWT,要么是为Web应用配置登录,而非REST API。虽有Angular/React客户端的令牌获取示例,但我需要支持curl、Postman或JavaFX客户端的注册与登录。请问如何修改AppUsersController以适配Auth0 JWT认证?


解决方案

1. 明确Auth0 JWT认证的核心逻辑

Auth0作为OAuth2身份提供者,登录(获取JWT令牌)不需要自己编写接口,客户端直接调用Auth0的/oauth/token端点获取令牌;你的API只需要验证令牌的合法性,并从令牌中提取用户信息关联到本地数据库。

2. 调整注册流程

如果要保留自己的注册接口,需要在本地保存用户的同时,同步在Auth0创建用户(否则用户无法通过Auth0获取令牌)。可以通过Auth0 Management API实现这一步:

步骤2.1 添加Auth0 Management API依赖

在pom.xml中添加:

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>auth0</artifactId>
    <version>1.40.0</version>
</dependency>

步骤2.2 配置Auth0 Management API凭证

在application.properties中添加:

auth0.management.client-id=你的Auth0 Management API客户端ID
auth0.management.client-secret=你的Auth0 Management API客户端密钥
auth0.management.domain=https://<my-domain-here>.us.auth0.com/

步骤2.3 修改注册接口

@RestController
@RequestMapping(value = "users", produces = MediaType.APPLICATION_JSON_VALUE)
public class AppUsersController {

    private final AppUserRepository repository;
    private final PasswordEncoder encoder;
    private final ManagementAPI auth0ManagementAPI;

    // 构造注入Auth0 ManagementAPI客户端
    public AppUsersController(AppUserRepository repository, PasswordEncoder encoder, @Value("${auth0.management.domain}") String domain,
                              @Value("${auth0.management.client-id}") String clientId,
                              @Value("${auth0.management.client-secret}") String clientSecret) {
        this.repository = repository;
        this.encoder = encoder;
        // 初始化Auth0 ManagementAPI客户端
        Authentication authentication = AuthenticationAPI.newBuilder(domain)
                .withClient(clientId)
                .withClientSecret(clientSecret)
                .build()
                .requestToken("https://" + domain + "/api/v2/")
                .execute()
                .getBody();
        this.auth0ManagementAPI = ManagementAPI.newBuilder(domain, authentication.getAccessToken())
                .build();
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public AppUserInfoDto register(@RequestBody @Valid AppUserRegistrationDto registration) {
        String username = registration.email();

        if (repository.findByUsername(username).isPresent()) {
            throw new UsernameAlreadyExistsException(username);
        }

        // 1. 保存用户到本地数据库
        AppUser user = repository.save(registration.toUser(encoder));

        // 2. 在Auth0创建用户
        User auth0User = User.newBuilder()
                .setEmail(username)
                .setPassword(registration.password()) // 注意:Auth0要求密码符合其复杂度规则
                .setEmailVerified(true) // 根据需求设置是否需要邮箱验证
                .build();
        try {
            auth0ManagementAPI.users().create(auth0User).execute();
        } catch (APIException | Auth0Exception e) {
            // 回滚本地用户保存(可添加事务管理)
            repository.delete(user);
            throw new RuntimeException("Failed to create user in Auth0", e);
        }

        return AppUserInfoDto.fromAppUser(user);
    }

    // ... 其他方法
}

注意:需要在Auth0控制台给Management API客户端分配create:users权限。

3. 修改current接口适配JWT认证

JWT认证后,Spring Security会将Jwt对象作为认证主体,而非原Basic认证的User对象。需要调整接口获取用户信息:

方法3.1 直接从Jwt提取用户名

@GetMapping
public AppUserInfoDto current(@AuthenticationPrincipal Jwt jwt) {
    // JWT的sub字段通常是用户唯一标识,email字段是用户邮箱(需在Auth0控制台配置JWT包含email声明)
    String username = jwt.getClaimAsString("email");
    return repository
            .findByUsername(username)
            .map(AppUserInfoDto::fromAppUser)
            .orElseThrow(AppUserNotFoundException::new);
}

方法3.2 自定义JWT认证转换器(更优雅)

如果希望将Jwt转换为自定义的用户对象,可配置JwtAuthenticationConverter:

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfiguration {

    // ... 其他配置

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        // 配置权限提取规则(如果需要)
        grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");

        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        // 设置自定义主体转换器,将Jwt转换为AppUser或UserDetails
        converter.setPrincipalClaimName("email"); // 用email作为用户名
        return converter;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .cors(Customizer.withDefaults())
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(PUBLIC_URLS).permitAll()
                        .anyRequest().authenticated())
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())))
                .build();
    }
}

此时current接口可以继续使用@AuthenticationPrincipal User,或者自定义AppUserDetails对象。

4. 客户端如何获取令牌(curl/Postman示例)

curl示例

curl --request POST \
  --url https://<your-auth0-domain>/oauth/token \
  --header 'content-type: application/json' \
  --data '{
    "client_id": "你的Auth0应用客户端ID",
    "client_secret": "你的Auth0应用客户端密钥",
    "audience": "localhost:8000",
    "grant_type": "password",
    "username": "用户邮箱",
    "password": "用户密码"
  }'

注意:需要在Auth0控制台启用Password授权类型(仅用于信任的客户端如JavaFX,不建议公开客户端使用)。

Postman示例

  1. 新建POST请求,URL为https://<your-auth0-domain>/oauth/token
  2. Headers添加Content-Type: application/json
  3. Body选择raw,输入JSON:
{
  "client_id": "你的客户端ID",
  "client_secret": "你的客户端密钥",
  "audience": "localhost:8000",
  "grant_type": "password",
  "username": "user@example.com",
  "password": "user-password"
}
  1. 发送请求即可获取包含access_token的响应。

5. 移除不必要的组件

由于不再使用HTTP Basic认证,AppUserDetailsService和PasswordEncoder(除了注册时加密本地密码)可以考虑调整:如果本地数据库不再存储密码(完全依赖Auth0管理密码),可以移除PasswordEncoder和UserDetailsService相关代码。


内容的提问来源于stack exchange,提问作者Amal K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 00:37:03