1. 概述

Auth0 是一个成熟的第三方身份认证与授权平台,支持多种应用类型,包括原生应用、单页应用(SPA)和 Web 应用。它提供了开箱即用的功能,比如:

✅ 单点登录(SSO)
✅ 社交登录(如 Google、GitHub)
✅ 多因素认证(MFA)
✅ 用户自注册与管理

本文将带你一步步在 Spring Boot 项目中集成 Auth0,结合 Spring Security 实现安全控制。重点覆盖 Auth0 账户配置、Spring Boot 安全配置、登录/登出流程,以及如何调用 Auth0 管理 API。

⚠️ 注意:本文假设你已有 Spring Security 基础,不赘述基础概念。


2. Auth0 账户配置

2.1. 注册 Auth0 账户

前往 Auth0 官网注册 一个免费账户。免费版支持最多 7000 名活跃用户,不限登录次数,足够用于学习和中小型项目。

auto0_2_1

2.2. 进入控制台

注册并登录后,你会看到 Auth0 仪表盘,展示登录活动、最新登录记录等信息。

auto0_2_2

2.3. 创建新应用

在左侧菜单选择 ApplicationsCreate Application

创建时注意选择应用类型:

Regular Web Applications(适用于 Spring Boot 这类服务端渲染应用)
❌ Native Apps / Single-Page Apps / Machine to Machine Apps(不适用)

auto0_2_3

2.4. 配置应用回调地址

创建完成后,进入应用设置页,配置以下 URI:

  • Allowed Callback URLs: http://localhost:8080/callback
  • Allowed Logout URLs: http://localhost:8080/

这些是 Auth0 在认证完成后回调你的应用的地址。

auto0_2_4

2.5. 获取客户端凭证

记下以下三个关键信息,后续配置 Spring Boot 会用到:

  • Domain: dev-example.auth0.com
  • Client ID: your-client-id-here
  • Client Secret: your-client-secret-here

auto0_2_5

⚠️ 踩坑提示:Client Secret 非常敏感,不要提交到代码仓库。


3. Spring Boot 项目配置

3.1. Maven 依赖

引入 Auth0 提供的 mvc-auth-commons 依赖,简化 OpenID Connect 集成:

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>mvc-auth-commons</artifactId>
    <version>1.2.0</version>
</dependency>

3.2. Gradle 依赖

如果你用 Gradle:

compile 'com.auth0:mvc-auth-commons:1.2.0'

3.3. application.properties

将 Auth0 凭证写入配置文件:

com.auth0.domain=dev-example.auth0.com
com.auth0.clientId=your-client-id-here
com.auth0.clientSecret=your-client-secret-here

3.4. AuthConfig 配置类

创建安全配置类,启用 Spring Security 并定义访问规则:

@Configuration
@EnableWebSecurity
public class AuthConfig {

    @Value("${com.auth0.domain}")
    private String domain;

    @Value("${com.auth0.clientId}")
    private String clientId;

    @Value("${com.auth0.clientSecret}")
    private String clientSecret;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
                .antMatchers("/callback", "/login", "/").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin().loginPage("/login")
            .and()
            .logout().logoutSuccessHandler(logoutSuccessHandler()).permitAll();
        return http.build();
    }

    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return new LogoutController(this);
    }
}

✅ 关键点:

  • 关闭 CSRF(简单示例中可接受)
  • /login, /callback, / 允许匿名访问
  • 其他请求需认证
  • 登出使用自定义 LogoutSuccessHandler

3.5. AuthenticationController Bean

注册 AuthenticationController,用于生成授权 URL 和处理回调:

@Bean
public AuthenticationController authenticationController() throws UnsupportedEncodingException {
    JwkProvider jwkProvider = new JwkProviderBuilder(domain).build();
    return AuthenticationController.newBuilder(domain, clientId, clientSecret)
        .withJwkProvider(jwkProvider)
        .build();
}

📌 原理:JwkProvider 用于从 Auth0 获取公钥,验证 JWT 签名(默认使用 RS256 算法)。


4. AuthController:登录与回调

创建控制器处理登录跳转和 Auth0 回调。

@Controller
public class AuthController {

    @Autowired
    private AuthConfig config;

    @Autowired
    private AuthenticationController authenticationController;
}

4.1. 登录接口

@GetMapping("/login")
protected void login(HttpServletRequest request, HttpServletResponse response) {
    String redirectUri = "http://localhost:8080/callback";
    String authorizeUrl = authenticationController.buildAuthorizeUrl(request, response, redirectUri)
        .withScope("openid email")
        .build();
    response.sendRedirect(authorizeUrl);
}

✅ 说明:

  • buildAuthorizeUrl 生成 Auth0 登录页跳转链接
  • scope=openid email 表示请求用户身份和邮箱信息

4.2. 回调接口

@GetMapping("/callback")
public void callback(HttpServletRequest request, HttpServletResponse response) {
    Tokens tokens = authenticationController.handle(request, response);
    
    DecodedJWT jwt = JWT.decode(tokens.getIdToken());
    TestingAuthenticationToken authToken = new TestingAuthenticationToken(
        jwt.getSubject(), jwt.getToken()
    );
    authToken.setAuthenticated(true);
    
    SecurityContextHolder.getContext().setAuthentication(authToken);
    response.sendRedirect(config.getContextPath(request) + "/");
}

⚠️ 踩坑:TestingAuthenticationToken 是 Spring Security 的测试用 Token,生产环境建议自定义 AbstractAuthenticationToken 实现。


5. HomeController:主页展示

展示用户信息的主页:

@Controller
public class HomeController {

