1. 概述
Auth0为各种类型的应用程序(如本机应用程序、单页应用程序和Web应用程序)提供身份验证和授权服务。此外,它还允许实现各种功能,如单点登录、社交App登录和多因素身份验证。
在本教程中,我们将通过分步指南探索Auth0的Spring Security,以及Auth0帐户的关键配置。
2. 设置Auth0
2.1 Auth0注册
首先,我们将注册一个免费的Auth0账户,该账号为多达7k的活动用户提供无限登录的访问权限。如果你已经有一个账号,我们可以跳过这一节:
2.2 仪表板
登录到Auth0帐户后,我们将看到一个仪表板,其中突出显示了登录记录、最新登录和最新注册等详细信息:
2.3 创建新应用程序
然后,从Applications菜单中,我们将为Spring Boot创建一个新的OpenID Connect(OIDC)应用程序。
此外,我们将从Native、Single-Page Apps和Machine to Machine Apps等可用选项中选择Regular Web Applications作为应用程序类型:
选择常规Web应用程序,填写App的名称:
2.4 应用程序设置
接下来,我们会得到一些应用程序URI,例如指向应用程序的Callback URL和Logout URL:
2.5 客户端凭据
最后,我们会得到与我们的应用关联的Domain、Client ID和Client Secret的值:
请保存好这些凭据,因为它们是我们Spring Boot应用程序中的Auth0设置所必需的。
3. Spring Boot应用程序设置
现在我们的Auth0帐户已准备好关键配置,我们准备将Auth0安全性集成到Spring Boot应用程序中。
3.1 Maven
首先,让我们将最新的mvc-auth-commons Maven依赖项添加到我们的pom.xml中:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>mvc-auth-commons</artifactId>
<version>1.2.0</version>
</dependency>
3.2 Gradle
同样,在使用Gradle时,我们可以在build.gradle文件中添加mvc-auth-commons依赖:
compile 'com.auth0:mvc-auth-commons:1.2.0'
3.3 application.properties
我们的Spring Boot应用程序需要Client Id和Client Secret等信息来启用Auth0帐户的身份验证。因此,我们需要将它们添加到application.properties文件中:
com.auth0.domain=dev-o9mb6xdi.us.auth0.com
com.auth0.clientId={clientId}
com.auth0.clientSecret={clientSecret}
3.4 AuthConfig
接下来,我们创建AuthConfig类以从application.properties文件中读取Auth0属性:
@Configuration
@EnableWebSecurity
public class AuthConfig {
@Value(value = "${com.auth0.domain}")
private String domain;
@Value(value = "${com.auth0.clientId}")
private String clientId;
@Value(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();
}
}
此外,AuthConfig类配置为通过创建SecurityFilterChain bean启用Web安全。
3.5 AuthenticationController
最后,我们将AuthenticationController类的bean引用添加到已经讨论过的AuthConfig类:
@Bean
public AuthenticationController authenticationController() throws UnsupportedEncodingException {
JwkProvider jwkProvider = new JwkProviderBuilder(domain).build();
return AuthenticationController.newBuilder(domain, clientId, clientSecret)
.withJwkProvider(jwkProvider)
.build();
}
在这里,我们在构建AuthenticationController类的实例时使用了JwkProviderBuilder类。我们将使用它获取公钥来验证token的签名(默认情况下,token使用RS256非对称签名算法进行签名)。
此外,authenticationController bean提供用于登录的授权URL并处理回调请求。
4. AuthController
接下来,我们将为登录和回调功能创建AuthController类:
@Controller
public class AuthController {
@Autowired
private AuthConfig config;
@Autowired
private AuthenticationController authenticationController;
}
在这里,我们注入了上一节中讨论的AuthConfig和AuthenticationController类型的bean。
4.1 登录
让我们创建允许我们的Spring Boot应用程序对用户进行身份验证的login方法:
@GetMapping(value = "/login")
protected void login(HttpServletRequest request, HttpServletResponse response) throws IOException {
String redirectUri = "http://localhost:8080/callback";
String authorizeUrl = authenticationController.buildAuthorizeUrl(request, response, redirectUri)
.withScope("openid email")
.build();
response.sendRedirect(authorizeUrl);
}
buildAuthorizeUrl方法生成Auth0授权URL并重定向到默认的Auth0登录页面。
4.2 回调
一旦用户使用Auth0凭据登录,回调请求将发送到我们的Spring Boot应用程序。为此,让我们创建callback方法:
@GetMapping(value = "/callback")
public void callback(HttpServletRequest request, HttpServletResponse response) throws IOException, IdentityVerificationException {
Tokens tokens = authenticationController.handle(request, response);
DecodedJWT jwt = JWT.decode(tokens.getIdToken());
TestingAuthenticationToken authToken2 = new TestingAuthenticationToken(jwt.getSubject(), jwt.getToken());
authToken2.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(authToken2);
response.sendRedirect(config.getContextPath(request) + "/");
}
我们处理回调请求,获取代表认证成功的accessToken和idToken。然后,我们创建了TestingAuthenticationToken对象来设置SecurityContextHolder中的authentication。
但是,我们可以创建AbstractAuthenticationToken类的实现以获得更好的可用性。
5. HomeController
最后,我们将为应用程序的登录页面创建具有默认映射的HomeController:
@Controller
public class HomeController {
@GetMapping(value = "/")
@ResponseBody
public String home(final Authentication authentication) {
TestingAuthenticationToken token = (TestingAuthenticationToken) authentication;
DecodedJWT jwt = JWT.decode(token.getCredentials().toString());
String email = jwt.getClaims().get("email").asString();
return "Welcome, " + email + "!";
}
}
这里,我们从idToken中提取了DecodedJWT对象。此外,用户信息(如电子邮件)是从claims中获取的。
就是这样!我们的Spring Boot应用程序已准备好提供Auth0安全支持。让我们使用Maven命令运行我们的应用程序:
mvn spring-boot:run
在localhost:8080/login访问应用程序时,我们可以看到Auth0提供的默认登录页面:
使用注册用户的凭据登录后,将显示包含用户电子邮件的欢迎消息:
此外,我们可以在默认登录屏幕上找到一个“Sign up”按钮,用于自行注册。
6. 注册
6.1 自行注册
第一次登录时,我们可以使用“Sign up”按钮创建一个Auth0帐户,然后提供电子邮件和密码等信息:
6.2 创建用户
或者,我们可以从Auth0帐户的Users菜单中创建一个新用户:
输入对应的信息:
6.3 连接设置
此外,我们可以选择各种类型的连接,例如数据库和社交登录,用于注册/登录我们的Spring Boot应用程序:
我们可以选择一系列社交软件,如Github、Google:
7. LogoutController
现在我们已经了解了登录和回调功能,我们可以向我们的Spring Boot应用程序添加注销功能。
让我们创建一个实现LogoutSuccessHandler的LogoutController类:
@Controller
public class LogoutController implements LogoutSuccessHandler {
@Autowired
private AuthConfig 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://dev-o9mb6xdi.us.auth0.com/v2/logout?client_id=" + config.getClientId() + "&returnTo=" + returnTo;
res.sendRedirect(logoutUrl);
}
}
在这里,onLogoutSuccess方法被覆盖以调用/v2/logout Auth0注销URL。
8. Auth0 Management API
到目前为止,我们已经在Spring Boot应用程序中集成了Auth0安全性。现在,让我们在同一个应用程序中与Auth0 Management API(系统API)进行交互。
8.1 创建新App
首先,要访问Auth0 Management API,我们将在Auth0帐户中创建一个Machine to Machine Application:
8.2 授权
然后,我们将向Auth0 Management API添加授权,使其具有读取/创建用户的权限:
8.3 客户端凭据
最后,我们可以得到Client Id和Client Secret,以从我们的Spring Boot应用程序访问Auth0 Management App:
8.4 Access Token
让我们使用上一节中得到的客户端凭据为Auth0 Management App生成访问令牌:
public String getManagementApiToken() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
JSONObject requestBody = new JSONObject();
requestBody.put("client_id", "hfUOjedOrkZhgDIkpv7QrO9gIL7c7gdl");
requestBody.put("client_secret", "2fWGr-LK3boq2axR0Z_aYUq0RTE5u8Z-tZiN_nRXOyEQVTPqPMIyWyAHeNm1e7Jx");
requestBody.put("audience", "https://dev-o9mb6xdi.us.auth0.com/api/v2/");
requestBody.put("grant_type", "client_credentials");
HttpEntity<String> request = new HttpEntity<>(requestBody.toString(), headers);
RestTemplate restTemplate = new RestTemplate();
HashMap<String, String> result = restTemplate.postForObject("https://dev-o9mb6xdi.us.auth0.com/oauth/token", request, HashMap.class);
return result.get("access_token");
}
在这里,我们向/oauth/token Auth0令牌URL发出REST请求,以获取访问和刷新令牌。
此外,我们可以将这些客户端凭据存储在application.properties文件中,并使用AuthConfig类读取它。
8.5 UserController
之后,让我们创建一个包含users方法的UserController类:
@Controller
public class UserController {
@GetMapping(value = "/users")
@ResponseBody
public ResponseEntity<String> users(HttpServletRequest request, HttpServletResponse response) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + getManagementApiToken());
HttpEntity<String> entity = new HttpEntity<String>(headers);
RestTemplate restTemplate = new RestTemplate();
return restTemplate
.exchange("https://dev-o9mb6xdi.us.auth0.com/api/v2/users", HttpMethod.GET, entity, String.class);
}
}
users方法通过使用上一节中生成的访问令牌向/api/v2/users Auth0 API发出GET请求来获取所有用户的列表。
因此,当我们访问localhost:8080/users时,可以得到包含所有用户的JSON响应:
[
{
"created_at": "2022-04-01T17:52:28.767Z",
"email": "tuyucheng2000@163.com",
"email_verified": false,
"identities": [
{
"connection": "Username-Password-Authentication",
"provider": "auth0",
"user_id": "62473bdc9bf338006971b250",
"isSocial": false
}
],
"name": "tuyucheng2000@163.com",
"nickname": "tuyucheng2000",
"picture": "https://s.gravatar.com/avatar/bb867b73be298109d59a71b441e6d53d?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Ftu.png",
"updated_at": "2022-10-19T12:54:21.800Z",
"user_id": "auth0|62473bdc9bf338006971b250",
"last_login": "2022-10-19T12:54:21.800Z",
"last_ip": "45.62.168.61",
"logins_count": 4
}
]
8.6 创建用户
同样,我们可以通过向/api/v2/users Auth0 API发出POST请求来创建用户:
@GetMapping(value = "/createUser")
@ResponseBody
public ResponseEntity<String> createUser(HttpServletResponse response) {
JSONObject request = new JSONObject();
request.put("email", "norman.lewis@email.com");
request.put("given_name", "Norman");
request.put("family_name", "Lewis");
request.put("connection", "Username-Password-Authentication");
request.put("password", "Pa33w0rd");
// ...
return restTemplate.postForEntity("https://dev-o9mb6xdi.us.auth0.com/api/v2/users", request.toString(), String.class);
}
然后,让我们访问localhost:8080/createUser并验证新用户的详细信息:
{
"created_at": "2022-04-01T17:54:44.296Z",
"email": "norman.lewis@email.com",
"email_verified": false,
"family_name": "Lewis",
"given_name": "Norman",
"identities": [
{
"connection": "Username-Password-Authentication",
"user_id": "62473c647ca173006f55bf23",
"provider": "auth0",
"isSocial": false
}
],
"name": "norman.lewis@email.com",
"nickname": "norman.lewis",
"picture": "https://s.gravatar.com/avatar/b3d4fa673c5dc9c06d7c4953605a8fd1?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fno.png",
"updated_at": "2022-04-01T17:54:44.296Z",
"user_id": "auth0|62473c647ca173006f55bf23"
}
类似地,我们可以执行各种操作,例如列出所有连接、创建连接、列出所有客户端以及使用Auth0 API创建客户端,具体取决于我们的权限。
9. 总结
在本教程中,我们通过Spring Boot和Spring Security集成了Auth0。
首先,我们使用基本配置设置Auth0帐户。然后,我们创建了一个Spring Boot应用程序并配置了application.properties,以便将Spring Security与Auth0集成。
接下来,我们研究了如何为Auth0 Management API创建API令牌。最后,我们研究了获取所有用户和创建用户等功能。
与往常一样,本教程的完整源代码可在GitHub上获得。