Skip to content

Commit 1005568

Browse files
Tariq HookTariq Hook
Tariq Hook
authored and
Tariq Hook
committed
first commit
0 parents  commit 1005568

30 files changed

+1330
-0
lines changed

.gitignore

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
HELP.md
2+
target/
3+
!.mvn/wrapper/maven-wrapper.jar
4+
!**/src/main/**/target/
5+
!**/src/test/**/target/
6+
7+
### STS ###
8+
.apt_generated
9+
.classpath
10+
.factorypath
11+
.project
12+
.settings
13+
.springBeans
14+
.sts4-cache
15+
16+
### IntelliJ IDEA ###
17+
.idea
18+
*.iws
19+
*.iml
20+
*.ipr
21+
22+
### NetBeans ###
23+
/nbproject/private/
24+
/nbbuild/
25+
/dist/
26+
/nbdist/
27+
/.nb-gradle/
28+
build/
29+
!**/src/main/**/build/
30+
!**/src/test/**/build/
31+
32+
### VS Code ###
33+
.vscode/
34+
**/firebase_config.json
35+
.DS_Store
36+
mvnw**
37+
.mvn**
+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
# Firebase And Spring Boot
2+
3+
This repository is an example of how to use Firebase with Springboot to provide authentication services.
4+
5+
## Configuring Firebase
6+
7+
Before we start enable the SignIn Methods. I enable Email/Password and Google and create a user. You can simply add a user using email/password.
8+
9+
![](./imgs/img01.png)
10+
![](./imgs/img02.png)
11+
12+
### Configuring Firebase in the SpringBoot Project:
13+
14+
To configure the firebase in the spring boot project first download the google-services.json file from your Firebase Project setting.
15+
16+
Go to Project Setting -> Service Account-> Scroll down and click on create and then click on Generate new Private Key
17+
18+
This will generate and download Private Key to access the Firebase Admin SDK
19+
20+
The code snippets are also provided for different language
21+
22+
![](./imgs/img03.png)
23+
24+
Now rename the private key to firebase_config.json and paste in the resource folder of your spring boot project. This is the location where we access our private key. You can also store on other location or environment variable. For this tutorial, we will store in the resource folder
25+
26+
![](./imgs/img04.png)
27+
28+
## Configuring POM.xml
29+
30+
These files are already in place but lets point them out
31+
32+
```
33+
<dependency>
34+
<groupId>com.google.firebase</groupId>
35+
<artifactId>firebase-admin</artifactId>
36+
<version>8.1.0</version>
37+
</dependency>
38+
<dependency>
39+
<groupId>org.springframework.boot</groupId>
40+
<artifactId>spring-boot-starter-security</artifactId>
41+
</dependency>
42+
```
43+
44+
## Configuring SpringBoot Project to add Security
45+
46+
We created a filter to check every request for the JWT bearer token in the request.
47+
48+
For this, we are made a **OncePerRequestFilter**. **Filter** base class that aims to guarantee a **single** execution **per request** dispatch.
49+
50+
51+
```
52+
@Component
53+
public class SecurityFilter extends OncePerRequestFilter {
54+
...
55+
56+
@Override
57+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
58+
throws ServletException, IOException {
59+
verifyToken(request);
60+
filterChain.doFilter(request, response);
61+
}
62+
63+
private void verifyToken(HttpServletRequest request) {
64+
String session = null;
65+
FirebaseToken decodedToken = null;
66+
CredentialType type = null;
67+
boolean strictServerSessionEnabled = securityProps.getFirebaseProps().isEnableStrictServerSession();
68+
Cookie sessionCookie = cookieUtils.getCookie("session");
69+
String token = securityService.getBearerToken(request);
70+
try {
71+
if (sessionCookie != null) {
72+
session = sessionCookie.getValue();
73+
decodedToken = FirebaseAuth.getInstance().verifySessionCookie(session,
74+
securityProps.getFirebaseProps().isEnableCheckSessionRevoked());
75+
type = CredentialType.SESSION;
76+
} else if (!strictServerSessionEnabled) {
77+
if (token != null && !token.equalsIgnoreCase("undefined")) {
78+
decodedToken = FirebaseAuth.getInstance().verifyIdToken(token);
79+
type = CredentialType.ID_TOKEN;
80+
}
81+
}
82+
} catch (FirebaseAuthException e) {
83+
e.printStackTrace();
84+
logger.error("Firebase Exception:: ", e.getLocalizedMessage());
85+
}
86+
FireBaseUser user = firebaseTokenToUserDto(decodedToken);
87+
if (user != null) {
88+
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user,
89+
new Credentials(type, decodedToken, token, session), null);
90+
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
91+
SecurityContextHolder.getContext().setAuthentication(authentication);
92+
}
93+
}
94+
95+
...
96+
97+
98+
}
99+
```
100+
101+
In this filter, we are first checking is session-based authentication if the session cookie is available then we are verifying the session cookie and fetching the details, and set to SecurityContextHolder. If the session cookie is not available then we are using Bearer token.
102+
103+
104+
```
105+
@Service
106+
public class SecurityService {
107+
108+
...
109+
110+
public User getUser() {
111+
User userPrincipal = null;
112+
SecurityContext securityContext = SecurityContextHolder.getContext();
113+
Object principal = securityContext.getAuthentication().getPrincipal();
114+
if (principal instanceof User) {
115+
userPrincipal = ((User) principal);
116+
}
117+
return userPrincipal;
118+
}
119+
120+
public Credentials getCredentials() {
121+
SecurityContext securityContext = SecurityContextHolder.getContext();
122+
return (Credentials) securityContext.getAuthentication().getCredentials();
123+
}
124+
125+
public boolean isPublic() {
126+
return securityProps.getAllowedPublicApis().contains(httpServletRequest.getRequestURI());
127+
}
128+
129+
public String getBearerToken(HttpServletRequest request) {
130+
String bearerToken = null;
131+
String authorization = request.getHeader("Authorization");
132+
if (StringUtils.hasText(authorization) && authorization.startsWith("Bearer ")) {
133+
bearerToken = authorization.substring(7);
134+
}
135+
return bearerToken;
136+
}
137+
}
138+
```
139+
140+
SecurityService to decode Bearer token from a request and fetching Principle from SecurityContextHolder.
141+
142+
```
143+
@Configuration
144+
public class SecurityConfig{
145+
146+
private ObjectMapper objectMapper;
147+
private SecurityProperties restSecProps;
148+
public SecurityFilter tokenAuthenticationFilter;
149+
150+
@Autowired
151+
public SecurityConfig(ObjectMapper objectMapper, SecurityProperties restSecProps, SecurityFilter tokenAuthenticationFilter){
152+
this.objectMapper = objectMapper;
153+
this.restSecProps = restSecProps;
154+
this.tokenAuthenticationFilter = tokenAuthenticationFilter;
155+
}
156+
157+
158+
159+
@Bean
160+
public AuthenticationEntryPoint restAuthenticationEntryPoint() {
161+
return (httpServletRequest, httpServletResponse, e) -> {
162+
Map<String, Object> errorObject = new HashMap<>();
163+
int errorCode = 401;
164+
errorObject.put("message", "Unauthorized access of protected resource, invalid credentials");
165+
errorObject.put("error", HttpStatus.UNAUTHORIZED);
166+
errorObject.put("code", errorCode);
167+
errorObject.put("timestamp", new Timestamp(new Date().getTime()));
168+
httpServletResponse.setContentType("application/json;charset=UTF-8");
169+
httpServletResponse.setStatus(errorCode);
170+
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(errorObject));
171+
};
172+
}
173+
174+
@Bean
175+
public CorsConfigurationSource corsConfigurationSource() {
176+
CorsConfiguration configuration = new CorsConfiguration();
177+
configuration.setAllowedOrigins(restSecProps.getAllowedOrigins());
178+
configuration.setAllowedMethods(restSecProps.getAllowedMethods());
179+
configuration.setAllowedHeaders(restSecProps.getAllowedHeaders());
180+
configuration.setAllowCredentials(restSecProps.isAllowCredentials());
181+
configuration.setExposedHeaders(restSecProps.getExposedHeaders());
182+
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
183+
source.registerCorsConfiguration("/**", configuration);
184+
return source;
185+
}
186+
187+
@Bean
188+
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
189+
http.cors().configurationSource(corsConfigurationSource()).and().csrf().disable().formLogin().disable()
190+
.httpBasic().disable().exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint())
191+
.and().authorizeRequests()
192+
.antMatchers(restSecProps.getAllowedPublicApis().toArray(String[]::new)).permitAll()
193+
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll().anyRequest().authenticated().and()
194+
.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
195+
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
196+
return http.build();
197+
}
198+
199+
@Bean
200+
public WebSecurityCustomizer webSecurityCustomizer() {
201+
return (web) -> web.ignoring().antMatchers("/images/**", "/js/**", "/webjars/**");
202+
}
203+
}
204+
205+
```
206+
207+
Spring Security WebSecurityConfigurerAdapter to configure Cors, SecurityFilter, and AuthenticationEntryPoint for changing the default HTML Unauthorised page to API based 401 response.
208+
209+
```
210+
security:
211+
...
212+
allowed-origins:
213+
- https://${DOMAIN}
214+
- http://localhost:3000
215+
allowed-methods:
216+
- GET
217+
- POST
218+
- PUT
219+
- PATCH
220+
- DELETE
221+
- OPTIONS
222+
allowed-headers:
223+
- Authorization
224+
- Origin
225+
- Content-Type
226+
- Accept
227+
- Accept-Encoding
228+
- Accept-Language
229+
- Access-Control-Allow-Origin
230+
- Access-Control-Allow-Headers
231+
- Access-Control-Request-Method
232+
- X-Requested-With
233+
- X-Auth-Token
234+
- X-Xsrf-Token
235+
- Cache-Control
236+
- Id-Token
237+
allowed-public-apis:
238+
- /favicon.ico
239+
- /session/login
240+
- /public/**
241+
exposed-headers:
242+
- X-Xsrf-Token
243+
```
244+
245+
**application.yml** file to change the properties of headers, public domains, etc
246+
247+
To get a token use this url:
248+
249+
```
250+
https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key={webapikey}
251+
```
252+
253+
with body:
254+
255+
```
256+
{
257+
"email":"[email protected]",
258+
"password":"12345678",
259+
"returnSecureToken":true
260+
}
261+
```
25.8 KB
Loading
26.5 KB
Loading
86.9 KB
Loading
10.9 KB
Loading

