зеркало из
1
0
Форкнуть 0
This commit is contained in:
Brett Hacker 2019-10-01 00:00:04 -05:00
Коммит 145ff73907
72 изменённых файлов: 4130 добавлений и 0 удалений

33
.classpath Normal file
Просмотреть файл

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry exported="true" kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

2
.deployment Normal file
Просмотреть файл

@ -0,0 +1,2 @@
[config]
command = deploy.cmd

13
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,13 @@
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
local.properties
# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
!/.mvn/wrapper/maven-wrapper.jar

36
.idea/libraries/lib.xml Normal file
Просмотреть файл

@ -0,0 +1,36 @@
<component name="libraryTable">
<library name="lib">
<CLASSES>
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/slf4j-log4j12-1.7.5.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/commons-logging-1.1.1.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/gson-2.2.4.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/spring-aop-3.0.5.RELEASE.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/spring-context-3.0.5.RELEASE.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/adal4j-1.1.1.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/spring-expression-3.0.5.RELEASE.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/mssql-jdbc-6.4.0.jre8.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/spring-web-3.0.5.RELEASE.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/spring-core-3.0.5.RELEASE.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/log4j-1.2.17.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/bcprov-jdk15on-1.51.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/commons-codec-1.10.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/lang-tag-1.4.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/json-20090211.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/nimbus-jose-jwt-3.1.2.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/jcip-annotations-1.0.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/spring-beans-3.0.5.RELEASE.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/slf4j-api-1.7.5.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/commons-lang3-3.3.1.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/oauth2-oidc-sdk-4.5.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/spring-asm-3.0.5.RELEASE.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/spring-context-support-3.0.5.RELEASE.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/aopalliance-1.0.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/mail-1.4.7.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/json-smart-1.1.1.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/activation-1.1.jar!/" />
<root url="jar://$PROJECT_DIR$/target/oidcpoc/WEB-INF/lib/spring-webmvc-3.0.5.RELEASE.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

6
.idea/misc.xml Normal file
Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml Normal file
Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/src/main/main.iml" filepath="$PROJECT_DIR$/src/main/main.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

248
.idea/workspace.xml Normal file
Просмотреть файл

@ -0,0 +1,248 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="10bb03d6-5048-4498-8d09-46563957dbe9" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/.classpath" beforeDir="false" afterPath="$PROJECT_DIR$/.classpath" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/microsoft/aad/oidcpoc/AuthFlow.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/microsoft/aad/oidcpoc/AuthFlow.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/microsoft/aad/oidcpoc/CoreFilter.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/microsoft/aad/oidcpoc/CoreFilter.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/webapp/WEB-INF/web.xml" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/webapp/WEB-INF/web.xml" afterDir="false" />
</list>
<ignored path="$PROJECT_DIR$/out/" />
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FUSProjectUsageTrigger">
<session id="1743779220">
<usages-collector id="statistics.lifecycle.project">
<counts>
<entry key="project.closed" value="1" />
<entry key="project.open.time.1" value="1" />
<entry key="project.opened" value="1" />
</counts>
</usages-collector>
</session>
</component>
<component name="FileEditorManager">
<leaf>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/main/java/com/microsoft/aad/oidcpoc/AadController.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="102">
<caret line="32" column="13" selection-start-line="32" selection-start-column="13" selection-end-line="32" selection-end-column="13" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/src/main/java/com/microsoft/aad/oidcpoc/HomeController.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="34">
<caret line="2" column="25" selection-start-line="2" selection-start-column="25" selection-end-line="2" selection-end-column="25" />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectFrameBounds" extendedState="6">
<option name="x" value="100" />
<option name="y" value="40" />
<option name="width" value="2800" />
<option name="height" value="1760" />
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="true">
<ConfirmationsSetting value="1" id="Add" />
</component>
<component name="ProjectView">
<navigator proportions="" version="1">
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="ProjectPane" />
<pane id="PackagesPane">
<subPane>
<expand>
<path>
<item name="java-webapp-oidc-migrate-poc" type="58d84e3e:PackageViewProjectNode" />
<item name="main" type="616d4139:PackageViewModuleNode" />
</path>
<path>
<item name="java-webapp-oidc-migrate-poc" type="58d84e3e:PackageViewProjectNode" />
<item name="main" type="616d4139:PackageViewModuleNode" />
<item name="Libraries" type="5c3f07af:PackageViewLibrariesNode" />
</path>
<path>
<item name="java-webapp-oidc-migrate-poc" type="58d84e3e:PackageViewProjectNode" />
<item name="main" type="616d4139:PackageViewModuleNode" />
<item name="Libraries" type="5c3f07af:PackageViewLibrariesNode" />
<item name="org" type="1f31426a:PackageElementNode" />
</path>
<path>
<item name="java-webapp-oidc-migrate-poc" type="58d84e3e:PackageViewProjectNode" />
<item name="main" type="616d4139:PackageViewModuleNode" />
<item name="Libraries" type="5c3f07af:PackageViewLibrariesNode" />
<item name="sun" type="1f31426a:PackageElementNode" />
</path>
</expand>
<select />
</subPane>
</pane>
<pane id="Scope" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="project.structure.last.edited" value="Libraries" />
<property name="project.structure.proportion" value="0.0" />
<property name="project.structure.side.proportion" value="0.2" />
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="10bb03d6-5048-4498-8d09-46563957dbe9" name="Default Changelist" comment="" />
<created>1540405748108</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1540405748108</updated>
</task>
<servers />
</component>
<component name="ToolWindowManager">
<frame x="-6" y="-6" width="1513" height="973" extended-state="6" />
<layout>
<window_info id="Designer" />
<window_info id="Favorites" side_tool="true" />
<window_info content_ui="combo" id="Project" order="0" visible="true" weight="0.24948454" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info anchor="bottom" id="Version Control" show_stripe_button="false" />
<window_info anchor="bottom" id="Terminal" />
<window_info anchor="bottom" id="Event Log" side_tool="true" />
<window_info active="true" anchor="bottom" id="Messages" visible="true" weight="0.32977462" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Find" order="1" />
<window_info anchor="bottom" id="Run" order="2" />
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="right" id="Palette&#9;" />
<window_info anchor="right" id="Maven Projects" />
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
</layout>
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/src/main/java/com/microsoft/aad/oidcpoc/AadController.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="102">
<caret line="32" column="13" selection-start-line="32" selection-start-column="13" selection-end-line="32" selection-end-column="13" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/com/microsoft/aad/oidcpoc/HomeController.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="34">
<caret line="2" column="25" selection-start-line="2" selection-start-column="25" selection-end-line="2" selection-end-column="25" />
</state>
</provider>
</entry>
</component>
<component name="masterDetails">
<states>
<state key="ArtifactsStructureConfigurable.UI">
<settings>
<artifact-editor />
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="FacetStructureConfigurable.UI">
<settings>
<last-edited>No facets are configured</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="GlobalLibrariesConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="JdkListConfigurable.UI">
<settings>
<last-edited>1.8</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="ModuleStructureConfigurable.UI">
<settings>
<last-edited>main</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="ProjectLibrariesConfigurable.UI">
<settings>
<last-edited>lib</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

37
.project Normal file
Просмотреть файл

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>java-webapp-oidc-migrate-poc</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.validation.validationbuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
</natures>
</projectDescription>

13
.settings/.jsdtscope Normal file
Просмотреть файл

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main/webapp"/>
<classpathentry excluding="**/bower_components/*|**/node_modules/*|**/*.min.js" kind="src" path="target/m2e-wtp/web-resources"/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.WebProject">
<attributes>
<attribute name="hide" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
<classpathentry kind="output" path=""/>
</classpath>

Просмотреть файл

@ -0,0 +1,4 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
encoding//src/test/java=UTF-8

Просмотреть файл

@ -0,0 +1,9 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.7

Просмотреть файл

@ -0,0 +1,2 @@
eclipse.preferences.version=1
org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false

Просмотреть файл

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

Просмотреть файл

@ -0,0 +1,2 @@
eclipse.preferences.version=1
org.eclipse.m2e.wtp.enabledProjectSpecificPrefs=false

Просмотреть файл

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="oidcpoc">
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
<property name="java-output-path" value="/active-directory-java-webapp-openidconnect-complete/target/classes"/>
<property name="context-root" value="oidcpoc"/>
</wb-module>
</project-modules>

Просмотреть файл

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<faceted-project>
<fixed facet="wst.jsdt.web"/>
<installed facet="java" version="1.7"/>
<installed facet="jst.web" version="2.4"/>
<installed facet="wst.jsdt.web" version="1.0"/>
</faceted-project>

Просмотреть файл

@ -0,0 +1 @@
org.eclipse.wst.jsdt.launching.baseBrowserLibrary

Просмотреть файл

@ -0,0 +1 @@
Window

Просмотреть файл

@ -0,0 +1,2 @@
disabled=06target
eclipse.preferences.version=1

Просмотреть файл

@ -0,0 +1,2 @@
eclipse.preferences.version=1
org.eclipse.wst.ws.service.policy.projectEnabled=false

21
LICENSE Normal file
Просмотреть файл

@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

161
README.md Normal file
Просмотреть файл

