Skip to content

Commit 4d4636d

Browse files
authored
web app sample (#84)
* web app sample
1 parent b9cd0f6 commit 4d4636d

File tree

31 files changed

+737
-1636
lines changed

31 files changed

+737
-1636
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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 http://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.1.4.RELEASE</version>
9+
<relativePath/> <!-- lookup parent from repository -->
10+
</parent>
11+
<groupId>com.microsoft.azure</groupId>
12+
<artifactId>msal-web-sample</artifactId>
13+
<packaging>war</packaging>
14+
<version>0.1.0</version>
15+
<name>msal-web-sample</name>
16+
<description>Web sample for Microsoft Authentication Library for Java</description>
17+
18+
<properties>
19+
<java.version>1.8</java.version>
20+
</properties>
21+
22+
<dependencies>
23+
<dependency>
24+
<groupId>com.microsoft.azure</groupId>
25+
<artifactId>msal4j</artifactId>
26+
<version>0.5.0-preview</version>
27+
</dependency>
28+
<dependency>
29+
<groupId>com.nimbusds</groupId>
30+
<artifactId>oauth2-oidc-sdk</artifactId>
31+
<version>6.5</version>
32+
</dependency>
33+
<dependency>
34+
<groupId>org.json</groupId>
35+
<artifactId>json</artifactId>
36+
<version>20090211</version>
37+
</dependency>
38+
<!-- Spring 3 dependencies -->
39+
<dependency>
40+
<groupId>org.springframework.boot</groupId>
41+
<artifactId>spring-boot-starter-thymeleaf</artifactId>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.springframework.boot</groupId>
45+
<artifactId>spring-boot-starter-web</artifactId>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.projectlombok</groupId>
49+
<artifactId>lombok</artifactId>
50+
<optional>true</optional>
51+
</dependency>
52+
<dependency>
53+
<groupId>org.springframework.boot</groupId>
54+
<artifactId>spring-boot-starter-test</artifactId>
55+
<scope>test</scope>
56+
</dependency>
57+
</dependencies>
58+
59+
<build>
60+
<plugins>
61+
<plugin>
62+
<groupId>org.springframework.boot</groupId>
63+
<artifactId>spring-boot-maven-plugin</artifactId>
64+
</plugin>
65+
</plugins>
66+
</build>
67+
68+
</project>
Lines changed: 74 additions & 163 deletions
Large diffs are not rendered by default.
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.azure.msalwebsample;
5+
6+
import java.net.MalformedURLException;
7+
import java.net.URI;
8+
import java.util.Collections;
9+
import java.util.Map;
10+
import java.util.concurrent.CompletableFuture;
11+
import java.util.concurrent.ExecutionException;
12+
import java.util.concurrent.Future;
13+
import javax.annotation.PostConstruct;
14+
import javax.naming.ServiceUnavailableException;
15+
import javax.servlet.http.HttpServletRequest;
16+
17+
import com.microsoft.aad.msal4j.*;
18+
import com.nimbusds.oauth2.sdk.AuthorizationCode;
19+
import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
20+
import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
21+
import lombok.Getter;
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.stereotype.Component;
24+
25+
@Component
26+
@Getter
27+
class AuthHelper {
28+
29+
static final String PRINCIPAL_SESSION_NAME = "principal";
30+
static final String TOKEN_CACHE_SESSION_ATTRIBUTE = "token_cache";
31+
static final String END_SESSION_ENDPOINT = "https://login.microsoftonline.com/common/oauth2/v2.0/logout";
32+
33+
private String clientId;
34+
private String clientSecret;
35+
private String authority;
36+
private String redirectUri;
37+
38+
@Autowired
39+
BasicConfiguration configuration;
40+
41+
@PostConstruct
42+
public void init() {
43+
clientId = configuration.getClientId();
44+
authority = configuration.getAuthority();
45+
clientSecret = configuration.getSecretKey();
46+
redirectUri = configuration.getRedirectUri();
47+
}
48+
49+
private ConfidentialClientApplication createClientApplication() throws MalformedURLException {
50+
return ConfidentialClientApplication.builder(clientId, ClientCredentialFactory.create(clientSecret)).
51+
authority(authority).
52+
build();
53+
}
54+
55+
IAuthenticationResult getAuthResultBySilentFlow(HttpServletRequest httpRequest) throws Throwable {
56+
IAuthenticationResult result = AuthHelper.getAuthSessionObject(httpRequest);
57+
58+
IAuthenticationResult updatedResult;
59+
ConfidentialClientApplication app;
60+
try {
61+
app = createClientApplication();
62+
63+
Object tokenCache = httpRequest.getSession().getAttribute("token_cache");
64+
if(tokenCache != null){
65+
app.tokenCache().deserialize(tokenCache.toString());
66+
}
67+
68+
SilentParameters parameters = SilentParameters.builder(
69+
Collections.singleton("https://graph.microsoft.com/.default"),
70+
result.account()).build();
71+
72+
CompletableFuture<IAuthenticationResult> future = app.acquireTokenSilently(parameters);
73+
74+
updatedResult = future.get();
75+
} catch (ExecutionException e) {
76+
throw e.getCause();
77+
}
78+
79+
if (updatedResult == null) {
80+
throw new ServiceUnavailableException("authentication result was null");
81+
}
82+
83+
//update session with latest token cache
84+
storeTokenCacheInSession(httpRequest, app.tokenCache().serialize());
85+
86+
return updatedResult;
87+
}
88+
89+
IAuthenticationResult getAuthResultByAuthCode(
90+
HttpServletRequest httpServletRequest,
91+
AuthorizationCode authorizationCode,
92+
String currentUri) throws Throwable {
93+
94+
IAuthenticationResult result;
95+
ConfidentialClientApplication app;
96+
try {
97+
app = createClientApplication();
98+
99+
String authCode = authorizationCode.getValue();
100+
AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder(
101+
authCode,
102+
new URI(currentUri)).
103+
build();
104+
105+
Future<IAuthenticationResult> future = app.acquireToken(parameters);
106+
107+
result = future.get();
108+
} catch (ExecutionException e) {
109+
throw e.getCause();
110+
}
111+
112+
if (result == null) {
113+
throw new ServiceUnavailableException("authentication result was null");
114+
}
115+
116+
storeTokenCacheInSession(httpServletRequest, app.tokenCache().serialize());
117+
118+
return result;
119+
}
120+
121+
private void storeTokenCacheInSession(HttpServletRequest httpServletRequest, String tokenCache){
122+
httpServletRequest.getSession().setAttribute(AuthHelper.TOKEN_CACHE_SESSION_ATTRIBUTE, tokenCache);
123+
}
124+
125+
void setSessionPrincipal(HttpServletRequest httpRequest, IAuthenticationResult result) {
126+
httpRequest.getSession().setAttribute(AuthHelper.PRINCIPAL_SESSION_NAME, result);
127+
}
128+
129+
void removePrincipalFromSession(HttpServletRequest httpRequest) {
130+
httpRequest.getSession().removeAttribute(AuthHelper.PRINCIPAL_SESSION_NAME);
131+
}
132+
133+
void updateAuthDataUsingSilentFlow(HttpServletRequest httpRequest) throws Throwable {
134+
IAuthenticationResult authResult = getAuthResultBySilentFlow(httpRequest);
135+
setSessionPrincipal(httpRequest, authResult);
136+
}
137+
138+
static boolean isAuthenticationSuccessful(AuthenticationResponse authResponse) {
139+
return authResponse instanceof AuthenticationSuccessResponse;
140+
}
141+
142+
static boolean isAuthenticated(HttpServletRequest request) {
143+
return request.getSession().getAttribute(PRINCIPAL_SESSION_NAME) != null;
144+
}
145+
146+
static IAuthenticationResult getAuthSessionObject(HttpServletRequest request) {
147+
Object principalSession = request.getSession().getAttribute(PRINCIPAL_SESSION_NAME);
148+
if(principalSession instanceof IAuthenticationResult){
149+
return (IAuthenticationResult) principalSession;
150+
} else {
151+
throw new IllegalStateException();
152+
}
153+
}
154+
155+
static boolean containsAuthenticationCode(HttpServletRequest httpRequest) {
156+
Map<String, String[]> httpParameters = httpRequest.getParameterMap();
157+
158+
boolean isPostRequest = httpRequest.getMethod().equalsIgnoreCase("POST");
159+
boolean containsErrorData = httpParameters.containsKey("error");
160+
boolean containIdToken = httpParameters.containsKey("id_token");
161+
boolean containsCode = httpParameters.containsKey("code");
162+
163+
return isPostRequest && containsErrorData || containsCode || containIdToken;
164+
}
165+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.azure.msalwebsample;
5+
6+
import java.io.IOException;
7+
import java.net.HttpURLConnection;
8+
import java.net.URL;
9+
import java.net.URLEncoder;
10+
import java.text.ParseException;
11+
12+
import javax.servlet.http.HttpServletRequest;
13+
import javax.servlet.http.HttpServletResponse;
14+
15+
import com.microsoft.aad.msal4j.*;
16+
import com.nimbusds.jwt.JWTParser;
17+
import org.json.JSONArray;
18+
import org.json.JSONObject;
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
21+
import org.springframework.stereotype.Controller;
22+
import org.springframework.ui.ModelMap;
23+
import org.springframework.web.bind.annotation.RequestMapping;
24+
import org.springframework.web.servlet.ModelAndView;
25+
26+
import static com.microsoft.azure.msalwebsample.AuthHelper.getAuthSessionObject;
27+
28+
@Controller
29+
public class AuthPageController {
30+
31+
@Autowired
32+
AuthHelper authHelper;
33+
34+
@RequestMapping("/msal4jsample")
35+
public String homepage(){
36+
return "index";
37+
}
38+
39+
@RequestMapping("/msal4jsample/secure/aad")
40+
public ModelAndView securePage(HttpServletRequest httpRequest) throws ParseException {
41+
ModelAndView mav = new ModelAndView("auth_page");
42+
43+
setAccountInfo(mav, httpRequest);
44+
45+
return mav;
46+
}
47+
48+
@RequestMapping("/msal4jsample/sign_out")
49+
public void signOut(HttpServletRequest httpRequest, HttpServletResponse response) throws ParseException, IOException {
50+
51+
httpRequest.getSession().invalidate();
52+
53+
String redirectUrl = "http://localhost:8080/msal4jsample/";
54+
response.sendRedirect(AuthHelper.END_SESSION_ENDPOINT +
55+
"?post_logout_redirect_uri=" + URLEncoder.encode(redirectUrl, "UTF-8"));
56+
}
57+
58+
@RequestMapping("/graph/users")
59+
public ModelAndView getUsersFromGraph(ModelMap model, HttpServletRequest httpRequest) throws Throwable {
60+
IAuthenticationResult result = authHelper.getAuthResultBySilentFlow(httpRequest);
61+
62+
ModelAndView mav;
63+
64+
if (result == null) {
65+
mav = new ModelAndView("error");
66+
mav.addObject("error", new Exception("AuthenticationResult not found in session."));
67+
} else {
68+
mav = new ModelAndView("auth_page");
69+
setAccountInfo(mav, httpRequest);
70+
71+
try {
72+
mav.addObject("users", getUserNamesFromGraph(result.accessToken()));
73+
74+
return mav;
75+
} catch (Exception e) {
76+
mav = new ModelAndView("error");
77+
mav.addObject("error", e);
78+
}
79+
}
80+
return mav;
81+
}
82+
83+
private String getUserNamesFromGraph(String accessToken) throws Exception {
84+
// Microsoft Graph users endpoint
85+
URL url = new URL("https://graph.microsoft.com/v1.0/users");
86+
87+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
88+
89+
// Set the appropriate header fields in the request header.
90+
conn.setRequestProperty("Authorization", "Bearer " + accessToken);
91+
conn.setRequestProperty("Accept", "application/json");
92+
93+
String response = HttpClientHelper.getResponseStringFromConn(conn);
94+
95+
int responseCode = conn.getResponseCode();
96+
if(responseCode != HttpURLConnection.HTTP_OK) {
97+
throw new IOException(response);
98+
}
99+
100+
JSONObject responseObject = HttpClientHelper.processResponse(responseCode, response);
101+
JSONArray users = JSONHelper.fetchDirectoryObjectJSONArray(responseObject);
102+
103+
StringBuilder builder = new StringBuilder();
104+
for (int i = 0; i < users.length(); i++) {
105+
JSONObject thisUserJSONObject = users.optJSONObject(i);
106+
User user = new User();
107+
JSONHelper.convertJSONObjectToDirectoryObject(thisUserJSONObject, user);
108+
builder.append(user.getUserPrincipalName());
109+
builder.append("<br/>");
110+
}
111+
return builder.toString();
112+
}
113+
114+
private void setAccountInfo(ModelAndView model, HttpServletRequest httpRequest) throws ParseException {
115+
IAuthenticationResult auth = getAuthSessionObject(httpRequest);
116+
117+
String tenantId = JWTParser.parse(auth.idToken()).getJWTClaimsSet().getStringClaim("tid");
118+
119+
model.addObject("tenantId", tenantId);
120+
model.addObject("account", getAuthSessionObject(httpRequest).account());
121+
}
122+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.azure.msalwebsample;
5+
6+
import lombok.AccessLevel;
7+
import lombok.Getter;
8+
import lombok.Setter;
9+
import org.springframework.boot.context.properties.ConfigurationProperties;
10+
import org.springframework.stereotype.Component;
11+
12+
@Getter
13+
@Setter
14+
@Component
15+
@ConfigurationProperties("aad")
16+
class BasicConfiguration {
17+
String clientId;
18+
@Getter(AccessLevel.NONE) String authority;
19+
String redirectUri;
20+
String secretKey;
21+
22+
String getAuthority(){
23+
if (!authority.endsWith("/")) {
24+
authority += "/";
25+
}
26+
return authority;
27+
}
28+
}

0 commit comments

Comments
 (0)