firebase使用
Security is imperative when writing web apps; but security and authentication are hard to do right. When developing client/server applications, I want to take advantage of solutions that are written by teams that really know how to do security well. For a recent project, I used the combination of Firebase for user authentication and JWT for securing the server-side APIs.
编写Web应用程序时,安全性势在必行; 但是安全性和身份验证很难做到。 在开发客户端/服务器应用程序时,我想利用真正了解如何做好安全性的团队编写的解决方案。 在最近的项目中,我将Firebase用于用户身份验证,并将JWT用于保护服务器端API。
In this article, the server is a REST API written in Micronaut. All authentication and security is done using bearer tokens. The security flow is simple:
在本文中,服务器是用Micronaut编写的REST API。 所有身份验证和安全性均使用承载令牌完成。 安全流程很简单:
- A web or mobile app authenticates a user against Firebase and retrieves an id token 网络或移动应用通过Firebase对用户进行身份验证并检索ID令牌
- An authentication request is then made to our REST server. The request includes the id token where it is verified against Firebase. 然后向我们的REST服务器发出身份验证请求。 该请求包含ID令牌,并在其中针对Firebase进行了验证。
- The server responds to the authentication request with a JSON web token (JWT). 服务器使用JSON Web令牌(JWT)响应身份验证请求。
- The user can then access secured endpoints using the JWT. 然后,用户可以使用JWT访问安全的端点。
My final solution is relatively simple, but as a new-comer to using Micronaut, it took a very long time to figure out. In order to help save you time, I’ve laid out the steps needed to implement this security flow.
我的最终解决方案相对简单,但是作为使用Micronaut的新手,花了很长时间才弄清楚。 为了帮助您节省时间,我列出了实现此安全流程所需的步骤。
创建Micronaut应用 (Create the Micronaut App)
Use Micronaut’s CLI app, mn
, to create an app.
使用Micronaut的CLI应用程序mn
来创建一个应用程序。
mn create firebase-jwt --features security-jwt
cd firebase-jwt
Edit build.gradle
and add the following dependency:
编辑build.gradle
并添加以下依赖项:
implementation("com.google.firebase:firebase-admin:7.0.0")
Verify that your src/main/resources/application.yml
contains the security section like the listing below. Make sure authentication
is set to bearer
. You can set your secret to whatever value you’d like.
验证您的src/main/resources/application.yml
包含安全性部分,如下面的清单所示。 确保将authentication
设置为bearer
。 您可以将秘密设置为所需的任何值。
micronaut:
application:
name: firebaseJwt
security:
authentication: bearertoken:
jwt:
signatures:
secret:
generator:
secret: '"${JWT_GENERATOR_SIGNATURE_SECRET:pleaseChangeThisSecretForANewOne}"'
创建Firebase应用并下载私人证书 (Create a Firebase App and download private credentials)
This step assumes that you’ve created a Firebase app with authentication enabled. Follow these instructions to download your Google Application Credentials JSON file. Copy the Firebase credentials file to firebase-jwt/src/main/resources
. The name of the credentials file doesn’t really matter, but for this demo, name it firebase-adminsdk.json
.
此步骤假设您已创建启用了身份验证的Firebase应用。 请按照以下说明下载您的Google Application Credentials JSON文件。 将Firebase凭证文件复制到firebase-jwt/src/main/resources
。 凭证文件的名称并不重要,但对于此演示,将其命名firebase-adminsdk.json
。
创建Java类 (Create Java classes)
The Firebase.java
below initializes the firebase sdk and provides the verifyIdToken
method you’ll need later. The string firebase-adminsdk.json
refers to the Firebase credentials file you added to the resources directory. It should be just the name, no path or leading slash! Note the @PostConstruct
annotation, this tells Micronaut to execute the init()
method when our Firebase object is created.
下面的Firebase.java
初始化verifyIdToken
sdk,并提供您稍后需要的verifyIdToken
方法。 字符串firebase-adminsdk.json
您添加到资源目录中的Firebase凭证文件。 它应该只是名称,没有路径或斜杠! 请注意@PostConstruct
批注,它告诉Micronaut在创建Firebase对象时执行init()
方法。
Firebase.java (Firebase.java)
package firebase.jwt;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseToken;
import io.micronaut.core.io.ResourceResolver;
import io.micronaut.core.io.scan.ClassPathResourceLoader;
import java.io.IOException;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.inject.Singleton;
@Singleton
class Firebase {
private boolean initialized = false;
@PostConstruct
public void init() {
if (initialized) {
return;
}
try {
var loader = new ResourceResolver()
.getLoader(ClassPathResourceLoader.class)
.get();
var credentials = GoogleCredentials.fromStream(
loader.getResourceAsStream("firebase-adminsdk.json").get());
var options = FirebaseOptions.builder()
.setCredentials(credentials)
.build();
FirebaseApp.initializeApp(options);
initialized = true;
}
catch (IOException e) {
LoggerFactory
.getLogger(Firebase.class)
.error("Failed to initialize Firebase authentication", e);
}
}
public FirebaseToken verifyIdToken(String idToken) throws FirebaseAuthException {
return FirebaseAuth.getInstance().verifyIdToken(idToken);
}
}
AuthHeader.java(AuthHeader.java)
AuthHeader.java
is a simple helper class for parsing an Authorization header and for providing a nicely formatted JSON response.
AuthHeader.java
是一个简单的帮助程序类,用于解析Authorization标头并提供格式正确的JSON响应。
package firebase.jwt;
import java.util.Optional;
public class AuthHeader {
private final String type;
private final String token;
public AuthHeader(String type, String token) {
this.type = type;
this.token = token;
}
public String getType() {
return type;
}
public String getToken() {
return token;
}
public static Optional<AuthHeader> parse(String authentication) {
var opt = Optional.<AuthHeader>empty();
if (authentication != null) {
var parts = authentication.split("\\s+");
if (parts.length == 2) {
var auth = new AuthHeader(parts[0].trim(), parts[1].trim());
opt = Optional.of(auth);
}
}
return opt;
}
}
FirebaseController.java(FirebaseController.java)
FirebaseController.java
is the controller we will use to exchange the firebase token for our server’s JWT. This controller has two endpoints:
FirebaseController.java
是用于交换服务器JWT的firebase令牌的控制器。 该控制器具有两个端点:
POST /auth
: For exchanging the firebase id token for our server’s tokenPOST /auth
:用于将firebase id令牌交换为我们服务器的令牌GET /
: For testing our server’s jwtGET /
:用于测试服务器的jwt
A note about the @Secured
annotations. The annotation on the class tells Micronaut that, by default, the controller methods do not require any authentication. We override this security setting on the index()
method so that only authenticated requests (i.e. HTTP requests with a Authorization: Bearer <jwttoken>
header ) are allowed to access that method.
有关@Secured
批注的注释。 该类上的注释告诉Micronaut,默认情况下,控制器方法不需要任何身份验证。 我们在index()
方法上覆盖了此安全设置,以便仅允许经过身份验证的请求(即具有Authorization: Bearer <jwttoken>
标头的HTTP请求)访问该方法。
package firebase.jwt;
import javax.inject.Inject;
import com.google.firebase.auth.FirebaseAuthException;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.annotation.*;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.authentication.UserDetails;
import io.micronaut.security.rules.SecurityRule;
import io.micronaut.security.token.jwt.generator.JwtTokenGenerator;
import java.util.Collections;
import java.util.Optional;
@Controller("/firebase")
@Secured(SecurityRule.IS_ANONYMOUS)
public class FirebaseController {
JwtTokenGenerator jwtTokenGenerator;
Firebase firebase;
@Inject
public FirebaseController(JwtTokenGenerator jwtTokenGenerator, Firebase firebase) {
this.jwtTokenGenerator = jwtTokenGenerator;
this.firebase = firebase;
}
@Get(uri="/", produces="text/plain")
@Secured(SecurityRule.IS_AUTHENTICATED)
public String index() {
return "Example Response";
}
@Post("/auth")
public Optional<AuthHeader> auth(HttpRequest<?> request) throws FirebaseAuthException {
var auth = request.getHeaders().get("Authorization");
var opt = AuthHeader.parse(auth);
var out = Optional.<AuthHeader>empty();
if (opt.isPresent()) {
var authHeader = opt.get();
var firebaseToken = firebase.verifyIdToken(authHeader.getToken());
var userDetails = new UserDetails(firebaseToken.getName(),
Collections.emptyList(),
firebaseToken.getClaims());
var jwt = jwtTokenGenerator.generateToken(userDetails, 3600);
out = jwt.map(t -> new AuthHeader("Bearer", t));
}
return out;
}
}
尝试一下(Trying it out)
启动服务器应用(Launch the server app)
Start your server: gradlew run --info
启动服务器: gradlew run --info
In your web client app (which is not presented here), use the Firebase JavaScript API to authenticate and retrieve an idToken. With that in hand you can exchange it for a JWT token from your micronaut app.
在您的Web客户端应用程序(此处未介绍)中,使用Firebase JavaScript API进行身份验证和检索idToken。 有了它,您就可以从您的micronaut应用程序将其交换为JWT令牌。
请求一个JWT令牌: (Request a JWT token:)
Request
请求
First, submit a request to the /firebase/auth
endpoint with your firebase idToken as a bearer authorization. I’m using httpie to make the HTTP requests:
首先,使用您的Firebase idToken作为承载授权,向/firebase/auth
端点提交一个请求。 我正在使用httpie发出HTTP请求:
http POST localhost:8080/firebase/auth \
“Authorization: Bearer <firebaseidtoken>"
This command sends the following HTTP request to localhost:
此命令将以下HTTP请求发送到localhost:
POST /firebase/auth HTTP/1.1
Host 123.456.78.90:8080
User-Agent HTTPie/2.2.0
Accept-Encoding gzip, deflate
Accept */*
Authorization Bearer <firebaseidtoken>
Content-Length 0
Connection keep-alive
Response
响应
The server will respond with a JSON body that includes a JWT token. The JWT token can then be used to authenticate against other endpoints in your micronaut app.
服务器将以包含JWT令牌的JSON正文进行响应。 然后,可以使用JWT令牌针对您的micronaut应用程序中的其他端点进行身份验证。
HTTP/1.1 200 OK
Content-Type: application/json
Date: Fri, 18 Sep 2020 23:23:39 GMT
connection: keep-alive
content-length: 836{
"token": "<jwttoken>",
"type": "Bearer"
}
使用JWT令牌访问安全的端点 (Access a secured endpoint using the JWT token)
Copy the token value and use it in a get request to your secured endpoint:
复制令牌值,并在获取请求中使用它到安全的端点:
Request
请求
http localhost:8080/firebase "Authorization: Bearer <jwttoken>"
Which sends the following HTTP request:
发送以下HTTP请求:
GET /firebase HTTP/1.1
Host 123.456.78.90:8080
User-Agent HTTPie/2.2.0
Accept-Encoding gzip, deflate
Accept */*
Authorization Bearer <jwttoken>Connection keep-alive
Response
响应
If successful, the server will respond with:
如果成功,服务器将响应:
HTTP/1.1 200 OK
Date: Fri, 18 Sep 2020 23:14:53 GMT
connection: keep-alive
content-length: 16
content-type: text/plainExample Response
If authentication fails, the server will respond with:
如果身份验证失败,服务器将响应:
HTTP/1.1 401 Unauthorized
Connection: keep-alive
Transfer-Encoding: chunked
翻译自: https://medium.com/swlh/micronaut-security-authenticating-with-firebase-7f266acae4c3
firebase使用