@ -0,0 +1,161 @@
# Integrating Azure AD into a Java web application
## Quick Start
<a href="https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FMicrosoft%2Fjava-webapp-oidc-migrate-poc%2Fmaster%2Fazuredeploy.json" target="_blank"><img src="http://azuredeploy.net/deploybutton.png"/></a>
__Details__
This Java app will give you with a quick and easy way to set up a Web application in Java using OAuth2. The sample included in the download is designed to run on any platform and tested with Java 8. This repo is based on the original repo at https://github.com/Azure-Samples/active-directory-java-webapp-openidconnect but has been updated with the following features:
* Uses Bootstrap to give it a nice layout similar to Visual Studio .Net web project templates
* Demonstrates a pattern for combining Azure AD authentication side by side with "legacy auth". The ARM template will create a simple Azure SQL database with a "User" table that has an email and a password column. The pattern allows a user to log in using "credentials" from the legacy process, or login to Azure AD and then automatically "link" the Azure AD account to the legacy account.
* Unlike most Java ARM templates, this one actually deploys the code for you as well. There is a custom "deploy.cmd" file that uses the Java tools present in Azure Web Apps to compile the Java classes and create the WAR file for automated deployment. If you fork this project, you can directly connect your app and enjoy continuous integration from GitHub! https://blog.github.com/2015-09-15-automating-code-deployment-with-github-and-azure/
IMPORTANT: the "legacy auth system" articulated here is in NO WAY meant to model a best practice for legacy authentication. The database table, hosted in an Azure SQL database, includes a CLEAR TEXT password. Surprisingly, there are still systems running today using such a process but it is unwise in the extreme and should be considered an anti-pattern.
* ARM template deploys the following:
* Azure Web App with this source code
* Azure SQL DB
* Requires the following (see step-by-step deployment instructions above for details):
1. Azure AD application with the following:
* Sign in and read user profile
### Step 1: Register an Azure AD Tenant
To use this sample you will need a Azure Active Directory Tenant. If you're not sure what a tenant is or how you would get one, read [What is an Azure AD tenant](http://technet.microsoft.com/library/jj573650.aspx)? or [Sign up for Azure as an organization](http://azure.microsoft.com/documentation/articles/sign-up-organization/). These docs should get you started on your way to using Azure AD.
### Step 2: Setup
An Azure Active Directory app must be created in your tenant:
* Log into the Azure portal, and click on Azure Active Directory, then click on Properties
* Click to copy the "Directory ID". This is also referred to as a "Tenant Id". Save this string, you'll need it in a bit.
* Click on App registrations
* Click "+ New application registration" and enter the name of your app (like "My Java Sample"). This title will be seen when users are prompted for their credentials.
* Select "Web app / API", and enter the Sign-on URL. If you're setting this up before you deploy the app to Azure, you can enter http://loopback as a placeholder. Click "Create". The default permissions will work fine.
* Back on the Settings panel, click "Keys". Under Description, enter a name for the application key, like "Key 1". Select an expiration date for the key.
* Click "Save". An application secret will be generated and displayed. COPY this key and record it - you'll need it in a minute when setting up the web application. NOTE: this key will not be displayed again and cannot be retrieved. If you lose it, you'll have to come back, delete it, and create another one.
* Click "Properties", and click "Yes" under Multi-tenanted at the bottom of the Properties panel then click "Save". This enables your application to authenticate users from other Azure AD tenants. NOTE: The App ID URI must match your Azure AD domain name if you choose the multi-tenant option.
* Finally, before we are done with the first app, record the "Application ID". You can click to the right of it in the main panel and it will copy it to your clipboard. Record it along with the app secret from above - these two strings will be needed to setup the web app.
* Use the info copied above to complete filling out the ARM template form (or the azuredeploy.parameters.json file if you choose to deploy via automation).
* Once deployed, return to the Azure AD app and update the URL and the Reply URL. Set the Reply url to your new app URL, like "https://mynewapp.azurewebsites.net/" (note the trailing slash).
* Log in!
### Optional Azure AD App Setup
If you'd rather use a command line to create your Azure AD app, try PowerShell:
```powershell
$appName = "[Your Azure AD app name]"
if ($ctx -eq $null) { $ctx = Connect-AzureRmAccount }
$md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
$hash = $md5.ComputeHash(([System.Guid]::NewGuid()).ToByteArray()) + $md5.ComputeHash(([System.Guid]::NewGuid()).ToByteArray())
$appPw = [System.Convert]::ToBase64String($hash)
$secPw = ConvertTo-SecureString $appPw -AsPlainText -Force -ErrorAction Stop
$app = New-AzureRmADApplication `
-DisplayName $appName `
-HomePage "https://$($appName.Replace(' ', ''))/" `
-IdentifierUris "https://$($ctx.Context.Tenant.Directory)/$($appName.Replace(' ', ''))" `
-Password $secPw `
-AvailableToOtherTenants $true
$sp = New-AzureRmADServicePrincipal -ApplicationId $app.ApplicationId
Write-Output "--------"
Write-Output "AppID: $($app.ApplicationId)"
Write-Output "AppSecret: $($appPw)"
Write-Output "Tenant Name: $($ctx.Context.Tenant.Directory)"
Write-Output "TenantId: $($ctx.Context.Tenant.Id)"
```
Or use the Azure command line (CL):
```bash
az ad app create --display-name "[Your Azure AD app name]" --homepage "[Your home page URL]" --password "[Really long password that ideally should be generated as above]" --identifier-uris [Your custom app URI] --available-to-other-tenants true
az ad sp create --id [AppID returned from previous command output]
```
Finally, if you'd like to deploy from this repo using PowerShell rather than clicking the link above:
```powershell
$startTime=Get-Date
Write-Host "Beginning deployment at $starttime"
$version++
#ensure we're logged in
$ctx = Get-AzureRmContext -ErrorAction SilentlyContinue
if ($ctx.Account -eq $null) { $ctx = Connect-AzureRmAccount }
#DEPLOYMENT OPTIONS
$RGName = "[New Resource Group Name]"
$DeployRegion = "[Resource Group location, like 'westus2']"
$HostingPlanName = "[Name of your new hosting plan]"
$sqlpassword = "[Password to use for your SQL server]"
$tenantName = "[Name of your Azure AD apps tenant, like 'contoso.onmicrosoft.com']"
$appId = "[GUID AppID of your Azure AD app]"
$appSecret = "[Your Azure AD app secret]"
#deploy
$parms=@{
"hostingPlanName" = $HostingPlanName;
"sqlPassword" = $sqlpassword;
"tenantName" = $tenantName;
"appId" = $appId;
"appSecret" = $appSecret;
}
$TemplateFile = "https://raw.githubusercontent.com/Microsoft/java-webapp-oidc-migrate-poc/master/azuredeploy.json?x=$version"
try {
Get-AzureRmResourceGroup -Name $RGName -ErrorAction Stop
Write-Host "Resource group $RGName exists, updating deployment"
}
catch {
$RG = New-AzureRmResourceGroup -Name $RGName -Location $DeployRegion
Write-Host "Created new resource group $RGName."
}
$deployment = New-AzureRmResourceGroupDeployment -ResourceGroupName $RGName -TemplateParameterObject $parms -TemplateFile $TemplateFile -Name "WebAppDeploy" -Force -Verbose
Start-Process -FilePath $deployment.Outputs.webUrl.Value
$endTime=Get-Date
Write-Host ""
Write-Host "Total Deployment time:"
New-TimeSpan -Start $startTime -End $endTime | Select Hours, Minutes, Seconds
```
### Latest version:
The latest version of this sample has been refactored to leverage a Managed Identity to connect to the database.
https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview
This is where I was deciding whether to connect with name/pw or MSI, so I can dev locally:
https://github.com/microsoft/java-webapp-oidc-migrate-poc/blob/master/src/main/java/com/microsoft/aad/oidcpoc/AuthHelper.java#L70
This news it up with MSI, using the latest JDBC which is smart enough to know how to grab the token with the MSI ID:
https://github.com/microsoft/java-webapp-oidc-migrate-poc/blob/master/src/main/java/com/microsoft/aad/oidcpoc/db.java#L30
This one retrieves the access token via REST then plugs it in to the getConnection method. I wrote it for older JDBC but I'm not using it here (and I didn't test it, either... (smile) ) - I just added it for reference.
https://github.com/microsoft/java-webapp-oidc-migrate-poc/blob/master/src/main/java/com/microsoft/aad/oidcpoc/db.java#L44
And here's what I was originally using by default:
https://github.com/microsoft/java-webapp-oidc-migrate-poc/blob/master/src/main/java/com/microsoft/aad/oidcpoc/db.java#L62
Here's the doc that describes the JDBC method:
https://docs.microsoft.com/en-us/sql/connect/jdbc/connecting-using-azure-active-directory-authentication?view=sql-server-2017
### Acknowledgements
We would like to acknowledge the folks who own/contribute to the following projects for their support of Azure Active Directory and their libraries that were used to build this sample. In places where we forked these libraries to add additional functionality, we ensured that the chain of forking remains intact so you can navigate back to the original package. Working with such great partners in the open source community clearly illustrates what open collaboration can accomplish. Thank you!
# Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

242
azuredeploy.json Normal file
Просмотреть файл

@ -0,0 +1,242 @@
{
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"hostingPlanName": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Name of the hosting plan to use in Azure."
}
},
"skuName": {
"type": "string",
"defaultValue": "B1",
"allowedValues": [
"F1",
"D1",
"B1",
"B2",
"B3",
"S1",
"S2",
"S3",
"P1",
"P2",
"P3",
"P4"
],
"metadata": {
"description": "Describes plan's pricing tier and instance size. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/"
}
},
"skuCapacity": {
"type": "int",
"defaultValue": 1,
"minValue": 1,
"metadata": {
"description": "Describes plan's instance count"
}
},
"SqlDBEdition": {
"type": "string",
"defaultValue": "Basic",
"allowedValues": [ "Basic", "Standard", "Premium" ],
"metadata": {
"description": "Select the performance characteristics for your SQL Database instance"
}
},
"SqlDBRequestedServiceObjectiveName": {
"type": "string",
"defaultValue": "Basic",
"allowedValues": [ "Basic", "S0", "S1", "S2", "P1", "P2", "P3" ],
"metadata": {
"description": "Describes the performance level for Edition"
}
},
"sqlPassword": {
"type": "securestring",
"metadata": {
"description": "SQL database app password"
}
},
"tenantName": {
"type": "string",
"metadata": {
"description": "The tenant name, like 'contoso.onmicrosoft.com', for your application in Azure AD (see docs)"
}
},
"appId": {
"type": "string",
"metadata": {
"description": "The app ID for your application in Azure AD (see docs)"
}
},
"appSecret": {
"type": "string",
"metadata": {
"description": "The app secret for your application in Azure AD (see docs)"
}
}
},
"variables": {
"websiteName": "[concat(parameters('hostingPlanName'), uniqueString(resourceGroup().Id))]",
"repoUrl": "https://github.com/Microsoft/java-webapp-oidc-migrate-poc.git",
"branch": "master",
"sqlserverName": "[toLower(concat(parameters('hostingPlanName'), 'sqlsrv'))]",
"DBCollation": "SQL_Latin1_General_CP1_CS_AS",
"DBName": "[concat(parameters('hostingPlanName'), 'db')]",
"sqlAppLogin": "[concat(parameters('hostingPlanName'), '_app')]"
},
"resources": [
{
"name": "[variables('sqlserverName')]",
"type": "Microsoft.Sql/servers",
"location": "[resourceGroup().location]",
"apiVersion": "2014-04-01-preview",
"dependsOn": [],
"tags": {
"displayName": "SQLServer"
},
"properties": {
"administratorLogin": "[variables('sqlAppLogin')]",
"administratorLoginPassword": "[parameters('sqlPassword')]"
},
"resources": [
{
"name": "[variables('DBName')]",
"type": "databases",
"location": "[resourceGroup().location]",
"apiVersion": "2014-04-01-preview",
"dependsOn": [
"[resourceId('Microsoft.Sql/servers', variables('sqlserverName'))]"
],
"tags": {
"displayName": "Database"
},
"properties": {
"collation": "[variables('DBCollation')]",
"edition": "[parameters('SqlDBEdition')]",
"maxSizeBytes": "1073741824",
"requestedServiceObjectiveName": "[parameters('SqlDBRequestedServiceObjectiveName')]"
}
}
]
},
{
"name": "[concat(variables('sqlserverName'), '/AllowAllWindowsAzureIps')]",
"type": "Microsoft.Sql/servers/firewallRules",
"apiVersion": "2014-04-01-preview",
"location": "[resourceGroup().location]",
"kind": "v12.0",
"properties": {
"startIpAddress": "0.0.0.0",
"endIpAddress": "0.0.0.0"
},
"dependsOn": [
"[resourceId('Microsoft.Sql/servers', variables('sqlserverName'))]"
]
},
{
"apiVersion": "2015-08-01",
"name": "[parameters('hostingPlanName')]",
"type": "Microsoft.Web/serverfarms",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "HostingPlan"
},
"sku": {
"name": "[parameters('skuName')]",
"capacity": "[parameters('skuCapacity')]"
},
"properties": {
"name": "[parameters('hostingPlanName')]"
}
},
{
"apiVersion": "2015-08-01",
"name": "[variables('webSiteName')]",
"type": "Microsoft.Web/sites",
"location": "[resourceGroup().location]",
"tags": {
"[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource",
"displayName": "Website"
},
"dependsOn": [
"[concat('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
],
"properties": {
"name": "[variables('webSiteName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
},
"resources": [
{
"apiVersion": "2015-08-01",
"name": "web",
"type": "sourcecontrols",
"tags": {
"displayName": "AppSource"
},
"dependsOn": [
"[concat('Microsoft.Web/Sites/', variables('webSiteName'))]",
"[concat('Microsoft.Web/Sites/', variables('webSiteName'), '/config/web')]",
"[concat('Microsoft.Web/Sites/', variables('webSiteName'), '/config/appsettings')]",
"[concat('Microsoft.Sql/Servers/', variables('sqlserverName'), '/firewallRules/AllowAllWindowsAzureIps')]",
"[concat('Microsoft.Sql/Servers/', variables('sqlserverName'), '/databases/', variables('DBName'))]"
],
"properties": {
"repoUrl": "[variables('repoUrl')]",
"branch": "[variables('branch')]",
"IsManualIntegration": true
}
},
{
"apiVersion": "2015-08-01",
"name": "web",
"type": "config",
"tags": {
"displayName": "AppConfig"
},
"dependsOn": [
"[concat('Microsoft.Web/sites/', variables('webSiteName'))]"
],
"properties": {
"javaVersion": "1.8",
"javaContainer": "TOMCAT",
"javaContainerVersion": "8.5"
}
},
{
"name": "appsettings",
"type": "config",
"apiVersion": "2015-08-01",
"dependsOn": [
"[concat('Microsoft.Web/sites/', variables('webSiteName'))]",
"[resourceId('Microsoft.Sql/servers', variables('sqlserverName'))]"
],
"tags": {
"displayName": "WebSettings"
},
"properties": {
"SCM_COMMAND_IDLE_TIMEOUT": 240,
"authority": "https://login.microsoftonline.com/",
"tenant": "[parameters('tenantName')]",
"db_host": "[reference(concat('Microsoft.Sql/servers/', variables('sqlserverName'))).fullyQualifiedDomainName]",
"db_name": "[variables('DBName')]",
"db_user": "[variables('sqlAppLogin')]",
"db_password": "[parameters('sqlPassword')]",
"client_id": "[parameters('appId')]",
"secret_key": "[parameters('appSecret')]",
"require_ssl": "true"
}
}
]
}
],
"outputs": {
"webUrl": {
"type": "string",
"value": "[concat('https://', variables('webSiteName'), '.azurewebsites.net')]"
}
}
}

Просмотреть файл

@ -0,0 +1,33 @@
{
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"hostingPlanName": {
"value": ""
},
"skuName": {
"value": "F1"
},
"skuCapacity": {
"value": "1"
},
"SqlDBEdition": {
"value": "Basic"
},
"SqlDBRequestedServiceObjectiveName": {
"value": "Basic"
},
"sqlPassword": {
"value": ""
},
"tenantName": {
"value": ""
},
"appId": {
"value": ""
},
"appSecret": {
"value": ""
}
}
}

39
dbschema.sql Normal file
Просмотреть файл

@ -0,0 +1,39 @@
/****** Object: Table [dbo].[Thing] Script Date: 4/5/2018 9:48:20 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Thing](
[ThingId] [int] IDENTITY(1,1) NOT NULL,
[UserId] [int] NOT NULL,
[Thing] [nvarchar](50) NULL,
CONSTRAINT [PK_Thing] PRIMARY KEY CLUSTERED
(
[ThingId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[User] Script Date: 4/5/2018 9:48:20 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[User](
[UserId] [int] IDENTITY(1,1) NOT NULL,
[Email] [nvarchar](100) NOT NULL,
[UniqueId] [nvarchar](100) NULL,
[FName] [nvarchar](50) NULL,
[LName] [nvarchar](50) NULL,
[Password] [nvarchar](20) NULL,
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[UserId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Thing] WITH CHECK ADD CONSTRAINT [FK_Thing_User] FOREIGN KEY([UserId])
REFERENCES [dbo].[User] ([UserId])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Thing] CHECK CONSTRAINT [FK_Thing_User]
GO

53
deploy.cmd Normal file
Просмотреть файл

@ -0,0 +1,53 @@
REM Call to check for DB schema update
for /f "delims=" %%A in ('sqlcmd.exe -s "" -W -h -1 -S "%APPSETTING_db_host%" -U "%APPSETTING_db_user%" -P "%APPSETTING_db_password%" -d "%APPSETTING_db_name%" -Q "exit(set nocount on; select count(*) from sys.tables where name='User')"') do set "DBConfigured=%%A"
if "%DBConfigured%"=="0" (
echo "configuring database"
sqlcmd.exe -S "%APPSETTING_db_host%" -U "%APPSETTING_db_user%" -P "%APPSETTING_db_password%" -d "%APPSETTING_db_name%" -i "%HOME%\site\repository\dbschema.sql"
) else (
echo "configuration done"
)
REM Get Ivy to help us repopulate our dependencies
curl -L -O http://search.maven.org/remotecontent?filepath=org/apache/ivy/ivy/2.3.0/ivy-2.3.0.jar
REM Setup dir structure
md "%HOME%\site\repository\target"
md "%HOME%\site\repository\target\oidcpoc"
md "%HOME%\site\repository\target\oidcpoc\WEB-INF"
md "%HOME%\site\repository\target\oidcpoc\WEB-INF\lib"
md "%HOME%\site\repository\target\oidcpoc\WEB-INF\classes"
md "%HOME%\site\repository\target\oidcpoc\WEB-INF\classes\com"
md "%HOME%\site\repository\target\oidcpoc\WEB-INF\classes\com\microsoft"
md "%HOME%\site\repository\target\oidcpoc\WEB-INF\classes\com\microsoft\aad"
md "%HOME%\site\repository\target\oidcpoc\WEB-INF\classes\com\microsoft\aad\oidcpoc"
md "%HOME%\site\repository\cache"
REM Copy main source to target
xcopy "%HOME%\site\repository\src\main\webapp\*.*" "%HOME%\site\repository\target\oidcpoc\" /s/e/v/d/c/y
REM Use Ivy to download our dependencies based on the Maven pom.xml from the Eclipse project
"%JAVA_HOME%\bin\java.exe" -jar "%HOME%\site\repository\ivy-2.3.0.jar" -ivy "%HOME%\site\repository\pom.xml" -m2compatible -cache "%HOME%\site\repository\cache"
REM Grab the jar files from the cache and put in the target lib folder
For /R "%HOME%\site\repository\cache" %%G in (*.jar) do copy "%%G" "%HOME%\site\repository\target\oidcpoc\WEB-INF\lib"
REM Build our classes with javac
SET tSourcePath=%HOME%/site/repository/src/main/java/com/microsoft/aad/oidcpoc
SET tTargetPath=%HOME%/site/repository/target/oidcpoc/WEB-INF/classes/com/microsoft/aad/oidcpoc
SET tLibPath="%HOME%\site\repository\target\oidcpoc\WEB-INF\lib\*"
"%JAVA_HOME%\bin\javac.exe" -cp %tLibPath%;"%HOME%\site\repository\target\oidcpoc\WEB-INF\classes\*" -sourcepath %tSourcePath% -d %HOME%\site\repository\target\oidcpoc\WEB-INF\classes\ %HOME%\site\repository\src\main\java\com\microsoft\aad\oidcpoc\*.java
REM Create our war file - Azure Web Apps will automatically use that to update the deployment
del /f/q "%HOME%\site\ROOT.war"
cd %HOME%\site\repository\target\oidcpoc
"%JAVA_HOME%\bin\jar.exe" -cvf "%HOME%\site\ROOT.war" *
timeout 3 cmd
del /f/q "%HOME%\site\wwwroot\webapps\ROOT.war"
rd /S /Q "%HOME%\site\wwwroot\webapps\ROOT"
move /Y "%HOME%\site\ROOT.war" "%HOME%\site\wwwroot\webapps\ROOT.war"
timeout 3 cmd
curl https://%WEBSITE_HOSTNAME%

Просмотреть файл

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<component jdk="JavaSE-1.7">
<exclude-output/>
<contentEntry url="file://$MODULE_DIR$"/>
</component>

Просмотреть файл

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="eclipse" classpath-dir="$MODULE_DIR$" type="JAVA_MODULE" version="4" />

7
metadata.json Normal file
Просмотреть файл

@ -0,0 +1,7 @@
{
"itemDisplayName": "Create a web app on Azure with Java 11 and Tomcat 9 enabled",
"description": "This template creates a web app on azure with Java 11 and Tomcat 9 enabled allowing you to run Java applications in Azure. Template was authored by Donovan Brown of Microsoft.",
"summary": "Azure AD legacy integration sample/PoC.",
"githubUsername": "bretthackermsft",
"dateUpdated": "2019-06-25"
}

113
pom.xml Normal file
Просмотреть файл

@ -0,0 +1,113 @@
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.azure</groupId>
<artifactId>oidcpoc</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>oidcpoc</name>
<url>http://maven.apache.org</url>
<properties>
<spring.version>3.0.5.RELEASE</spring.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>7.2.2.jre8</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>adal4j</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
<version>4.5</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20090211</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<!-- Spring 3 dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
<build>
<finalName>oidcpoc</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warName>${project.artifactId}</warName>
<source>${project.basedir}\src</source>
<target>${maven.compiler.target}</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>install</id>
<phase>install</phase>
<goals>
<goal>sources</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.5</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>

Просмотреть файл

@ -0,0 +1,61 @@
/*******************************************************************************
* Copyright © Microsoft Open Technologies, Inc.
*
* All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
* OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
* ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
* PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for the specific language
* governing permissions and limitations under the License.
******************************************************************************/
package com.microsoft.aad.oidcpoc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.microsoft.aad.adal4j.AuthenticationResult;
@Controller
public class AadController {
@RequestMapping(value="/secure/aad", method = { RequestMethod.GET, RequestMethod.POST })
public String getDirectoryObjects(ModelMap model, HttpServletRequest httpRequest) {
HttpSession session = httpRequest.getSession();
AuthenticationResult result = (AuthenticationResult) session.getAttribute(AuthHelper.PRINCIPAL_SESSION_NAME);
if (result == null) {
model.addAttribute("error", new Exception("AuthenticationResult not found in session."));
return "/error";
} else {
setUserInfoAndTenant(model, result, session);
}
return "/secure/aad";
}
@RequestMapping(value="/secure/profile", method = RequestMethod.GET )
public String getProfile(ModelMap model, HttpServletRequest httpRequest) {
return "/secure/profile";
}
private void setUserInfoAndTenant(ModelMap model, AuthenticationResult authenticationResult, HttpSession session) {
String tenant = AuthHelper.getSetting(session.getServletContext(), "tenant");
model.addAttribute("tenant", tenant);
model.addAttribute("userInfo", authenticationResult.getUserInfo());
}
}

Просмотреть файл

@ -0,0 +1,301 @@
package com.microsoft.aad.oidcpoc;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.naming.ServiceUnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.ClientCredential;
import com.nimbusds.jwt.JWTParser;
import com.nimbusds.oauth2.sdk.AuthorizationCode;
import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
public class AuthFlow {
public static final String STATES = "states";
public static final String STATE = "state";
public static final Integer STATE_TTL = 3600;
public static final String FAILED_TO_VALIDATE_MESSAGE = "Failed to validate data received from Authorization service - ";
private String clientId = "";
private String clientSecret = "";
private String tenant = "";
private String authority;
public boolean is_b2c;
private String policy_susi="";
public AuthFlow(String ClientID, String ClientSecret, String Tenant, String Authority, String B2CProfile) {
clientId = ClientID;
clientSecret = ClientSecret;
tenant = Tenant;
authority = Authority;
policy_susi = B2CProfile;
}
public boolean isAuthDataExpired(HttpServletRequest httpRequest) {
AuthenticationResult authData = AuthHelper.getAuthSessionObject(httpRequest);
return authData.getExpiresOnDate().before(new Date()) ? true : false;
}
public void updateAuthDataUsingRefreshToken(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws Throwable {
AuthenticationResult authData =
getAccessTokenFromRefreshToken(AuthHelper.getAuthSessionObject(httpRequest).getRefreshToken());
setSessionPrincipal(httpRequest, authData, httpResponse);
}
public void processAuthenticationData(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String currentUri, String fullUrl)
throws Throwable {
HashMap<String, String> params = new HashMap<>();
for (String key : httpRequest.getParameterMap().keySet()) {
params.put(key, httpRequest.getParameterMap().get(key)[0]);
}
// validate that state in response equals to state in request
StateData stateData = validateState(httpRequest.getSession(), params.get(STATE));
AuthenticationResponse authResponse = AuthenticationResponseParser.parse(new URI(fullUrl), params);
if (AuthHelper.isAuthenticationSuccessful(authResponse)) {
AuthenticationSuccessResponse oidcResponse = (AuthenticationSuccessResponse) authResponse;
// validate that OIDC Auth Response matches Code Flow (contains only requested artifacts)
validateAuthRespMatchesCodeFlow(oidcResponse);
AuthenticationResult authData =
getAccessToken(oidcResponse.getAuthorizationCode(), currentUri);
// validate nonce to prevent reply attacks (code maybe substituted to one with broader access)
validateNonce(stateData, getClaimValueFromIdToken(authData.getIdToken(), "nonce"));
setSessionPrincipal(httpRequest, authData, httpResponse);
} else {
AuthenticationErrorResponse oidcResponse = (AuthenticationErrorResponse) authResponse;
throw new Exception(String.format("Request for auth code failed: %s - %s",
oidcResponse.getErrorObject().getCode(),
oidcResponse.getErrorObject().getDescription()));
}
}
public void sendAuthRedirect(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException {
httpResponse.setStatus(302);
// use state parameter to validate response from Authorization server
String state = UUID.randomUUID().toString();
// use nonce parameter to validate idToken
String nonce = UUID.randomUUID().toString();
storeStateInSession(httpRequest.getSession(), state, nonce);
String currentUri = AuthHelper.GetReplyUri(httpRequest);
httpResponse.sendRedirect(getRedirectUrl(currentUri, state, nonce));
}
private void validateNonce(StateData stateData, String nonce) throws Exception {
if (StringUtils.isEmpty(nonce) || !nonce.equals(stateData.getNonce())) {
throw new Exception(FAILED_TO_VALIDATE_MESSAGE + "could not validate nonce");
}
}
private String getClaimValueFromIdToken(String idToken, String claimKey) throws ParseException {
return (String) JWTParser.parse(idToken).getJWTClaimsSet().getClaim(claimKey);
}
/**
* make sure that state is stored in the session,
* delete it from session - should be used only once
*
* @param session
* @param state
* @throws Exception
*/
private StateData validateState(HttpSession session, String state) throws Exception {
if (StringUtils.isNotEmpty(state)) {
StateData stateDataInSession = removeStateFromSession(session, state);
if (stateDataInSession != null) {
return stateDataInSession;
}
}
throw new Exception(FAILED_TO_VALIDATE_MESSAGE + "could not validate state");
}
private void validateAuthRespMatchesCodeFlow(AuthenticationSuccessResponse oidcResponse) throws Exception {
if (oidcResponse.getIDToken() != null || oidcResponse.getAccessToken() != null ||
oidcResponse.getAuthorizationCode() == null) {
throw new Exception(FAILED_TO_VALIDATE_MESSAGE + "unexpected set of artifacts received");
}
}
@SuppressWarnings("unchecked")
private StateData removeStateFromSession(HttpSession session, String state) {
Map<String, StateData> states = (Map<String, StateData>) session.getAttribute(STATES);
if (states != null) {
eliminateExpiredStates(states);
StateData stateData = states.get(state);
if (stateData != null) {
states.remove(state);
return stateData;
}
}
return null;
}
@SuppressWarnings("unchecked")
private void storeStateInSession(HttpSession session, String state, String nonce) {
if (session.getAttribute(STATES) == null) {
session.setAttribute(STATES, new HashMap<String, StateData>());
}
((Map<String, StateData>) session.getAttribute(STATES)).put(state, new StateData(nonce, new Date()));
}
private void eliminateExpiredStates(Map<String, StateData> map) {
Iterator<Map.Entry<String, StateData>> it = map.entrySet().iterator();
Date currTime = new Date();
while (it.hasNext()) {
Map.Entry<String, StateData> entry = it.next();
long diffInSeconds = TimeUnit.MILLISECONDS.
toSeconds(currTime.getTime() - entry.getValue().getExpirationDate().getTime());
if (diffInSeconds > STATE_TTL) {
it.remove();
}
}
}
private AuthenticationResult getAccessTokenFromRefreshToken(
String refreshToken) throws Throwable {
AuthenticationContext context;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(authority + tenant + "/", true,
service);
Future<AuthenticationResult> future = context
.acquireTokenByRefreshToken(refreshToken, new ClientCredential(clientId, clientSecret), null, null);
result = future.get();
} catch (ExecutionException e) {
throw e.getCause();
} finally {
service.shutdown();
}
if (result == null) {
throw new ServiceUnavailableException("authentication result was null");
}
return result;
}
private AuthenticationResult getAccessToken(
AuthorizationCode authorizationCode, String currentUri)
throws Throwable {
String authCode = authorizationCode.getValue();
ClientCredential credential = new ClientCredential(clientId,
clientSecret);
AuthenticationContext context;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(authority + tenant + "/", true,
service);
Future<AuthenticationResult> future = context
.acquireTokenByAuthorizationCode(authCode, new URI(
currentUri), credential, null);
result = future.get();
} catch (ExecutionException e) {
throw e.getCause();
} finally {
service.shutdown();
}
if (result == null) {
throw new ServiceUnavailableException("authentication result was null");
}
return result;
}
private void setSessionPrincipal(HttpServletRequest httpRequest,
AuthenticationResult result, HttpServletResponse httpResponse) throws Exception, Exception {
httpRequest.getSession().setAttribute(AuthHelper.PRINCIPAL_SESSION_NAME, result);
String uri = httpRequest.getRequestURI();
if (! uri.equalsIgnoreCase("/secure/linkaccounts")) {
//exempting the linkaccounts call from linked account checking to avoid a loop here
String uniqueId = result.getUserInfo().getUniqueId();
if (!AuthHelper.SetAADSession(httpRequest, uniqueId)) {
httpResponse.sendRedirect(((HttpServletRequest) httpRequest)
.getContextPath() + "/secure/linkaccounts");
return;
}
}
}
public void removePrincipalFromSession(HttpServletRequest httpRequest) {
httpRequest.getSession().removeAttribute(AuthHelper.PRINCIPAL_SESSION_NAME);
}
private String getRedirectUrl(String currentUri, String state, String nonce)
throws UnsupportedEncodingException {
String redirectUrl = null;
if (is_b2c) {
redirectUrl = authority
+ this.tenant
+ "/oauth2/authorize?response_type=code&scope=openid&response_mode=form_post&redirect_uri="
+ URLEncoder.encode(currentUri, "UTF-8") + "&client_id="
+ clientId + "&resource=https%3a%2f%2fgraph.windows.net"
+ "&p=" + policy_susi
+ "&state=" + state
+ "&nonce=" + nonce;
}
else
{
redirectUrl = authority
+ this.tenant
+ "/oauth2/authorize?response_type=code&scope=openid&response_mode=form_post&redirect_uri="
+ URLEncoder.encode(currentUri, "UTF-8") + "&client_id="
+ clientId + "&resource=https%3a%2f%2fgraph.windows.net"
+ "&state=" + state
+ "&nonce=" + nonce;
}
return redirectUrl;
}
private class StateData {
private String nonce;
private Date expirationDate;
public StateData(String nonce, Date expirationDate) {
this.nonce = nonce;
this.expirationDate = expirationDate;
}
public String getNonce() {
return nonce;
}
public Date getExpirationDate() {
return expirationDate;
}
}
}

Просмотреть файл

@ -0,0 +1,204 @@
/*******************************************************************************
* Copyright © Microsoft Open Technologies, Inc.
*
* All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
* OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
* ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
* PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for the specific language
* governing permissions and limitations under the License.
******************************************************************************/
package com.microsoft.aad.oidcpoc;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.nimbusds.jwt.JWTParser;
import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
public final class AuthHelper {
public static final String PRINCIPAL_SESSION_NAME = "principal";
public static final String USEROBJ_SESSION_NAME = "DBUser";
public static final String ACCESS_TOKEN_LOCAL = "local";
private AuthHelper() {
}
public static void SetLocalSession(HttpSession session, DBUser user) {
//String accessTokenType, String accessToken, String refreshToken, long expiresIn, String idToken, UserInfo userInfo, boolean isMultipleResourceRefreshToken
AuthenticationResult res = new AuthenticationResult(AuthHelper.ACCESS_TOKEN_LOCAL, null, null, 0, null, null, false);
session.setAttribute(AuthHelper.PRINCIPAL_SESSION_NAME, res);
session.setAttribute(AuthHelper.USEROBJ_SESSION_NAME, user);
}
public static DBUser AuthLocalUser(HttpServletRequest request, String userName, String password) throws Exception {
return AuthLocalUser(request, userName, password, true);
}
public static DBUser AuthLocalUser(HttpServletRequest request, String userName, String password, boolean setLocalSession) throws Exception {
HttpSession session = request.getSession();
//db database = new db(getSetting(request, "db_host"), getSetting(request,"db_name"), getSetting(request,"db_user"), getSetting(request, "db_password"));
db database = GetDB(request);
DBUser user = database.ValidateUser(userName, password);
if (user == null) {
return null;
} else {
//need to create authenticated session
if (setLocalSession)
AuthHelper.SetLocalSession(session, user);
return user;
}
}
private static db GetDB(HttpServletRequest request) throws Exception {
db database = null;
String msiEndpoint = getSetting(request, "MSI_ENDPOINT");
if (msiEndpoint!=null) {
database = new db(getSetting(request, "db_host"), getSetting(request,"db_name"), msiEndpoint);
} else {
database = new db(getSetting(request, "db_host"), getSetting(request,"db_name"), getSetting(request,"db_user"), getSetting(request, "db_password"));
}
return database;
}
public static boolean SetAADSession(HttpServletRequest request, String uniqueId) throws Exception {
//db database = new db(getSetting(request, "db_host"), getSetting(request,"db_name"), getSetting(request,"db_user"), getSetting(request, "db_password"));
db database = GetDB(request);
DBUser user = database.GetUser(uniqueId);
if (user == null) {
//user not found, will need to load the legacy login form to match
return false;
} else {
HttpSession session = request.getSession();
session.setAttribute(AuthHelper.USEROBJ_SESSION_NAME, user);
return true;
}
}
public static void LinkAccounts(HttpServletRequest request, String uniqueId, int userId) throws Exception {
HttpSession session = request.getSession();
//db database = new db(getSetting(request, "db_host"), getSetting(request,"db_name"), getSetting(request,"db_user"), getSetting(request, "db_password"));
db database = GetDB(request);
DBUser user = database.LinkAccounts(userId, uniqueId);
session.setAttribute(AuthHelper.USEROBJ_SESSION_NAME, user);
}
public static DBUser GetSessionProfile(HttpServletRequest request) {
HttpSession session = request.getSession();
Object usrblob = session.getAttribute(AuthHelper.USEROBJ_SESSION_NAME);
return (usrblob!=null) ? (DBUser)usrblob : null;
}
public static boolean isAuthenticated(HttpServletRequest request) {
return request.getSession().getAttribute(PRINCIPAL_SESSION_NAME) != null;
}
public static AuthenticationResult getAuthSessionObject(
HttpServletRequest request) {
return (AuthenticationResult) request.getSession().getAttribute(
PRINCIPAL_SESSION_NAME);
}
public static String getPrincipalName(HttpServletRequest request) {
DBUser user = GetSessionProfile(request);
return (user!=null) ? user.Email : "N/A";
}
public static String GetCurrentUri(HttpServletRequest request) {
return GetCurrentUri(request, false);
}
public static String getSetting(ServletContext ctx, String key) {
return System.getProperty(key);
}
public static String getSetting(HttpServletRequest request, String key) {
HttpSession session = request.getSession();
ServletContext ctx = session.getServletContext();
return getSetting(ctx, key);
}
private static SchemeDTO GetScheme(HttpServletRequest request) {
boolean requireSsl = Boolean.parseBoolean(getSetting(request, "require_ssl"));
SchemeDTO res = new SchemeDTO();
res.Scheme = (requireSsl) ? "https" : request.getScheme();
res.Port = (requireSsl) ? 443 : request.getServerPort();
return res;
}
public static String GetCurrentUri(HttpServletRequest request, boolean FQDNOnly) {
SchemeDTO scheme = GetScheme(request);
String currentUri = scheme.Scheme
+ "://"
+ request.getServerName()
+ ("http".equals(scheme.Scheme)
&& scheme.Port == 80
|| "https".equals(scheme.Scheme)
&& scheme.Port == 443 ? "" : ":"
+ scheme.Port);
if (!FQDNOnly)
currentUri += request.getRequestURI();
return currentUri;
}
public static String GetReplyUri(HttpServletRequest request) {
SchemeDTO scheme = GetScheme(request);
String uri = scheme.Scheme
+ "://"
+ request.getServerName()
+ ("http".equals(scheme.Scheme)
&& scheme.Port == 80
|| "https".equals(scheme.Scheme)
&& scheme.Port == 443 ? "" : ":"
+ scheme.Port)
+ "/";
return uri;
}
public static Map<String, Object> GetClaims(String idTokenString) {
Map<String, Object> claims = null;
try {
claims = JWTParser.parse(idTokenString).getJWTClaimsSet().getAllClaims();
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return claims;
}
public static boolean containsAuthenticationData(
HttpServletRequest httpRequest) {
//Map<String, String[]> map = httpRequest.getParameterMap();
return httpRequest.getMethod().equalsIgnoreCase("POST") && (httpRequest.getParameterMap().containsKey(
AuthParameterNames.ERROR)
|| httpRequest.getParameterMap().containsKey(
AuthParameterNames.ID_TOKEN) || httpRequest
.getParameterMap().containsKey(AuthParameterNames.CODE));
}
public static boolean isAuthenticationSuccessful(
AuthenticationResponse authResponse) {
return authResponse instanceof AuthenticationSuccessResponse;
}
}

Просмотреть файл

@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright © Microsoft Open Technologies, Inc.
*
* All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
* OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
* ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
* PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for the specific language
* governing permissions and limitations under the License.
******************************************************************************/
package com.microsoft.aad.oidcpoc;
public final class AuthParameterNames {
private AuthParameterNames() {
}
public static String ERROR = "error";
public static String ERROR_DESCRIPTION = "error_description";
public static String ERROR_URI = "error_uri";
public static String ID_TOKEN = "id_token";
public static String CODE = "code";
}

Просмотреть файл

@ -0,0 +1,105 @@
/*******************************************************************************
// Copyright (c) Microsoft Corporation.
// All rights reserved.
//
// This code is licensed under the MIT License.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
******************************************************************************/
package com.microsoft.aad.oidcpoc;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.microsoft.aad.adal4j.AuthenticationResult;
public class BasicFilter implements Filter {
private AuthFlow _flow;
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
String currentUri = AuthHelper.GetReplyUri((HttpServletRequest) request);
String queryStr = httpRequest.getQueryString();
String fullUrl = currentUri + (queryStr != null ? "?" + queryStr : "");
//check if user has already authenticated locally
AuthenticationResult result = AuthHelper
.getAuthSessionObject(httpRequest);
if (result != null && result.getAccessTokenType()==AuthHelper.ACCESS_TOKEN_LOCAL) {
chain.doFilter(request, response);
return;
}
//continue with Azure AD validation
// check if user has a AuthData in the session
if (!AuthHelper.isAuthenticated(httpRequest)) {
if (AuthHelper.containsAuthenticationData(httpRequest)) {
_flow.processAuthenticationData(httpRequest, httpResponse, currentUri, fullUrl);
} else {
// not authenticated
_flow.sendAuthRedirect(httpRequest, httpResponse);
return;
}
}
if (_flow.isAuthDataExpired(httpRequest)) {
_flow.updateAuthDataUsingRefreshToken(httpRequest, httpResponse);
}
} catch (AuthenticationException authException) {
// something went wrong (like expiration or revocation of token)
// we should invalidate AuthData stored in session and redirect to Authorization server
_flow.removePrincipalFromSession(httpRequest);
_flow.sendAuthRedirect(httpRequest, httpResponse);
return;
} catch (Throwable exc) {
httpResponse.setStatus(500);
request.setAttribute("error", exc.getMessage());
request.getRequestDispatcher("/error.jsp").forward(request, response);
}
}
chain.doFilter(request, response);
}
public void init(FilterConfig config) throws ServletException {
ServletContext ctx = config.getServletContext();
_flow = new AuthFlow(
AuthHelper.getSetting(ctx, "client_id"),
AuthHelper.getSetting(ctx, "secret_key"),
AuthHelper.getSetting(ctx, "tenant"),
AuthHelper.getSetting(ctx, "authority"),
AuthHelper.getSetting(ctx, "policy_susi"));
_flow.is_b2c = (AuthHelper.getSetting(ctx, "is_b2c").equals("true"));
}
public void destroy() {
}
}

Просмотреть файл

@ -0,0 +1,73 @@
package com.microsoft.aad.oidcpoc;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.microsoft.aad.adal4j.AuthenticationException;
public class CoreFilter implements Filter {
private AuthFlow _flow;
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
//see if call is a response from Azure AD with claims
if (AuthHelper.isAuthenticated(httpRequest) || (!AuthHelper.containsAuthenticationData(httpRequest))) {
chain.doFilter(request, response);
return;
}
//continue with Azure AD validation
// check if user has a AuthData in the session
String currentUri = AuthHelper.GetReplyUri((HttpServletRequest) request);
String queryStr = httpRequest.getQueryString();
String fullUrl = currentUri + (queryStr != null ? "?" + queryStr : "");
_flow.processAuthenticationData(httpRequest, httpResponse, currentUri, fullUrl);
if (_flow.isAuthDataExpired(httpRequest)) {
_flow.updateAuthDataUsingRefreshToken(httpRequest, httpResponse);
}
} catch (AuthenticationException authException) {
// something went wrong (like expiration or revocation of token)
// we should invalidate AuthData stored in session and redirect to Authorization server
_flow.removePrincipalFromSession(httpRequest);
return;
} catch (Throwable exc) {
httpResponse.setStatus(500);
request.setAttribute("error", exc.getMessage());
request.getRequestDispatcher("/error.jsp").forward(request, response);
}
}
chain.doFilter(request, response);
}
public void init(FilterConfig config) throws ServletException {
ServletContext ctx = config.getServletContext();
_flow = new AuthFlow(
AuthHelper.getSetting(ctx, "client_id"),
AuthHelper.getSetting(ctx, "secret_key"),
AuthHelper.getSetting(ctx, "tenant"),
AuthHelper.getSetting(ctx, "authority"),
AuthHelper.getSetting(ctx, "policy_susi"));
_flow.is_b2c = (AuthHelper.getSetting(ctx, "is_b2c").equals("true"));
}
public void destroy() {
}
}

Просмотреть файл

@ -0,0 +1,12 @@
package com.microsoft.aad.oidcpoc;
import java.io.Serializable;
public class DBUser implements Serializable {
private static final long serialVersionUID = -7136067107910290726L;
public int UserId;
public String Email;
public String UniqueId;
public String FName;
public String LName;
}

Просмотреть файл

@ -0,0 +1,67 @@
/*******************************************************************************
* Copyright © Microsoft Open Technologies, Inc.
*
* All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
* OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
* ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
* PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for the specific language
* governing permissions and limitations under the License.
******************************************************************************/
package com.microsoft.aad.oidcpoc;
/**
* @author Azure Active Directory Contributor
*
*/
public abstract class DirectoryObject {
public DirectoryObject() {
super();
}
/**
*
* @return
*/
public abstract String getObjectId();
/**
* @param objectId
*/
public abstract void setObjectId(String objectId);
/**
*
* @return
*/
public abstract String getObjectType();
/**
*
* @param objectType
*/
public abstract void setObjectType(String objectType);
/**
*
* @return
*/
public abstract String getDisplayName();
/**
*
* @param displayName
*/
public abstract void setDisplayName(String displayName);
}

Просмотреть файл

@ -0,0 +1,30 @@
package com.microsoft.aad.oidcpoc;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
@RequestMapping(value="/", method = RequestMethod.GET )
public String GetIndex(ModelMap model, HttpServletRequest httpRequest) {
return "/index";
}
@RequestMapping(value="/about", method = RequestMethod.GET )
public String GetAbout(ModelMap model, HttpServletRequest httpRequest) {
return "/about";
}
@RequestMapping(value="/contact", method = RequestMethod.GET )
public String GetContact(ModelMap model, HttpServletRequest httpRequest) {
return "/contact";
}
}

Просмотреть файл

@ -0,0 +1,168 @@
/*******************************************************************************
* Copyright © Microsoft Open Technologies, Inc.
*
* All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
* OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
* ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
* PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for the specific language
* governing permissions and limitations under the License.
******************************************************************************/
package com.microsoft.aad.oidcpoc;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import org.json.JSONException;
import org.json.JSONObject;
/**
* This is Helper class for all RestClient class.
*
* @author Azure Active Directory Contributor
*
*/
public class HttpClientHelper {
public HttpClientHelper() {
super();
}
public static String getResponseStringFromConn(HttpURLConnection conn, boolean isSuccess) throws IOException {
BufferedReader reader = null;
if (isSuccess) {
reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
} else {
reader = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
}
StringBuffer stringBuffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
stringBuffer.append(line);
}
return stringBuffer.toString();
}
public static String getResponseStringFromConn(HttpURLConnection conn, String payLoad) throws IOException {
// Send the http message payload to the server.
if (payLoad != null) {
conn.setDoOutput(true);
OutputStreamWriter osw = new OutputStreamWriter(conn.getOutputStream());
osw.write(payLoad);
osw.flush();
osw.close();
}
// Get the message response from the server.
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = "";
StringBuffer stringBuffer = new StringBuffer();
while ((line = br.readLine()) != null) {
stringBuffer.append(line);
}
br.close();
return stringBuffer.toString();
}
public static byte[] getByteaArrayFromConn(HttpURLConnection conn, boolean isSuccess) throws IOException {
InputStream is = conn.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int bytesRead = 0;
while ((bytesRead = is.read(buff, 0, buff.length)) != -1) {
baos.write(buff, 0, bytesRead);
}
byte[] bytes = baos.toByteArray();
baos.close();
return bytes;
}
/**
* for bad response, whose responseCode is not 200 level
*
* @param responseCode
* @param errorCode
* @param errorMsg
* @return
* @throws JSONException
*/
public static JSONObject processResponse(int responseCode, String errorCode, String errorMsg) throws JSONException {
JSONObject response = new JSONObject();
response.put("responseCode", responseCode);
response.put("errorCode", errorCode);
response.put("errorMsg", errorMsg);
return response;
}
/**
* for bad response, whose responseCode is not 200 level
*
* @param responseCode
* @param errorCode
* @param errorMsg
* @return
* @throws JSONException
*/
public static JSONObject processGoodRespStr(int responseCode, String goodRespStr) throws JSONException {
JSONObject response = new JSONObject();
response.put("responseCode", responseCode);
if (goodRespStr.equalsIgnoreCase("")) {
response.put("responseMsg", "");
} else {
response.put("responseMsg", new JSONObject(goodRespStr));
}
return response;
}
/**
* for good response
*
* @param responseCode
* @param responseMsg
* @return
* @throws JSONException
*/
public static JSONObject processBadRespStr(int responseCode, String responseMsg) throws JSONException {
JSONObject response = new JSONObject();
response.put("responseCode", responseCode);
if (responseMsg.equalsIgnoreCase("")) { // good response is empty string
response.put("responseMsg", "");
} else { // bad response is json string
JSONObject errorObject = new JSONObject(responseMsg).optJSONObject("odata.error");
String errorCode = errorObject.optString("code");
String errorMsg = errorObject.optJSONObject("message").optString("value");
response.put("responseCode", responseCode);
response.put("errorCode", errorCode);
response.put("errorMsg", errorMsg);
}
return response;
}
}

Просмотреть файл

@ -0,0 +1,235 @@
/*******************************************************************************
* Copyright © Microsoft Open Technologies, Inc.
*
* All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
* OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
* ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
* PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for the specific language
* governing permissions and limitations under the License.
******************************************************************************/
package com.microsoft.aad.oidcpoc;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.text.WordUtils;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* This class provides the methods to parse JSON Data from a JSON Formatted
* String.
*
* @author Azure Active Directory Contributor
*
*/
public class JSONHelper {
private static Logger logger = Logger.getLogger(JSONHelper.class);
JSONHelper() {
// PropertyConfigurator.configure("log4j.properties");
}
/**
* This method parses an JSON Array out of a collection of JSON Objects
* within a string.
*
* @param jSonData
* The JSON String that holds the collection.
* @return An JSON Array that would contains all the collection object.
* @throws Exception
*/
public static JSONArray fetchDirectoryObjectJSONArray(JSONObject jsonObject) throws Exception {
JSONArray jsonArray = new JSONArray();
jsonArray = jsonObject.optJSONObject("responseMsg").optJSONArray("value");
return jsonArray;
}
/**
* This method parses an JSON Object out of a collection of JSON Objects
* within a string
*
* @param jsonObject
* @return An JSON Object that would contains the DirectoryObject.
* @throws Exception
*/
public static JSONObject fetchDirectoryObjectJSONObject(JSONObject jsonObject) throws Exception {
JSONObject jObj = new JSONObject();
jObj = jsonObject.optJSONObject("responseMsg");
return jObj;
}
/**
* This method parses the skip token from a json formatted string.
*
* @param jsonData
* The JSON Formatted String.
* @return The skipToken.
* @throws Exception
*/
public static String fetchNextSkiptoken(JSONObject jsonObject) throws Exception {
String skipToken = "";
// Parse the skip token out of the string.
skipToken = jsonObject.optJSONObject("responseMsg").optString("odata.nextLink");
if (!skipToken.equalsIgnoreCase("")) {
// Remove the unnecessary prefix from the skip token.
int index = skipToken.indexOf("$skiptoken=") + (new String("$skiptoken=")).length();
skipToken = skipToken.substring(index);
}
return skipToken;
}
/**
* @param jsonObject
* @return
* @throws Exception
*/
public static String fetchDeltaLink(JSONObject jsonObject) throws Exception {
String deltaLink = "";
// Parse the skip token out of the string.
deltaLink = jsonObject.optJSONObject("responseMsg").optString("aad.deltaLink");
if (deltaLink == null || deltaLink.length() == 0) {
deltaLink = jsonObject.optJSONObject("responseMsg").optString("aad.nextLink");
logger.info("deltaLink empty, nextLink ->" + deltaLink);
}
if (!deltaLink.equalsIgnoreCase("")) {
// Remove the unnecessary prefix from the skip token.
int index = deltaLink.indexOf("deltaLink=") + (new String("deltaLink=")).length();
deltaLink = deltaLink.substring(index);
}
return deltaLink;
}
/**
* This method would create a string consisting of a JSON document with all
* the necessary elements set from the HttpServletRequest request.
*
* @param request
* The HttpServletRequest
* @return the string containing the JSON document.
* @throws Exception
* If there is any error processing the request.
*/
public static String createJSONString(HttpServletRequest request, String controller) throws Exception {
JSONObject obj = new JSONObject();
try {
Field[] allFields = Class.forName(
"com.microsoft.windowsazure.activedirectory.sdk.graph.models." + controller).getDeclaredFields();
String[] allFieldStr = new String[allFields.length];
for (int i = 0; i < allFields.length; i++) {
allFieldStr[i] = allFields[i].getName();
}
List<String> allFieldStringList = Arrays.asList(allFieldStr);
Enumeration<String> fields = request.getParameterNames();
while (fields.hasMoreElements()) {
String fieldName = fields.nextElement();
String param = request.getParameter(fieldName);
if (allFieldStringList.contains(fieldName)) {
if (param == null || param.length() == 0) {
if (!fieldName.equalsIgnoreCase("password")) {
obj.put(fieldName, JSONObject.NULL);
}
} else {
if (fieldName.equalsIgnoreCase("password")) {
obj.put("passwordProfile", new JSONObject("{\"password\": \"" + param + "\"}"));
} else {
obj.put(fieldName, param);
}
}
}
}
} catch (JSONException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return obj.toString();
}
/**
*
* @param key
* @param value
* @return string format of this JSON obje
* @throws Exception
*/
public static String createJSONString(String key, String value) throws Exception {
JSONObject obj = new JSONObject();
try {
obj.put(key, value);
} catch (JSONException e) {
e.printStackTrace();
}
return obj.toString();
}
/**
* This is a generic method that copies the simple attribute values from an
* argument jsonObject to an argument generic object.
*
* @param jsonObject
* The jsonObject from where the attributes are to be copied.
* @param destObject
* The object where the attributes should be copied into.
* @throws Exception
* Throws a Exception when the operation are unsuccessful.
*/
public static <T> void convertJSONObjectToDirectoryObject(JSONObject jsonObject, T destObject) throws Exception {
// Get the list of all the field names.
Field[] fieldList = destObject.getClass().getDeclaredFields();
// For all the declared field.
for (int i = 0; i < fieldList.length; i++) {
// If the field is of type String, that is
// if it is a simple attribute.
if (fieldList[i].getType().equals(String.class)) {
// Invoke the corresponding set method of the destObject using
// the argument taken from the jsonObject.
destObject
.getClass()
.getMethod(String.format("set%s", WordUtils.capitalize(fieldList[i].getName())),
new Class[] { String.class })
.invoke(destObject, new Object[] { jsonObject.optString(fieldList[i].getName()) });
}
}
}
public static JSONArray joinJSONArrays(JSONArray a, JSONArray b) {
JSONArray comb = new JSONArray();
for (int i = 0; i < a.length(); i++) {
comb.put(a.optJSONObject(i));
}
for (int i = 0; i < b.length(); i++) {
comb.put(b.optJSONObject(i));
}
return comb;
}
}

Просмотреть файл

@ -0,0 +1,77 @@
package com.microsoft.aad.oidcpoc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.UserInfo;
@Controller
public class LoginController {
@RequestMapping(value = "/login", method = RequestMethod.GET )
public String GetLoginForm(ModelMap model, HttpServletRequest httpRequest) {
return "/login";
}
@RequestMapping(value = "/login", method = RequestMethod.POST )
public String Login(ModelMap model, HttpServletRequest httpRequest) throws Exception {
String userName = httpRequest.getParameter("j_username");
String password = httpRequest.getParameter("j_password");
if (AuthHelper.AuthLocalUser(httpRequest, userName, password) != null) {
return "redirect:/";
} else {
model.addAttribute("error", "Sorry, you are unauthorized. If you have previously linked your account to your work login, you will have to use that account going forward.");
return "/login";
}
}
@RequestMapping(value="/logout", method = RequestMethod.GET )
public String logout(ModelMap model, HttpServletRequest httpRequest) throws Exception {
AuthenticationResult res = AuthHelper.getAuthSessionObject(httpRequest);
HttpSession session = httpRequest.getSession();
String tenant = AuthHelper.getSetting(session.getServletContext(), "tenant");
session.invalidate();
String logoutUrl = "/";
if (res.getAccessTokenType() != AuthHelper.ACCESS_TOKEN_LOCAL) {
String replyUri = AuthHelper.GetReplyUri(httpRequest);
logoutUrl = String.format("https://login.microsoftonline.com/%s/oauth2/logout?post_logout_redirect_uri=%s", tenant, replyUri);
}
return "redirect:" + logoutUrl;
}
@RequestMapping(value="/secure/linkaccounts", method = RequestMethod.GET )
public String linkAccounts(ModelMap model, HttpServletRequest httpRequest) {
return "/secure/linkaccounts";
}
@RequestMapping(value="/secure/linkaccounts", method = RequestMethod.POST )
public String establishLink(ModelMap model, HttpServletRequest httpRequest) throws ClassNotFoundException, Exception {
String userName = httpRequest.getParameter("j_username");
String password = httpRequest.getParameter("j_password");
DBUser user = AuthHelper.AuthLocalUser(httpRequest, userName, password, false);
if (user != null) {
AuthenticationResult res = AuthHelper.getAuthSessionObject(httpRequest);
UserInfo info = res.getUserInfo();
String uniqueId = info.getUniqueId();
int userId = user.UserId;
AuthHelper.LinkAccounts(httpRequest, uniqueId, userId);
return "redirect:/";
} else {
model.addAttribute("error", "Sorry, we could not validate your account. Please try again.");
return "/secure/linkaccounts";
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
package com.microsoft.aad.oidcpoc;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class MyStuffController {
@RequestMapping(value="/secure/stuff", method = RequestMethod.GET )
public String GetIndex(ModelMap model, HttpServletRequest httpRequest) {
return "/secure/stuff";
}
}

Просмотреть файл

@ -0,0 +1,59 @@
package com.microsoft.aad.oidcpoc;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class PropertyReading implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext ctx = sce.getServletContext();
String configPropPath = ctx.getRealPath("/WEB-INF/config.properties");
String localPropPath = ctx.getRealPath("/WEB-INF/local.properties");
final Properties configProps = new Properties();
final Properties localProps = new Properties();
try {
configProps.load(new FileInputStream(configPropPath));
localProps.load(new FileInputStream(localPropPath));
} catch (final IOException e) {
e.printStackTrace();
}
Set<String> names = configProps.stringPropertyNames();
//Enumeration<String> name = ctx.getInitParameterNames();
String res=null;
for (String prop : names)
{
//check environment var (Azure)
res = System.getenv(prop);
if (res == null)
{
//check local.properties file (local dev)
res = localProps.getProperty(prop);
if (res == null)
{
//check web.xml (in case someone edits those defaults)
res = ctx.getInitParameter(prop);
}
}
if (res!=null) {
//put value in system property bag
System.setProperty(prop, res);
}
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO Auto-generated method stub
}
}

Просмотреть файл

@ -0,0 +1,6 @@
package com.microsoft.aad.oidcpoc;
public class SchemeDTO {
public String Scheme;
public int Port;
}

Просмотреть файл

@ -0,0 +1,533 @@
/*******************************************************************************
* Copyright © Microsoft Open Technologies, Inc.
*
* All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
* OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
* ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
* PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for the specific language
* governing permissions and limitations under the License.
******************************************************************************/
package com.microsoft.aad.oidcpoc;
import java.security.acl.Group;
import java.util.ArrayList;
//import javax.xml.bind.annotation.XmlRootElement;
import org.json.JSONObject;
/**
* The User Class holds together all the members of a WAAD User entity and all the access methods and set methods
* @author Azure Active Directory Contributor
*/
//@XmlRootElement
public class User extends DirectoryObject{
// The following are the individual private members of a User object that holds
// a particular simple attribute of an User object.
protected String objectId;
protected String objectType;
protected String accountEnabled;
protected String city;
protected String country;
protected String department;
protected String dirSyncEnabled;
protected String displayName;
protected String facsimileTelephoneNumber;
protected String givenName;
protected String jobTitle;
protected String lastDirSyncTime;
protected String mail;
protected String mailNickname;
protected String mobile;
protected String password;
protected String passwordPolicies;
protected String physicalDeliveryOfficeName;
protected String postalCode;
protected String preferredLanguage;
protected String state;
protected String streetAddress;
protected String surname;
protected String telephoneNumber;
protected String usageLocation;
protected String userPrincipalName;
protected boolean isDeleted; // this will move to dto
/**
* below 4 properties are for future use
*/
// managerDisplayname of this user
protected String managerDisplayname;
// The directReports holds a list of directReports
private ArrayList<User> directReports;
// The groups holds a list of group entity this user belongs to.
private ArrayList<Group> groups;
// The roles holds a list of role entity this user belongs to.
private ArrayList<Group> roles;
/**
* The constructor for the User class. Initializes the dynamic lists and managerDisplayname variables.
*/
public User(){
directReports = null;
groups = new ArrayList<Group>();
roles = new ArrayList<Group>();
managerDisplayname = null;
}
//
// public User(String displayName, String objectId){
// setDisplayName(displayName);
// setObjectId(objectId);
// }
//
// public User(String displayName, String objectId, String userPrincipalName, String accountEnabled){
// setDisplayName(displayName);
// setObjectId(objectId);
// setUserPrincipalName(userPrincipalName);
// setAccountEnabled(accountEnabled);
// }
//
/**
* @return The objectId of this user.
*/
public String getObjectId() {
return objectId;
}
/**
* @param objectId The objectId to set to this User object.
*/
public void setObjectId(String objectId) {
this.objectId = objectId;
}
/**
* @return The objectType of this User.
*/
public String getObjectType() {
return objectType;
}
/**
* @param objectType The objectType to set to this User object.
*/
public void setObjectType(String objectType) {
this.objectType = objectType;
}
/**
* @return The userPrincipalName of this User.
*/
public String getUserPrincipalName() {
return userPrincipalName;
}
/**
* @param userPrincipalName The userPrincipalName to set to this User object.
*/
public void setUserPrincipalName(String userPrincipalName) {
this.userPrincipalName = userPrincipalName;
}
/**
* @return The usageLocation of this User.
*/
public String getUsageLocation() {
return usageLocation;
}
/**
* @param usageLocation The usageLocation to set to this User object.
*/
public void setUsageLocation(String usageLocation) {
this.usageLocation = usageLocation;
}
/**
* @return The telephoneNumber of this User.
*/
public String getTelephoneNumber() {
return telephoneNumber;
}
/**
* @param telephoneNumber The telephoneNumber to set to this User object.
*/
public void setTelephoneNumber(String telephoneNumber) {
this.telephoneNumber = telephoneNumber;
}
/**
* @return The surname of this User.
*/
public String getSurname() {
return surname;
}
/**
* @param surname The surname to set to this User Object.
*/
public void setSurname(String surname) {
this.surname = surname;
}
/**
* @return The streetAddress of this User.
*/
public String getStreetAddress() {
return streetAddress;
}
/**
* @param streetAddress The streetAddress to set to this User.
*/
public void setStreetAddress(String streetAddress) {
this.streetAddress = streetAddress;
}
/**
* @return The state of this User.
*/
public String getState() {
return state;
}
/**
* @param state The state to set to this User object.
*/
public void setState(String state) {
this.state = state;
}
/**
* @return The preferredLanguage of this User.
*/
public String getPreferredLanguage() {
return preferredLanguage;
}
/**
* @param preferredLanguage The preferredLanguage to set to this User.
*/
public void setPreferredLanguage(String preferredLanguage) {
this.preferredLanguage = preferredLanguage;
}
/**
* @return The postalCode of this User.
*/
public String getPostalCode() {
return postalCode;
}
/**
* @param postalCode The postalCode to set to this User.
*/
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
/**
* @return The physicalDeliveryOfficeName of this User.
*/
public String getPhysicalDeliveryOfficeName() {
return physicalDeliveryOfficeName;
}
/**
* @param physicalDeliveryOfficeName The physicalDeliveryOfficeName to set to this User Object.
*/
public void setPhysicalDeliveryOfficeName(String physicalDeliveryOfficeName) {
this.physicalDeliveryOfficeName = physicalDeliveryOfficeName;
}
/**
* @return The passwordPolicies of this User.
*/
public String getPasswordPolicies() {
return passwordPolicies;
}
/**
* @param passwordPolicies The passwordPolicies to set to this User object.
*/
public void setPasswordPolicies(String passwordPolicies) {
this.passwordPolicies = passwordPolicies;
}
/**
* @return The mobile of this User.
*/
public String getMobile() {
return mobile;
}
/**
* @param mobile The mobile to set to this User object.
*/
public void setMobile(String mobile) {
this.mobile = mobile;
}
/**
* @return The Password of this User.
*/
public String getPassword() {
return password;
}
/**
* @param password The mobile to set to this User object.
*/
public void setPassword(String password) {
this.password = password;
}
/**
* @return The mail of this User.
*/
public String getMail() {
return mail;
}
/**
* @param mail The mail to set to this User object.
*/
public void setMail(String mail) {
this.mail = mail;
}
/**
* @return The MailNickname of this User.
*/
public String getMailNickname() {
return mailNickname;
}
/**
* @param mail The MailNickname to set to this User object.
*/
public void setMailNickname(String mailNickname) {
this.mailNickname = mailNickname;
}
/**
* @return The jobTitle of this User.
*/
public String getJobTitle() {
return jobTitle;
}
/**
* @param jobTitle The jobTitle to set to this User Object.
*/
public void setJobTitle(String jobTitle) {
this.jobTitle = jobTitle;
}
/**
* @return The givenName of this User.
*/
public String getGivenName() {
return givenName;
}
/**
* @param givenName The givenName to set to this User.
*/
public void setGivenName(String givenName) {
this.givenName = givenName;
}
/**
* @return The facsimileTelephoneNumber of this User.
*/
public String getFacsimileTelephoneNumber() {
return facsimileTelephoneNumber;
}
/**
* @param facsimileTelephoneNumber The facsimileTelephoneNumber to set to this User Object.
*/
public void setFacsimileTelephoneNumber(String facsimileTelephoneNumber) {
this.facsimileTelephoneNumber = facsimileTelephoneNumber;
}
/**
* @return The displayName of this User.
*/
public String getDisplayName() {
return displayName;
}
/**
* @param displayName The displayName to set to this User Object.
*/
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
/**
* @return The dirSyncEnabled of this User.
*/
public String getDirSyncEnabled() {
return dirSyncEnabled;
}
/**
* @param dirSyncEnabled The dirSyncEnabled to set to this User.
*/
public void setDirSyncEnabled(String dirSyncEnabled) {
this.dirSyncEnabled = dirSyncEnabled;
}
/**
* @return The department of this User.
*/
public String getDepartment() {
return department;
}
/**
* @param department The department to set to this User.
*/
public void setDepartment(String department) {
this.department = department;
}
/**
* @return The lastDirSyncTime of this User.
*/
public String getLastDirSyncTime() {
return lastDirSyncTime;
}
/**
* @param lastDirSyncTime The lastDirSyncTime to set to this User.
*/
public void setLastDirSyncTime(String lastDirSyncTime) {
this.lastDirSyncTime = lastDirSyncTime;
}
/**
* @return The country of this User.
*/
public String getCountry() {
return country;
}
/**
* @param country The country to set to this User.
*/
public void setCountry(String country) {
this.country = country;
}
/**
* @return The city of this User.
*/
public String getCity() {
return city;
}
/**
* @param city The city to set to this User.
*/
public void setCity(String city) {
this.city = city;
}
/**
* @return The accountEnabled attribute of this User.
*/
public String getAccountEnabled() {
return accountEnabled;
}
/**
* @param accountEnabled The accountEnabled to set to this User.
*/
public void setAccountEnabled(String accountEnabled) {
this.accountEnabled = accountEnabled;
}
public boolean isIsDeleted() {
return this.isDeleted;
}
public void setIsDeleted(boolean isDeleted) {
this.isDeleted = isDeleted;
}
@Override
public String toString() {
return new JSONObject(this).toString();
}
public String getManagerDisplayname(){
return managerDisplayname;
}
public void setManagerDisplayname(String managerDisplayname){
this.managerDisplayname = managerDisplayname;
}
}
/**
* The Class DirectReports Holds the essential data for a single DirectReport entry. Namely,
* it holds the displayName and the objectId of the direct entry. Furthermore, it provides the
* access methods to set or get the displayName and the ObjectId of this entry.
*/
//class DirectReport extends User{
//
// private String displayName;
// private String objectId;
//
// /**
// * Two arguments Constructor for the DirectReport Class.
// * @param displayName
// * @param objectId
// */
// public DirectReport(String displayName, String objectId){
// this.displayName = displayName;
// this.objectId = objectId;
// }
//
// /**
// * @return The diaplayName of this direct report entry.
// */
// public String getDisplayName() {
// return displayName;
// }
//
//
// /**
// * @return The objectId of this direct report entry.
// */
// public String getObjectId() {
// return objectId;
// }
//
//}

Просмотреть файл

@ -0,0 +1,173 @@
package com.microsoft.aad.oidcpoc;
import java.io.IOException;
import org.apache.log4j.Logger;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import org.json.JSONException;
import org.json.JSONObject;
import java.sql.DriverManager;
public class db {
String hostName;
String dbName;
String user;
String password;
String url = String.format("jdbc:sqlserver://%s:1433;database=%s;user=%s;password=%s;encrypt=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;", hostName, dbName, user, password);
Connection connection = null;
private static Logger logger = Logger.getLogger(db.class);
public db(String host, String db, String msiClientId) throws Exception {
hostName = host;
dbName = db;
url = String.format("jdbc:sqlserver://%s:1433;database=%s;authentication=ActiveDirectoryMSI;msiClientId=%s;encrypt=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;", hostName, dbName, msiClientId);
try {
connection = DriverManager.getConnection(url);
logger.info("Connecting DB via MSI in connection string");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public db(String host, String db, String msiEndpoint, String msiSecret, String msiClientId) throws Exception {
hostName = host;
dbName = db;
url = String.format("jdbc:sqlserver://%s:1433;database=%s;authentication=NotSpecified;encrypt=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;", hostName, dbName, msiClientId);
Properties info = new Properties();
String value = getAccessToken(msiEndpoint, msiSecret);
info.setProperty("accessToken", value);
try {
connection = DriverManager.getConnection(url, info);
logger.info("Connecting DB via MSI access token in connection property");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public db(String host, String db, String usr, String pw) throws Exception {
hostName = host;
dbName = db;
user = usr;
password = pw;
url = String.format("jdbc:sqlserver://%s:1433;database=%s;user=%s;password=%s;encrypt=true;hostNameInCertificate=*.database.windows.net;loginTimeout=30;", hostName, dbName, user, password);
try {
connection = DriverManager.getConnection(url);
logger.info("Connecting DB via sql username and password");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private String getAccessToken(String msiEndpoint, String msiSecret) throws IOException, JSONException {
msiEndpoint += "?resource=https://vault.azure.net&api-version=2017-09-01";
URL url = new URL(msiEndpoint);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setRequestProperty("secret", msiSecret);
connection.setRequestMethod("GET");
connection.connect();
int responseCode = connection.getResponseCode();
String response = HttpClientHelper.getResponseStringFromConn(connection, (responseCode==200));
JSONObject res = new JSONObject(response);
return res.getString("access_token");
}
public DBUser LinkAccounts(int UserId, String uniqueId) throws SQLException {
//user hasn't had their AAD and DB accounts linked
//NOTE: as we are linking, we want to disable the user from logging in using local
//credentials in the future - so we will randomize their password now.
String rndpw = generateRandomHexToken(20).substring(0, 19);
String updsql = "UPDATE [User] SET UniqueId = ?, Password = ? WHERE UserId = ?";
PreparedStatement pstmt = connection.prepareStatement(updsql);
pstmt.setString(1, uniqueId);
pstmt.setString(2, rndpw);
pstmt.setInt(3, UserId);
pstmt.executeUpdate();
return GetUser(uniqueId);
}
private static String generateRandomHexToken(int byteLength) {
//https://stackoverflow.com/questions/41107/how-to-generate-a-random-alpha-numeric-string#44227131
SecureRandom secureRandom = new SecureRandom();
byte[] token = new byte[byteLength];
secureRandom.nextBytes(token);
return new BigInteger(1, token).toString(16); //hex encoding
}
public DBUser GetUser(String uniqueId) {
DBUser res = null;
try {
String selectSql = "SELECT * FROM [User] WHERE UniqueId = ?";
PreparedStatement pstmt = connection.prepareStatement(selectSql);
pstmt.setString(1, uniqueId);
ResultSet resultSet = pstmt.executeQuery();
if (resultSet.next()) {
//we have a match
res = new DBUser();
res.UserId = resultSet.getInt("UserId");
res.Email = resultSet.getNString("Email");
res.FName = resultSet.getNString("FName");
res.LName = resultSet.getNString("LName");
res.UniqueId = resultSet.getNString("UniqueId");
}
if (resultSet.next()) {
//sanity check - we had more than one match
res = null;
}
connection.close();
}
catch (Exception e) {
e.printStackTrace();
}
return res;
}
public DBUser ValidateUser(String userName, String password) {
DBUser res = null;
try {
String selectSql = "SELECT * FROM [User] WHERE Email = ? AND Password = ?";
PreparedStatement pstmt = connection.prepareStatement(selectSql);
pstmt.setString(1, userName);
pstmt.setString(2, password);
ResultSet resultSet = pstmt.executeQuery();
if (resultSet.next()) {
//we have a match
res = new DBUser();
res.UserId = resultSet.getInt("UserId");
res.Email = resultSet.getNString("Email");
res.FName = resultSet.getNString("FName");
res.LName = resultSet.getNString("LName");
res.UniqueId = resultSet.getNString("UniqueId");
}
if (resultSet.next()) {
//we had more than one match
res = null;
}
connection.close();
}
catch (Exception e) {
e.printStackTrace();
}
return res;
}
}

12
src/main/main.iml Normal file
Просмотреть файл

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/java" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="lib" level="project" />
</component>
</module>

Просмотреть файл

@ -0,0 +1,16 @@
# Root logger option
log4j.rootLogger=DEBUG, file, stdout
# Direct log messages to a log file
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=logging.log
log4j.appender.file.MaxFileSize=1MB
log4j.appender.file.MaxBackupIndex=1
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

Просмотреть файл

@ -0,0 +1,4 @@
Manifest-Version: 1.0
Build-Jdk: 1.6.0_38
Created-By: Maven Integration for Eclipse

Просмотреть файл

@ -0,0 +1,6 @@
#Generated by Maven Integration for Eclipse
#Fri Nov 15 12:34:22 PST 2013
version=0.0.1-SNAPSHOT
groupId=com.microsoft.aad
m2e.projectName=oidcpoc
artifactId=oidcpoc

Просмотреть файл

@ -0,0 +1,97 @@
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.aad</groupId>
<artifactId>oidcpoc</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>oidcpoc</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>com.microsoft.windowsazure.activedirectory.sdk.graph</groupId>
<artifactId>WindowsAzure-ActiveDirectory-SDK-Graph</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>15.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
<version>2.13</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>2.20</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3</version>
</dependency>
</dependencies>
<build>
<finalName>java-webapp-oidc-migrate-poc</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warName>${project.artifactId}</warName>
<source>${project.basedir}\src</source>
<target>${maven.compiler.target}</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>install</id>
<phase>install</phase>
<goals>
<goal>sources</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.5</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>

Просмотреть файл

@ -0,0 +1,12 @@
#Tue Apr 17 16:26:00 CDT 2018
authority=https://login.microsoftonline.com/
tenant=[your aad tenant]
db_host=javatestdb.database.windows.net
db_name=javatest
db_user=java_app
db_password=[db account password]
client_id=[aad client id]
secret_key=[aad secret]
require_ssl=false
is_b2c=false
policy_susi=null

Просмотреть файл

@ -0,0 +1,15 @@
#Tue Apr 17 16:26:00 CDT 2018
authority=https://login.microsoftonline.com/
tenant=common
db_host=[database FQDN]
db_name=[database name]
db_user=[database app user]
db_password=[database user password]
client_id=[Azure AD App ID]
secret_key=[Azure AD App secret]
require_ssl=false
is_b2c=true
policy_susi=B2C1_susi_1
MSI_ENDPOINT=null
MSI_SECRET=null
MSIAppId=ae6a8a82-cd58-4307-816d-b3f5272fe3b1

Просмотреть файл

@ -0,0 +1,27 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc = "http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.microsoft.aad.oidcpoc" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<mvc:resources mapping = "/resources/**" location = "/WEB-INF/resources/" />
<mvc:annotation-driven/>
</beans>

Просмотреть файл

@ -0,0 +1,180 @@
body {
padding-top: 70px;
padding-bottom: 20px;
}
/* Set padding to keep content from hitting the edges */
.body-content {
padding-left: 15px;
padding-right: 15px;
}
/* Override the default bootstrap behavior where horizontal description lists
will truncate terms that are too long to fit in the left column
*/
.dl-horizontal dt {
white-space: normal;
}
/* Set width on the form input elements since they're 100% wide by default */
input,
label,
select,
textarea {
max-width: none;
vertical-align: middle;
}
td,tr {
padding: 5px;
}
ul.dropdown-menu span.glyphicon {
margin-right:8px;
}
ul.dropdown-menu li a {
padding:6px 40px 6px 13px;
}
div.form-group button.btn, div.form-group a.btn, div.form-group input.btn {
width: 140px;
}
div.notes {
font-size:.9em;
display:none;
}
label {
cursor:pointer;
}
.glyphicon.glyphicon-aad-true {
background-image: url('/content/images/AADIcon_18.png');
background-repeat:no-repeat;
display:inline-block;
width:18px;
height:18px;
}
.glyphicon.glyphicon-aad-false {
background-image: url('/content/images/AADIcon_18_bw.png');
background-repeat:no-repeat;
display:inline-block;
width:18px;
height:18px;
}
.bg-rowEdit {
color: #4a4a4a;
background-color: #fff;
}
.bg-rowEdit:hover,
.bg-rowEdit:focus {
background-color: #cecccc;
}
span.helpIcon {
background-image: url(/Content/images/help.png);
background-repeat: no-repeat;
display:inline-block;
width:10px;
height:10px;
cursor:pointer;
}
label.addHelp:after{
content:"";
padding: 2px!important;
margin-left:2px;
display:inline-block;
width:10px;
height:10px;
position: relative;
cursor:pointer;
background-image: url(/Content/images/help.png);
background-repeat: no-repeat;
}
.tooltip .tooltip-inner, .tooltip .tooltip-arrow {
border-top-color: #5a5a5a;
}
.tooltip-inner {
padding:8px;
max-width:500px;
text-align:left;
}
button.btn.control {
width:80px!important;
font-size:.9em;
}
.form-group.control {
border-bottom:1px solid #cacaca;
margin-top:-10px;
padding-bottom:8px;
}
span.form-control label.addHelp {
padding-top:2px!important;
}
.glyphicon-refresh-animate {
-animation: spin .7s infinite linear;
-webkit-animation: spin2 .7s infinite linear;
}
@-webkit-keyframes spin2 {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
}
}
.ui-loader {
font-size: 1.3em;
padding-top:0;
padding-left:0;
margin-top:15px;
margin-right:-6px;
display:inline-block;
color:#efefef;
float:right;
}
#collapsedLoader {
visibility:hidden;
}
#expandedLoader {
visibility:hidden;
display:inline-block;
}
@media (max-width: 768px) {
#loaderWrapper {
position: absolute !important;
top: 0;
margin-top:-20px;
display:block!important;
right: 75px;
}
#collapsedLoader {
display: inline-block;
margin-right:10px;
visibility:visible;
}
#expandedLoader {
display: none;
visibility:hidden;
}
}
@media (min-width: 769px) {
#collapsedLoader {
display: none;
visibility: hidden;
}
#expandedLoader {
display: inline-block;
visibility: visible;
}
}
.loginForm .row {
margin-top:18px;
}

Просмотреть файл

@ -0,0 +1,90 @@
<?xml version="1.0"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>authority</param-name>
<param-value>https://login.microsoftonline.com/</param-value>
</context-param>
<context-param>
<param-name>tenant</param-name>
<param-value>common</param-value>
</context-param>
<context-param>
<param-name>require_ssl</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>is_b2c</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>db_host</param-name>
<param-value>[fqdn to sql server, like dbserver.example.com]</param-value>
</context-param>
<context-param>
<param-name>db_name</param-name>
<param-value>[name of db on sql server]</param-value>
</context-param>
<context-param>
<param-name>db_user</param-name>
<param-value>[app service account name on sql]</param-value>
</context-param>
<context-param>
<param-name>db_password</param-name>
<param-value>[app service account pw on sql]</param-value>
</context-param>
<filter>
<filter-name>BasicFilter</filter-name>
<filter-class>com.microsoft.aad.oidcpoc.BasicFilter</filter-class>
<init-param>
<param-name>client_id</param-name>
<param-value>[Azure AD application ID]</param-value>
</init-param>
<init-param>
<param-name>secret_key</param-name>
<param-value>[Azure AD application api key]</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>BasicFilter</filter-name>
<url-pattern>/secure/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>CoreFilter</filter-name>
<filter-class>com.microsoft.aad.oidcpoc.CoreFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CoreFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>com.microsoft.aad.oidcpoc.PropertyReading</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>

Просмотреть файл

@ -0,0 +1,4 @@
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,58 @@
<%@ page import = "com.microsoft.aad.oidcpoc.*"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Auth Test</title>
<%
//forcing SSL here to work around Azure app service java hosting
//https://stackoverflow.com/questions/30371122/azure-web-app-not-passing-https-scheme-to-java-web-application
Boolean requireSsl = Boolean.valueOf(request.getSession().getServletContext().getInitParameter("require_ssl"));
if (requireSsl) { %>
<script type="text/javascript">
if (location.protocol == "http:")
location.href = "https://" + location.host + location.pathname + location.search + location.hash;
</script>
<% } %>
<link rel="shortcut icon" href="https://appservice.azureedge.net/images/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="//ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.css"></link>
<link rel="stylesheet" href="//ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap-theme.css"></link>
<link rel="stylesheet" href="/resources/site.css"></link>
<script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-3.3.1.js"></script>
<script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.js"></script>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">AAD Auth Test</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/about">About</a>
<li><a href="/contact">Contact</a>
<% if (AuthHelper.isAuthenticated(request)) { %>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Admin <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/secure/stuff">My Stuff</a></li>
</ul>
</li>
<% } %>
</ul>
<%@ include file = "_login.jsp" %>
</div>
</div>
</div>
<div class="container body-content">

Просмотреть файл

@ -0,0 +1,25 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ page import = "com.microsoft.aad.oidcpoc.*"%>
<% if (AuthHelper.isAuthenticated(request)) {
String userName = AuthHelper.getPrincipalName(request);
if (userName == null) userName = "N/A";
%>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><%= userName %> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/secure/profile">View Profile</a></li>
</ul>
</li>
<li>
<a href="/logout">Sign out</a>
</li>
</ul>
<% } else { %>
<ul class="nav navbar-nav navbar-right">
<li><a href="/login">Sign In</a></li>
</ul>
<% } %>

Просмотреть файл

@ -0,0 +1,20 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<p>Login using a local account</p>
<form method = "POST">
<div class="loginForm">
<div class="row">
<div class="col-sm-3">Login</div>
<div class="col-sm-9">
<input class="form-control" type = "text" name="j_username" id="j_username"></div>
</div>
<div class="row">
<div class="col-sm-3">Password</div>
<div class="col-sm-9"><input class="form-control" type = "password" name="j_password" id="j_password"></div>
</div>
<div class="row">
<div class="col-sm-12"><input class="btn btn-primary pull-right" type = "submit" value = "Login"></div>
</div>
</div>
</form>

40
src/main/webapp/about.jsp Normal file
Просмотреть файл

@ -0,0 +1,40 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ include file = "/_header.jsp" %>
<div class="panel panel-primary" style="margin-top:20px;">
<div class="panel-title panel-heading">
About
</div>
<div class="panel-body">
<div class="col-sm-12">
<p>This proof of concept builds on the OIDC Java sample hosted at <a target="_blank" href="https://github.com/Azure-Samples/active-directory-java-webapp-openidconnect">https://github.com/Azure-Samples/active-directory-java-webapp-openidconnect</a>.
The purpose is to demonstrate a pattern (using the adal4j library) for integrating Azure AD authentication with a
legacy authentication system, and illustrating one approach to migrating users from the legacy auth system over to
Azure AD.</p>
<p>IMPORTANT: the "legacy auth system" articulated here is in NO WAY meant to model a best practice for legacy
authentication. The database table, hosted in an Azure SQL database, includes a CLEAR TEXT password. Surprisingly,
there are still systems running today using such a process but it is unwise in the extreme and should be considered
an anti-pattern.</p>
</div>
<div class="col-sm-12">
<h4>Usage</h4>
<ul>
<li>Local users are listed in the [User] table in the database. Log in using a name and password to continue
working using a local account. (Note: if you have deployed this from scratch, you will need to populate this table manually.)</li>
<li>Click "Azure Active Directory" to login using an Azure AD work account. On first login, a consent form will display
ensuring the user can authorize the app for their own tenant.</li>
<li>After authenticating to Azure AD, the user is returned to the web app. The app will notice that the user hasn't linked
their Azure AD account to a local account and will prompt them to do so.</li>
<li>The user will login a final time using their legacy, local credentials.</li>
<li>This app will update the local database record with the unique ID from Azure AD. It will then scramble the password on the
local account so the user can't login there again.</li>
<li>Going forward, the user will authenticate to Azure AD but still have all the relevant information available in their session
from their local user account.</li>
</ul>
</div>
</div>
</div>
<%@ include file = "/_footer.jsp" %>

Просмотреть файл

@ -0,0 +1,19 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ include file = "/_header.jsp" %>
<address>
One Microsoft Way<br />
Redmond, WA 98052-6399<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br />
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>
<%@ include file = "/_footer.jsp" %>

Просмотреть файл

@ -0,0 +1,9 @@
<%@ include file = "_header.jsp" %>
<h2>ERROR PAGE!</h2>
<p>
Exception -
<%=request.getAttribute("error")%></p>
<ul>
<li><a href="<%=request.getContextPath()%>/index.jsp">Go Home</a></li>
</ul>
<%@ include file = "_footer.jsp" %>

30
src/main/webapp/index.jsp Normal file
Просмотреть файл

@ -0,0 +1,30 @@
<%@ page import = "com.microsoft.aad.oidcpoc.*"%>
<%@ include file = "_header.jsp" %>
<div class="jumbotron">
<h1>Azure Active Directory</h1>
<p><a href="https://docs.microsoft.com/en-us/azure/active-directory/" class="btn btn-primary btn-lg">Learn more &raquo;</a></p>
</div>
<div class="row">
<div class="col-md-4">
<h2>For Developers</h2>
<p>
Azure AD supports developers building both single-tenant, line-of-business (LOB) apps, as well as developers looking to develop multi-tenant apps.
</p>
<p><a class="btn btn-default" href="https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-developers-guide">Learn more &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>For Social and More</h2>
<p>Tutorials, references, and other documentation show you how to protect consumer-facing web and mobile applications with secure sign-in.</p>
<p><a class="btn btn-default" href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/">Learn more &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>For the Enterprise</h2>
<p>Azure AD Connect will integrate your on-premises directories with Azure Active Directory.</p>
<p><a class="btn btn-default" href="https://docs.microsoft.com/en-us/azure/active-directory/connect/active-directory-aadconnect">Learn more &raquo;</a></p>
</div>
</div>
<%@ include file = "_footer.jsp" %>

32
src/main/webapp/login.jsp Normal file
Просмотреть файл

@ -0,0 +1,32 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ include file = "_header.jsp" %>
<div class="panel panel-primary" style="margin-top:20px;">
<div class="panel-title panel-heading">
Login
</div>
<div class="panel-body">
<div class="col-sm-6">
<div class="alert alert-warning" style="display:none">${error}</div>
<%@ include file = "_loginForm.jsp" %>
</div>
<div class="col-sm-6" style="text-align:center;margin-top:30px;">
<p>Login using a work account</p>
<button class="btn btn-primary" id="btnAAD">Azure Active Directory</button>
</div>
</div>
</div>
<script type="text/javascript">
$(function() {
$("#btnAAD").on("click", function() {
location.href="/secure/aad";
});
if ($("div.alert.alert-warning").html().length>0) {
$("div.alert.alert-warning").css({"display":"block"});
}
$("#j_username").focus();
});
</script>
<%@ include file = "_footer.jsp" %>

Просмотреть файл

@ -0,0 +1,38 @@
<%@ include file = "/_header.jsp" %>
<h3>Current user</h3>
<table>
<tr>
<td>uniqueId:</td>
<td>${userInfo.uniqueId}</td>
</tr>
<tr>
<td>displayableId:</td>
<td>${userInfo.displayableId}</td>
</tr>
<tr>
<td>givenName:</td>
<td>${userInfo.givenName}</td>
</tr>
<tr>
<td>familyName:</td>
<td>${userInfo.familyName}</td>
</tr>
<tr>
<td>identityProvider:</td>
<td>${userInfo.identityProvider}</td>
</tr>
</table>
<br>
<ul>
<li><a href="<%=request.getContextPath()%>/secure/aad?cc=1">Get
new Access Token via Client Credentials</a></li>
</ul>
<ul>
<li><a href="<%=request.getContextPath()%>/secure/aad?refresh=1">Get
new Access Token via Refresh Token</a></li>
</ul>
<ul>
<li><a href="<%=request.getContextPath()%>/index.jsp">Go Home</a></li>
</ul>
<%@ include file = "/_footer.jsp" %>

Просмотреть файл

@ -0,0 +1,32 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ include file = "/_header.jsp" %>
<div class="panel panel-warning" style="margin-top:20px;">
<div class="panel-title panel-heading">
NOTICE: Link Accounts
</div>
<div class="panel-body">
<div class="col-sm-4">
<div class="alert alert-warning" style="display:none">${error}</div>
<%@ include file = "/_loginForm.jsp" %>
</div>
<div class="col-sm-4">
<p>You have successfully authenticated to your work account for the first time. We now need you to log in one more time
using your existing account so that the accounts will be linked. After this is completed, you will no longer be able to
log in using your old account.</p>
<p>Please login on the left.</p>
</div>
</div>
</div>
<script type="text/javascript">
$(function() {
if ($("div.alert.alert-warning").html().length>0) {
$("div.alert.alert-warning").css({"display":"block"});
}
$("#j_username").focus();
});
</script>
<%@ include file = "/_footer.jsp" %>

Просмотреть файл

@ -0,0 +1,69 @@
<%@ page import = "com.microsoft.aad.oidcpoc.*"%>
<%@ page import = "com.microsoft.aad.adal4j.*"%>
<%@ page import = "java.util.*"%>
<%@ include file = "/_header.jsp" %>
<div class="col-sm-4">
<h4>User Info</h4>
<%
DBUser user = AuthHelper.GetSessionProfile(request);
if (user==null){ %>
Your account has not been linked to the system yet.
<div>
<a href="/secure/linkaccounts">Link Accounts</a>
</div>
<% } else { %>
<table class="table">
<tr>
<td>UserID</td>
<td><%= user.UserId %></td>
</tr>
<tr>
<td>Email</td>
<td><%= user.Email %></td>
</tr>
<tr>
<td>UniqueId</td>
<td><%= user.UniqueId %></td>
</tr>
<tr>
<td>FName</td>
<td><%= user.FName %></td>
</tr>
<tr>
<td>LName</td>
<td><%= user.LName %></td>
</tr>
</table>
<% } %>
</div>
<div class="col-sm-4">
<h4>Claims Info</h4>
<%
AuthenticationResult res = AuthHelper.getAuthSessionObject(request);
if (res.getAccessTokenType() != AuthHelper.ACCESS_TOKEN_LOCAL) {
Map<String, Object> claims = AuthHelper.GetClaims(res.getIdToken()); %>
<table class="table">
<tr>
<th>Claim Name</th>
<th>Claim Value</th>
</tr>
<% for (Map.Entry<String, Object> claim : claims.entrySet()) { %>
<tr>
<td><%= claim.getKey() %></td>
<td><%= claim.getValue() %></td>
</tr>
<% } %>
</table>
<% } else { %>
You were authenticated locally and don't have any claims to display.
<% } %>
</div>
<%@ include file = "/_footer.jsp" %>

Просмотреть файл

@ -0,0 +1,12 @@
<%@ page import = "com.microsoft.aad.oidcpoc.*"%>
<%@ include file = "/_header.jsp" %>
<div class="row">
<div class="col-md-12">
Here's some stuff I can only see when I'm logged in.
</div>
</div>
<%@ include file = "/_footer.jsp" %>

Просмотреть файл

@ -0,0 +1,9 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ include file = "/_header.jsp" %>
Sorry, you are not authorized.
<%@ include file = "/_footer.jsp" %>