FireBaseApiAuthenticationDemo/pom.xml

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.boot</groupId>
7+
<artifactId>spring-boot-starter-parent</artifactId>
8+
<version>2.7.2</version>
9+
<relativePath/> <!-- lookup parent from repository -->
10+
</parent>
11+
<groupId>com.codedifferently</groupId>
12+
<artifactId>FireBaseApiAuthenticationDemo</artifactId>
13+
<version>0.0.1-SNAPSHOT</version>
14+
<name>FireBaseApiAuthenticationDemo</name>
15+
<description>FireBaseApiAuthenticationDemo</description>
16+
<properties>
17+
<java.version>11</java.version>
18+
</properties>
19+
<dependencies>
20+
<dependency>
21+
<groupId>org.springframework.boot</groupId>
22+
<artifactId>spring-boot-starter-data-jpa</artifactId>
23+
</dependency>
24+
25+
<dependency>
26+
<groupId>org.springframework.boot</groupId>
27+
<artifactId>spring-boot-starter-security</artifactId>
28+
</dependency>
29+
<!-- https://mvnrepository.com/artifact/com.google.firebase/firebase-admin -->
30+
<dependency>
31+
<groupId>com.google.firebase</groupId>
32+
<artifactId>firebase-admin</artifactId>
33+
<version>8.1.0</version>
34+
</dependency>
35+
36+
<dependency>
37+
<groupId>org.springframework.boot</groupId>
38+
<artifactId>spring-boot-starter-web</artifactId>
39+
</dependency>
40+
41+
<dependency>
42+
<groupId>mysql</groupId>
43+
<artifactId>mysql-connector-java</artifactId>
44+
<scope>runtime</scope>
45+
</dependency>
46+
<dependency>
47+
<groupId>org.projectlombok</groupId>
48+
<artifactId>lombok</artifactId>
49+
<optional>true</optional>
50+
</dependency>
51+
52+
53+
54+
<dependency>
55+
<groupId>org.springframework.boot</groupId>
56+
<artifactId>spring-boot-starter-test</artifactId>
57+
<scope>test</scope>
58+
</dependency>
59+
<dependency>
60+
<groupId>org.springframework.security</groupId>
61+
<artifactId>spring-security-test</artifactId>
62+
<scope>test</scope>
63+
</dependency>
64+
</dependencies>
65+
66+
<build>
67+
<plugins>
68+
<plugin>
69+
<groupId>org.springframework.boot</groupId>
70+
<artifactId>spring-boot-maven-plugin</artifactId>
71+
<configuration>
72+
<excludes>
73+
<exclude>
74+
<groupId>org.projectlombok</groupId>
75+
<artifactId>lombok</artifactId>
76+
</exclude>
77+
</excludes>
78+
</configuration>
79+
</plugin>
80+
</plugins>
81+
</build>
82+
83+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.codedifferently.firebaseapiauthenticationdemo;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class FireBaseApiAuthenticationDemoApplication {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(FireBaseApiAuthenticationDemoApplication.class, args);
11+
}
12+
13+
}

0 commit comments

Comments
 (0)