    @GetMapping("/")
    @ResponseBody
    public String home(Authentication authentication) {
        TestingAuthenticationToken token = (TestingAuthenticationToken) authentication;
        DecodedJWT jwt = JWT.decode(token.getCredentials().toString());
        String email = jwt.getClaims().get("email").asString();
        return "Welcome, " + email + "!";
    }
}

运行应用:mvn spring-boot:run

访问 http://localhost:8080/login,跳转至 Auth0 登录页:

auto0_5_1

登录后显示欢迎信息:

auto0_5_2


6. 用户注册

6.1. 自注册

Auth0 登录页自带“Sign Up”按钮,用户可自行注册:

auto0_6_1

6.2. 后台创建用户

也可在 Auth0 控制台手动创建用户:

auto0_6_2

6.3. 连接方式配置

支持多种登录方式,如数据库认证、社交登录:

auto0_6_3

支持的社交登录包括:

auto0_6_4


7. LogoutController:登出功能

实现 LogoutSuccessHandler,登出时清空会话并跳转 Auth0 注销接口:

@Controller
public class LogoutController implements LogoutSuccessHandler {

    private final AuthConfig config;

    public LogoutController(AuthConfig config) {
        this.config = config;
    }

    @Override
    public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse res,
                               Authentication authentication) {
        if (req.getSession() != null) {
            req.getSession().invalidate();
        }
        String returnTo = "http://localhost:8080/";
        String logoutUrl = "https://" + config.getDomain() + "/v2/logout?client_id=" +
            config.getClientId() + "&returnTo=" + returnTo;
        try {
            res.sendRedirect(logoutUrl);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

📌 注意:登出必须调用 Auth0 的 /v2/logout 接口,否则单点登录状态未清除。


8. 调用 Auth0 管理 API

8.1. 创建 Machine to Machine 应用

在 Auth0 控制台创建一个 Machine to Machine Application,用于调用管理 API:

auto0_8_1

8.2. 授权管理 API 权限

为该应用授予 read:userscreate:users 权限:

auto0_8_2

8.3. 获取客户端凭证

记录新应用的 Client ID 和 Secret:

auto0_8_3

8.4. 获取管理 API Token

使用客户端凭证模式获取访问令牌:

public String getManagementApiToken() {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);

    JSONObject requestBody = new JSONObject();
    requestBody.put("client_id", "mgmt-client-id");
    requestBody.put("client_secret", "mgmt-client-secret");
    requestBody.put("audience", "https://dev-example.auth0.com/api/v2/");
    requestBody.put("grant_type", "client_credentials");

    HttpEntity<String> request = new HttpEntity<>(requestBody.toString(), headers);
    RestTemplate restTemplate = new RestTemplate();
    Map<String, String> result = restTemplate.postForObject(
        "https://dev-example.auth0.com/oauth/token", request, Map.class);
    return result.get("access_token");
}

📌 关键参数:

  • audience: 必须是 https://{domain}/api/v2/
  • grant_type: client_credentials

8.5. UserController:获取用户列表

@Controller
public class UserController {

    @GetMapping("/users")
    @ResponseBody
    public ResponseEntity<String> users() {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + getManagementApiToken());
        HttpEntity<String> entity = new HttpEntity<>(headers);
        
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> result = restTemplate.exchange(
            "https://dev-example.auth0.com/api/v2/users",
            HttpMethod.GET, entity, String.class);
        return result;
    }
}

访问 http://localhost:8080/users 返回用户列表:

[{
    "created_at": "2020-05-05T14:38:18.955Z",
    "email": "user@example.com",
    "email_verified": true,
    "identities": [{
        "user_id": "5eb17a5a1cc1ac0c1487c37f78758",
        "provider": "auth0",
        "connection": "Username-Password-Authentication",
        "isSocial": false
    }],
    "name": "user@example.com",
    "nickname": "ansh",
    "logins_count": 64
}]

8.6. 创建用户

@GetMapping("/createUser")
@ResponseBody
public ResponseEntity<String> createUser() {
    JSONObject request = new JSONObject();
    request.put("email", "newuser@example.com");
    request.put("given_name", "Norman");
    request.put("family_name", "Lewis");
    request.put("connection", "Username-Password-Authentication");
    request.put("password", "Pa33w0rd");

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.set("Authorization", "Bearer " + getManagementApiToken());

    HttpEntity<String> entity = new HttpEntity<>(request.toString(), headers);
    RestTemplate restTemplate = new RestTemplate();
    return restTemplate.postForEntity(
        "https://dev-example.auth0.com/api/v2/users", entity, String.class);
}

成功创建后返回用户详情:

{
    "created_at": "2020-05-10T12:30:15.343Z",
    "email": "newuser@example.com",
    "email_verified": false,
    "family_name": "Lewis",
    "given_name": "Norman",
    "identities": [{
        "connection": "Username-Password-Authentication",
        "user_id": "5eb7f3d76b69bc0c120a8901576",
        "provider": "auth0",
        "isSocial": false
    }],
    "name": "newuser@example.com",
    "nickname": "norman.lewis"
}

✅ 扩展:通过管理 API 还可操作连接、客户端、规则等资源。


9. 总结

本文完整演示了 Spring Boot + Spring Security + Auth0 的集成方案,涵盖:

  • Auth0 应用配置与凭证管理
  • 登录、回调、登出流程实现
  • 使用 TestingAuthenticationToken 快速集成(生产建议自定义)
  • 调用 Auth0 管理 API 实现用户 CRUD

所有代码示例已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-auth0

💡 建议:生产环境应使用 Spring Security OAuth2 Client 或 Spring Security 6 的新特性,避免直接操作 SecurityContextHolder


原始标题:Spring Security With Auth0