Skip to content

Commit c2b1d2e

Browse files
committed
ScribeJava OAuth2
1 parent 0869578 commit c2b1d2e

9 files changed

Lines changed: 286 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
config
2+
.env
3+
target
4+
.idea
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
6+
<modelVersion>4.0.0</modelVersion>
7+
8+
<groupId>com.hellokoding.springboot</groupId>
9+
<artifactId>scribejava-oauth2</artifactId>
10+
<version>1.0-SNAPSHOT</version>
11+
12+
<parent>
13+
<groupId>org.springframework.boot</groupId>
14+
<artifactId>spring-boot-starter-parent</artifactId>
15+
<version>2.3.2.RELEASE</version>
16+
</parent>
17+
18+
<properties>
19+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
20+
<java.version>1.8</java.version>
21+
</properties>
22+
23+
<dependencies>
24+
<dependency>
25+
<groupId>org.springframework.boot</groupId>
26+
<artifactId>spring-boot-starter-web</artifactId>
27+
</dependency>
28+
<dependency>
29+
<groupId>com.github.scribejava</groupId>
30+
<artifactId>scribejava-apis</artifactId>
31+
<version>6.9.0</version>
32+
</dependency>
33+
<dependency>
34+
<groupId>org.projectlombok</groupId>
35+
<artifactId>lombok</artifactId>
36+
</dependency>
37+
</dependencies>
38+
39+
<build>
40+
<plugins>
41+
<plugin>
42+
<groupId>org.springframework.boot</groupId>
43+
<artifactId>spring-boot-maven-plugin</artifactId>
44+
</plugin>
45+
</plugins>
46+
</build>
47+
48+
</project>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.hellokoding.springboot.oauth2;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class OAuth2Application {
8+
public static void main(String[] args) {
9+
SpringApplication.run(OAuth2Application.class, args);
10+
}
11+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.hellokoding.springboot.oauth2;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.github.scribejava.core.model.OAuth2AccessToken;
5+
import com.github.scribejava.core.model.OAuthRequest;
6+
import com.github.scribejava.core.model.Verb;
7+
import com.github.scribejava.core.oauth.OAuth20Service;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.HttpStatus;
10+
import org.springframework.web.bind.annotation.GetMapping;
11+
import org.springframework.web.bind.annotation.PathVariable;
12+
import org.springframework.web.bind.annotation.RestController;
13+
import org.springframework.web.servlet.view.RedirectView;
14+
15+
import javax.servlet.http.HttpServletRequest;
16+
import javax.servlet.http.HttpServletResponse;
17+
import java.io.IOException;
18+
import java.util.Collections;
19+
import java.util.Map;
20+
import java.util.UUID;
21+
import java.util.concurrent.ExecutionException;
22+
23+
@RestController
24+
@RequiredArgsConstructor
25+
public class OAuth2Controller {
26+
private final OAuth2ServiceFactory oAuth2ServiceFactory;
27+
private final ObjectMapper objectMapper;
28+
29+
@GetMapping("/oauth2/authorization/{serviceId}")
30+
public RedirectView oauth2Login(@PathVariable String serviceId, HttpServletRequest request) {
31+
String state = UUID.randomUUID().toString();
32+
request.getSession().setAttribute("state", state);
33+
return new RedirectView(oAuth2ServiceFactory.getService(serviceId).getAuthorizationUrl(state));
34+
}
35+
36+
@GetMapping("/login/oauth2/code/{serviceId}")
37+
public RedirectView oauth2Code(@PathVariable String serviceId, String code, String state, HttpServletRequest request, HttpServletResponse response) throws InterruptedException, ExecutionException, IOException {
38+
if (!request.getSession().getAttribute("state").equals(state)) {
39+
response.setStatus(HttpStatus.UNAUTHORIZED.value());
40+
} else {
41+
OAuth20Service oAuth20Service = oAuth2ServiceFactory.getService(serviceId);
42+
OAuth2AccessToken accessToken = oAuth20Service.getAccessToken(code);
43+
44+
OAuth2ServiceFactory.OAuth2Api oAuth2Api = (OAuth2ServiceFactory.OAuth2Api)oAuth20Service.getApi();
45+
final OAuthRequest oAuthRequest = new OAuthRequest(Verb.GET, oAuth2Api.getUserInfoEndpoint());
46+
oAuth20Service.signRequest(accessToken, oAuthRequest);
47+
Map<String, String> map = objectMapper.readValue(oAuth20Service.execute(oAuthRequest).getBody(), Map.class);
48+
request.getSession().setAttribute("user", map);
49+
}
50+
51+
return new RedirectView("/");
52+
}
53+
54+
@GetMapping("/user")
55+
public Map<String, Object> user(HttpServletRequest request) {
56+
Map<String, String> map = (Map)request.getSession().getAttribute("user");
57+
return Collections.singletonMap("name", map.get("name"));
58+
}
59+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.hellokoding.springboot.oauth2;
2+
3+
import org.springframework.boot.context.properties.ConfigurationProperties;
4+
import org.springframework.stereotype.Component;
5+
6+
@Component
7+
@ConfigurationProperties(prefix = "oauth2.client")
8+
public class OAuth2Properties {
9+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.hellokoding.springboot.oauth2;
2+
3+
import com.github.scribejava.core.builder.ServiceBuilder;
4+
import com.github.scribejava.core.builder.api.DefaultApi20;
5+
import com.github.scribejava.core.oauth.OAuth20Service;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.core.env.Environment;
8+
import org.springframework.stereotype.Component;
9+
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
13+
@RequiredArgsConstructor
14+
@Component
15+
public class OAuth2ServiceFactory {
16+
private static Map<String, OAuth20Service> services = new HashMap<>();
17+
private final Environment environment;
18+
private static final String CLIENT_ID = "oauth2.client.registration.%s.clientId";
19+
private static final String CLIENT_SECRET = "oauth2.client.registration.%s.clientSecret";
20+
private static final String REDIRECT_URI = "oauth2.client.registration.%s.redirectUri";
21+
private static final String SCOPE = "oauth2.client.registration.%s.scope";
22+
private static final String AUTHORIZATION_GRANT_TYPE = "oauth2.client.registration.%s.authorizationGrantType";
23+
24+
public OAuth20Service getService(String serviceId) {
25+
if (services.containsKey(serviceId))
26+
return services.get(serviceId);
27+
28+
String clientId = environment.getProperty(String.format(CLIENT_ID, serviceId));
29+
String clientSecret = environment.getProperty(String.format(CLIENT_SECRET, serviceId));
30+
String redirectUri = environment.getProperty(String.format(REDIRECT_URI, serviceId));
31+
String scope = environment.getProperty(String.format(SCOPE, serviceId));
32+
String authorizationGrantType = environment.getProperty(String.format(AUTHORIZATION_GRANT_TYPE, serviceId));
33+
OAuth20Service oAuth20Service = new ServiceBuilder(clientId)
34+
.apiSecret(clientSecret)
35+
.callback(redirectUri)
36+
.defaultScope(scope)
37+
.responseType(authorizationGrantType)
38+
.build(new OAuth2Api(serviceId));
39+
services.put(serviceId, oAuth20Service);
40+
41+
return oAuth20Service;
42+
}
43+
44+
@RequiredArgsConstructor
45+
class OAuth2Api extends DefaultApi20 {
46+
private final String serviceId;
47+
48+
@Override
49+
public String getAccessTokenEndpoint() {
50+
return environment.getProperty(String.format("oauth2.client.provider.%s.tokenUri", serviceId));
51+
}
52+
53+
@Override
54+
protected String getAuthorizationBaseUrl() {
55+
return environment.getProperty(String.format("oauth2.client.provider.%s.authorizationUri", serviceId));
56+
}
57+
58+
public String getUserInfoEndpoint() {
59+
return environment.getProperty(String.format("oauth2.client.provider.%s.userInfoUri", serviceId));
60+
}
61+
}
62+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.hellokoding.springboot.oauth2;
2+
3+
public interface OAuth2Session {
4+
void put(String key, Object value);
5+
Object get(String key);
6+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
server:
2+
port: 8081
3+
4+
oauth2:
5+
client:
6+
registration:
7+
google:
8+
clientId: 913721443262-aefsgfa8s4f31q97kia2kftmklv0bvk3.apps.googleusercontent.com
9+
clientSecret: -r3PFhYXtAH6yppGosXyeOfl
10+
redirectUri: http://localhost:8081/login/oauth2/code/google
11+
scope: email
12+
authorizationGrantType: code
13+
14+
provider:
15+
google:
16+
authorizationUri: https://accounts.google.com/o/oauth2/v2/auth
17+
tokenUri: https://oauth2.googleapis.com/token
18+
userInfoUri: https://openidconnect.googleapis.com/v1/userinfo
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<title>Login with OAuth2 / OpenId Connect</title>
5+
<style>
6+
body {
7+
margin: 50px 50px;
8+
}
9+
10+
a {
11+
display: block;
12+
line-height: 40px;
13+
}
14+
</style>
15+
</head>
16+
<body>
17+
<h1>Login with OAuth2 / OpenId Connect</h1>
18+
<div class="container">
19+
<div id="login" style="display:block">
20+
<a href="/oauth2/authorization/github">Login with Github</a>
21+
<a href="/oauth2/authorization/google">Login with Google</a>
22+
<a href="/oauth2/authorization/facebook">Login with Facebook</a>
23+
<a href="/oauth2/authorization/okta">Login with Okta</a>
24+
<a href="/oauth2/authorization/linkedin">Login with LinkedIn</a>
25+
</div>
26+
<div id="welcome" style="display:none">
27+
Welcome <span id="name"></span> | <button onClick="logout()">Logout</button>
28+
</div>
29+
</div>
30+
<script>
31+
fetch('/user').then((response) => {
32+
if (response.ok) {
33+
return response.json();
34+
} else {
35+
throw new Error('Something went wrong');
36+
}
37+
})
38+
.then((responseJson) => {
39+
document.getElementById('name').innerText = data.name;
40+
document.getElementById('login').style.display = 'none';
41+
document.getElementById('welcome').style.display = 'block';
42+
})
43+
.catch((error) => {
44+
console.error('Error: ', error);
45+
});
46+
47+
function logout() {
48+
fetch('/logout', {
49+
method: 'POST',
50+
headers: {
51+
'X-XSRF-TOKEN': getCookie('XSRF-TOKEN')
52+
}
53+
})
54+
.then(() => {
55+
document.getElementById('login').style.display = 'block';
56+
document.getElementById('welcome').style.display = 'none';
57+
})
58+
.catch((error) => {
59+
console.error('Error: ', error);
60+
});
61+
}
62+
63+
function getCookie(name) {
64+
var v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
65+
return v ? v[2] : null;
66+
}
67+
</script>
68+
</body>
69+
</html>

0 commit comments

Comments
 (0)