KEYCLOAK-904 Offline portal example added
This commit is contained in:
parent
df46a8ea0d
commit
018866aa81
16 changed files with 566 additions and 8 deletions
|
@ -3,6 +3,7 @@ package org.keycloak.util;
|
|||
import java.io.IOException;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
|
||||
/**
|
||||
|
@ -30,7 +31,7 @@ public class RefreshTokenUtil {
|
|||
|
||||
|
||||
/**
|
||||
* Return refresh token or offline otkne
|
||||
* Return refresh token or offline token
|
||||
*
|
||||
* @param decodedToken
|
||||
* @return
|
||||
|
@ -39,9 +40,9 @@ public class RefreshTokenUtil {
|
|||
return JsonSerialization.readValue(decodedToken, RefreshToken.class);
|
||||
}
|
||||
|
||||
private static RefreshToken getRefreshToken(String refreshToken) throws IOException {
|
||||
byte[] decodedToken = Base64Url.decode(refreshToken);
|
||||
return getRefreshToken(decodedToken);
|
||||
public static RefreshToken getRefreshToken(String refreshToken) throws IOException {
|
||||
byte[] encodedContent = new JWSInput(refreshToken).getContent();
|
||||
return getRefreshToken(encodedContent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -216,7 +216,19 @@ An example for retrieve service account dedicated to the Client Application itse
|
|||
|
||||
[http://localhost:8080/service-account-portal](http://localhost:8080/service-account-portal)
|
||||
|
||||
Client authentication is done with OAuth2 Client Credentials Grant in out-of-bound request (Not Keycloak login screen displayed)
|
||||
Client authentication is done with OAuth2 Client Credentials Grant in out-of-bound request (Not Keycloak login screen displayed) .
|
||||
|
||||
The example also shows different methods of client authentication. There is ProductSAClientSecretServlet using traditional authentication with clientId and client_secret,
|
||||
but there is also ProductSAClientSignedJWTServlet using client authentication with JWT signed by client private key.
|
||||
|
||||
Step 11: Offline Access Example
|
||||
===============================
|
||||
An example for retrieve offline token, which is then saved to the database and can be used by application anytime later. Offline token
|
||||
is valid even if user is already logged out from SSO. Server restart also won't invalidate offline token. Offline token can be revoked by the user in
|
||||
account management or by admin in admin console.
|
||||
|
||||
[http://localhost:8080/offline-access-portal](http://localhost:8080/offline-access-portal)
|
||||
|
||||
|
||||
Admin Console
|
||||
==========================
|
||||
|
|
0
examples/demo-template/customer-app/src/main/webapp/index.html
Executable file → Normal file
0
examples/demo-template/customer-app/src/main/webapp/index.html
Executable file → Normal file
73
examples/demo-template/offline-access-app/pom.xml
Normal file
73
examples/demo-template/offline-access-app/pom.xml
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-examples-demo-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.0.Final-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.keycloak.example.demo</groupId>
|
||||
<artifactId>offline-access-example</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<name>Offline Access Portal</name>
|
||||
<description/>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jboss</id>
|
||||
<name>jboss repo</name>
|
||||
<url>http://repository.jboss.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-spi</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>offline-access-portal</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jboss.as.plugins</groupId>
|
||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.wildfly.plugins</groupId>
|
||||
<artifactId>wildfly-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,226 @@
|
|||
package org.keycloak.example;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.security.cert.X509Certificate;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.ServerRequest;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
import org.keycloak.util.RefreshTokenUtil;
|
||||
import org.keycloak.util.StreamUtil;
|
||||
import org.keycloak.util.Time;
|
||||
import org.keycloak.util.UriUtils;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class OfflineAccessPortalServlet extends HttpServlet {
|
||||
|
||||
|
||||
@Override
|
||||
public void init() throws ServletException {
|
||||
getServletContext().setAttribute(HttpClient.class.getName(), new DefaultHttpClient());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
getHttpClient().getConnectionManager().shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
|
||||
if (req.getRequestURI().endsWith("/login")) {
|
||||
storeToken(req);
|
||||
req.getRequestDispatcher("/WEB-INF/pages/loginCallback.jsp").forward(req, resp);
|
||||
return;
|
||||
}
|
||||
|
||||
String refreshToken = RefreshTokenDAO.loadToken();
|
||||
String refreshTokenInfo;
|
||||
boolean savedTokenAvailable;
|
||||
if (refreshToken == null) {
|
||||
refreshTokenInfo = "No token saved in database. Please login first";
|
||||
savedTokenAvailable = false;
|
||||
} else {
|
||||
RefreshToken refreshTokenDecoded = RefreshTokenUtil.getRefreshToken(refreshToken);
|
||||
String exp = (refreshTokenDecoded.getExpiration() == 0) ? "NEVER" : Time.toDate(refreshTokenDecoded.getExpiration()).toString();
|
||||
refreshTokenInfo = String.format("<p>Type: %s</p><p>ID: %s</p><p>Expires: %s</p>", refreshTokenDecoded.getType(), refreshTokenDecoded.getId(), exp);
|
||||
savedTokenAvailable = true;
|
||||
}
|
||||
req.setAttribute("tokenInfo", refreshTokenInfo);
|
||||
req.setAttribute("savedTokenAvailable", savedTokenAvailable);
|
||||
|
||||
String customers;
|
||||
if (req.getRequestURI().endsWith("/loadCustomers")) {
|
||||
customers = loadCustomers(req, refreshToken);
|
||||
} else {
|
||||
customers = "";
|
||||
}
|
||||
req.setAttribute("customers", customers);
|
||||
|
||||
req.getRequestDispatcher("/WEB-INF/pages/view.jsp").forward(req, resp);
|
||||
}
|
||||
|
||||
private void storeToken(HttpServletRequest req) throws IOException {
|
||||
RefreshableKeycloakSecurityContext ctx = (RefreshableKeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
|
||||
String refreshToken = ctx.getRefreshToken();
|
||||
|
||||
RefreshTokenDAO.saveToken(refreshToken);
|
||||
|
||||
RefreshToken refreshTokenDecoded = RefreshTokenUtil.getRefreshToken(refreshToken);
|
||||
Boolean isOfflineToken = refreshTokenDecoded.getType().equals(RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
|
||||
req.setAttribute("isOfflineToken", isOfflineToken);
|
||||
}
|
||||
|
||||
private String loadCustomers(HttpServletRequest req, String refreshToken) throws ServletException, IOException {
|
||||
// Retrieve accessToken first with usage of refresh (offline) token from DB
|
||||
String accessToken = null;
|
||||
try {
|
||||
KeycloakDeployment deployment = getDeployment(req);
|
||||
AccessTokenResponse response = ServerRequest.invokeRefresh(deployment, refreshToken);
|
||||
accessToken = response.getToken();
|
||||
} catch (ServerRequest.HttpFailure failure) {
|
||||
return "Failed to refresh token. Status from auth-server request: " + failure.getStatus() + ", Error: " + failure.getError();
|
||||
}
|
||||
|
||||
// Load customers now
|
||||
HttpGet get = new HttpGet(UriUtils.getOrigin(req.getRequestURL().toString()) + "/database/customers");
|
||||
get.addHeader("Authorization", "Bearer " + accessToken);
|
||||
|
||||
HttpResponse response = getHttpClient().execute(get);
|
||||
InputStream is = response.getEntity().getContent();
|
||||
try {
|
||||
if (response.getStatusLine().getStatusCode() != 200) {
|
||||
return "Error when loading customer. Status: " + response.getStatusLine().getStatusCode() + ", error: " + StreamUtil.readString(is);
|
||||
} else {
|
||||
List<String> list = JsonSerialization.readValue(is, TypedList.class);
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (String customer : list) {
|
||||
result.append(customer + "<br />");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private KeycloakDeployment getDeployment(HttpServletRequest servletRequest) throws ServletException {
|
||||
// The facade object is needed just if you have relative "auth-server-url" in keycloak.json. Otherwise you can call deploymentContext.resolveDeployment(null)
|
||||
HttpFacade facade = getFacade(servletRequest);
|
||||
|
||||
AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) getServletContext().getAttribute(AdapterDeploymentContext.class.getName());
|
||||
if (deploymentContext == null) {
|
||||
throw new ServletException("AdapterDeploymentContext not set");
|
||||
}
|
||||
return deploymentContext.resolveDeployment(facade);
|
||||
}
|
||||
|
||||
// TODO: Merge with facade in ServletOAuthClient and move to some common servlet adapter
|
||||
private HttpFacade getFacade(final HttpServletRequest servletRequest) {
|
||||
return new HttpFacade() {
|
||||
|
||||
@Override
|
||||
public Request getRequest() {
|
||||
return new Request() {
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return servletRequest.getMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURI() {
|
||||
return servletRequest.getRequestURL().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return servletRequest.isSecure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryParamValue(String param) {
|
||||
return servletRequest.getParameter(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstParam(String param) {
|
||||
return servletRequest.getParameter(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cookie getCookie(String cookieName) {
|
||||
// not needed
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
return servletRequest.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name) {
|
||||
// not needed
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
try {
|
||||
return servletRequest.getInputStream();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
return servletRequest.getRemoteAddr();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response getResponse() {
|
||||
throw new IllegalStateException("Not yet implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain() {
|
||||
throw new IllegalStateException("Not yet implemented");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private HttpClient getHttpClient() {
|
||||
return (HttpClient) getServletContext().getAttribute(HttpClient.class.getName());
|
||||
}
|
||||
|
||||
static class TypedList extends ArrayList<String> {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.keycloak.example;
|
||||
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class OfflineExampleUris {
|
||||
|
||||
|
||||
public static final String LOGIN_CLASSIC = "/offline-access-portal/app/login";
|
||||
|
||||
|
||||
public static final String LOGIN_WITH_OFFLINE_TOKEN = "/offline-access-portal/app/login?scope=offline_access";
|
||||
|
||||
|
||||
public static final String LOAD_CUSTOMERS = "/offline-access-portal/app/loadCustomers";
|
||||
|
||||
|
||||
public static final String ACCOUNT_MGMT = KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH + "/applications")
|
||||
.queryParam("referrer", "offline-access-portal").build("demo").toString();
|
||||
|
||||
|
||||
public static final String LOGOUT = KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
|
||||
.queryParam("redirect_uri", "/offline-access-portal").build("demo").toString();
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.keycloak.example;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.keycloak.util.StreamUtil;
|
||||
|
||||
/**
|
||||
* Very simple DAO, which stores/loads just one token per whole application into file in tmp directory. Useful just for example purposes.
|
||||
* In real environment, token should be stored in database.
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class RefreshTokenDAO {
|
||||
|
||||
public static final String FILE = System.getProperty("java.io.tmpdir") + "/offline-access-portal";
|
||||
|
||||
public static void saveToken(final String token) throws IOException {
|
||||
PrintWriter writer = null;
|
||||
try {
|
||||
writer = new PrintWriter(new BufferedWriter(new FileWriter(FILE)));
|
||||
writer.print(token);
|
||||
} finally {
|
||||
if (writer != null) {
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String loadToken() throws IOException {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(FILE);
|
||||
return StreamUtil.readString(fis);
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
return null;
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
fis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<jboss-deployment-structure>
|
||||
<deployment>
|
||||
<dependencies>
|
||||
<!-- the Demo code uses classes in these modules. These are optional to import if you are not using
|
||||
Apache Http Client or the HttpClientBuilder that comes with the adapter core -->
|
||||
<module name="org.apache.httpcomponents"/>
|
||||
<module name="org.keycloak.keycloak-adapter-spi"/>
|
||||
</dependencies>
|
||||
</deployment>
|
||||
</jboss-deployment-structure>
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"realm": "demo",
|
||||
"resource": "offline-access-portal",
|
||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url": "/auth",
|
||||
"ssl-required" : "external",
|
||||
"credentials": {
|
||||
"secret": "password"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||
pageEncoding="ISO-8859-1" %>
|
||||
<%@ page session="false" %>
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Offline Access Example</title>
|
||||
</head>
|
||||
<body bgcolor="#ffffff">
|
||||
<h1>Offline Access Example</h1>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>
|
||||
Login finished and refresh token saved successfully.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<div style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;">
|
||||
<% if ((Boolean) request.getAttribute("isOfflineToken")) { %>
|
||||
Token type <b>is</b> offline token! You will be able to load customers even after logout or server restart. Offline token can be revoked in account management or by admin in admin console.
|
||||
<% } else { %>
|
||||
Token <b>is not</b> offline token! Once you logout or restart server, token won't be valid anymore and you won't be able to load customers.
|
||||
<% } %>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="/offline-access-portal/app">Back to home page</a>
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,45 @@
|
|||
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||
pageEncoding="ISO-8859-1" %>
|
||||
<%@ page import="org.keycloak.example.OfflineExampleUris" %>
|
||||
<%@ page session="false" %>
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Offline Access Example</title>
|
||||
</head>
|
||||
<body bgcolor="#ffffff">
|
||||
<h1>Offline Access Example</h1>
|
||||
|
||||
<hr />
|
||||
|
||||
<% if (request.getRemoteUser() == null) { %>
|
||||
<a href="<%= OfflineExampleUris.LOGIN_CLASSIC %>">Login classic</a> |
|
||||
<a href="<%= OfflineExampleUris.LOGIN_WITH_OFFLINE_TOKEN %>">Login with offline access</a> |
|
||||
<% } else { %>
|
||||
<a href='<%= OfflineExampleUris.LOGOUT %>'>Logout</a> |
|
||||
<% } %>
|
||||
|
||||
<a href='<%= OfflineExampleUris.ACCOUNT_MGMT %>'>Account management</a> |
|
||||
|
||||
<% if ((Boolean) request.getAttribute("savedTokenAvailable")) { %>
|
||||
<a href="<%= OfflineExampleUris.LOAD_CUSTOMERS %>">Load customers with saved token</a> |
|
||||
<% } %>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Saved Refresh Token Info</h2>
|
||||
<div style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;">
|
||||
<%= request.getAttribute("tokenInfo") %>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Customers</h2>
|
||||
<div style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;">
|
||||
<%= request.getAttribute("customers") %>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0">
|
||||
|
||||
<module-name>offline-access-portal</module-name>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>OfflineAccessPortalServle</servlet-name>
|
||||
<servlet-class>org.keycloak.example.OfflineAccessPortalServlet</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>OfflineAccessPortalServle</servlet-name>
|
||||
<url-pattern>/app/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>User</web-resource-name>
|
||||
<url-pattern>/app/login/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>user</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
|
||||
<!--
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<user-data-constraint>
|
||||
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
||||
</user-data-constraint>
|
||||
</security-constraint> -->
|
||||
|
||||
<login-config>
|
||||
<auth-method>KEYCLOAK</auth-method>
|
||||
<realm-name>demo</realm-name>
|
||||
</login-config>
|
||||
|
||||
<security-role>
|
||||
<role-name>admin</role-name>
|
||||
</security-role>
|
||||
<security-role>
|
||||
<role-name>user</role-name>
|
||||
</security-role>
|
||||
</web-app>
|
|
@ -0,0 +1,3 @@
|
|||
<script>
|
||||
window.location = "/offline-access-portal/app";
|
||||
</script>
|
|
@ -37,6 +37,7 @@
|
|||
<module>third-party</module>
|
||||
<module>third-party-cdi</module>
|
||||
<module>service-account</module>
|
||||
<module>offline-access-app</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package org.keycloak.example;
|
||||
|
||||
/**
|
||||
* Client authentication based on JWT signed by client private key .
|
||||
* See Keycloak documentation and <a href="https://tools.ietf.org/html/rfc7519">specs</a> for more details.
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ProductSAClientSignedJWTServlet extends ProductServiceAccountServlet {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": [ "user" ],
|
||||
"realmRoles": [ "user", "offline_access" ],
|
||||
"clientRoles": {
|
||||
"account": [ "manage-account" ]
|
||||
}
|
||||
|
@ -37,7 +37,7 @@
|
|||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": [ "user" ],
|
||||
"realmRoles": [ "user", "offline_access" ],
|
||||
"clientRoles": {
|
||||
"account": [ "manage-account" ]
|
||||
}
|
||||
|
@ -52,7 +52,7 @@
|
|||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": [ "user" ],
|
||||
"realmRoles": [ "user", "offline_access" ],
|
||||
"clientRoles": {
|
||||
"account": [ "manage-account" ]
|
||||
}
|
||||
|
@ -103,6 +103,10 @@
|
|||
{
|
||||
"client": "third-party",
|
||||
"roles": ["user"]
|
||||
},
|
||||
{
|
||||
"client": "offline-access-portal",
|
||||
"roles": ["user", "offline_access"]
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
|
@ -190,6 +194,17 @@
|
|||
"attributes": {
|
||||
"jwt.credential.certificate": "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"clientId": "offline-access-portal",
|
||||
"enabled": true,
|
||||
"consentRequired": true,
|
||||
"adminUrl": "/offline-access-portal",
|
||||
"baseUrl": "/offline-access-portal",
|
||||
"redirectUris": [
|
||||
"/offline-access-portal/*"
|
||||
],
|
||||
"secret": "password"
|
||||
}
|
||||
],
|
||||
"clientScopeMappings": {
|
||||
|
|
Loading…
Reference in a new issue