This is a refactored version of code from:
https://github.com/ucsb-cs56-pconrad/spring-boot-github-oauth-demo02
In this version, we take on some of the "TODO" items from demo02, including:
- Adding an application property for the he designated github org that defines privlege levels (instead of it being hard coded in the sy
CustomRoles.java
file. rsions: - Code to put a login/logout button in your navigation bar
- Code to put the username and user's avatar in your navigation bar
- Code to put an indication of the user's level of access in your navigation bar
- Code so that when showing the user various links to pages, we hide the links (or grey them out) if the user is not authorized for that function of the app
Now, let's walk through the details.
The configuration steps need to be followed VERY CAREFULLY or it will not work.
NOTE: *If you are running Java version 11 on your machine, you will have trouble with this version of the app. Instead, use this version: https://github.com/ucsb-cs56-f18/spring-boot-github-oauth-demo02-java11
You can check your java version via javac -version
-
If you want to be able to test on localhost with https, configure the key store.
- This is a self-signed certificate, and does NOT offer security; it is only for testing purposes
- When running with
https
with a self-signed certificate, you will likely get browser warnings that the site may be unsafe. It is ok to proceed to the site in spite of these warnings. - Run this, noting how to respond to the prompts below:
cd src/main/resources keytool -genkey -alias mydomain -keyalg RSA -keystore KeyStore.jks -keysize 2048
-
Use
password
as the password (or else change the hardcoded valuepassword
in the filesrc/main/resources/application.properties
-
For all the other values, you can just take the defaults (except you have to answer "yes" to the question where it asks you if the values are correct.)
-
-
Copy the file
localhost.json.SAMPLE
tolocalhost.json
- Double check that
localhost.json
is in your.gitignore
file. The reason there is alocalhost.json.SAMPLE
that does NOT have real client secrets in it is so that these won't accidentally leak to being stored in a github repo. - In the next step, you'll edit the values in this file.
- Double check that
-
Create a Github OAuth app to get the client-id and client-secret values, and put those values into
localhost.json
. To create a Github OAuth app:- Login to Github, and go to Settings under your personal account
- Navigate to Developer Settings (Or just go to: https://github.com/settings/developers)
- Click the "New OAuth App" button
- Give the app a name that matches your repo name, plus "test on localhost" (for example
GauchoTool test on localhost
). It is not required that this name match exactly, but you will want to be able to find it later to be sure that you are debugging the settings of the correct app - For
Homepage URL
enterhttps://127.0.0.1:8082
- For
Application description
, you may put in anything you want. - For
Authorization callback URL
you must put in this. Be sure it matches exactly, including upper/lower case:https://127.0.0.1:8082/callback?client_name=GitHubClient
- Click
Register Application
- Now you have the
Client ID
andClient Secret
values you need for the next step.
-
Carefully edit the
Client ID
andClient Secret
into thelocalhost.json
file. -
Run
. env.sh
- This defines the environment variable
SPRING_APPLICATION_JSON
which is an environment variable that can override application setting values in thesrc/main/resources/application.properties
file.
- This defines the environment variable
-
Run
mvn spring-boot:run
To run on heroku, you need to create a DIFFERENT OAuth app. That's because the callback URL is different when running on Heroku.
-
Create a heroku application. You can do this with the Heroku Dashboard online, or by doing
heroku login
and thenheroku create app-name
.If you choose
app-name
, then your app's url will behttps://app-name.herokuapp.com
You'll need that URL below. Everywhere you see
app-name.herokuapp.com
in these insructions, you must substitutee your actual application's name. -
Copy the file
heroku.json.SAMPLE
toheroku.json
- Double check that
heroku.json
is in your.gitignore
file.
- Double check that
-
Create a separate Github OAuth app to get the client-id and client-secret values, and put those values into
heroku.json
. To create a Github OAuth app:- Login to Github, and go to Settings under your personal account
- Navigate to Developer Settings (Or just go to: https://github.com/settings/developers)
- Click the "New OAuth App" button
- Give the app a name that matches your repo name, plus "on heroku" (for example
GauchoTool test on localhost
). It is not required that this name match exactly, but you will want to be able to find it later to be sure that you are debugging the settings of the correct app - For
Homepage URL
enterhttps://app-name.herokuapp.com
(substituting your REAL app name.) - For
Application description
, you may put in anything you want. - For
Authorization callback URL
you must put in this. Be sure it matches exactly, including upper/lower case:https://app-name.herokuapp.com/callback?client_name=GitHubClient
- Click
Register Application
- Now you have the
Client ID
andClient Secret
values you need for the next step.
-
Carefully edit the
Client ID
andClient Secret
into theheroku.json
file. -
Run
./setHerokuEnv.py --app appname
appname
should be your app name on Heroku (without the.herokuapp.com
part)- This is some Python code that reads from
heroku.json
and for each variable, runs the commandheroku config:set variable=value
-
As usual, set the heroku application name in the
pom.xml
(in the usual way). -
Run
mvn heroku:deploy
This is a demo of an application that illustrates github OAuth with spring boot. That is, it allows you to implement logging in with a username/password with a good tradeoff between effort and security; not much effort, very good security.
If you have an app that only PROVIDES information, and does not allow users to STORE information, then you might not need this.
If your app DOES allow users to STORE information in a database of any kind, then you DO need this. Having an app on the public web in which users can store information, without logging in or authenticating, presents a security risk. In particular:
- spammers may store all kinds of unsavory content in your database, including advertisements for illegal products, offensive material, etc.
- hackers may launch a "denial of service" attack by trying to just fill your database with content as quickly as possible.
The fact that your app requires not authentication may embolden the hackers, since there is no accountability.
- Requiring them to register with, for example, a github id, provides at least a moderate level of accountability.
- Requiring, for updating content, that their account be specifically approved by one of your group members (acting as an admin for the app) provides even more accountability.
The code in this app allows you to do exactly that, with minimum effort.
The code here allows you to delegate certain features to Github so that you don't have to implement them yourself:
-
That means that you don't have to implement the function of creating a user account. Someone that wants to use your account just creates a Github account and uses that to login.
-
You don't have to keep track of user's passwords. Github does that for you.
-
Unless you want to associate data with specific users (e.g. have user profiles, have user's "own" certain data within the application), you don't even need to keep track of users in a database. (If you want to associate data with users, you can do that, but you'll need to create your own table. You'll only store the username as the "key" for that table to lookup users, and then associate whatever data you want with each user.)
-
Without having to do much extra coding, you can establish four permission levels:
- Pages that anyone on the web can access without logging in
- Pages that only folks that have logged in with a github account can access
- Pages that only folks belonging to a certin github organization (one that you specify) can access. This allows you to control who can, and who cannot use those features in your webapp; users have to ask one of your organization admins to add them to the organization; and then they can access those pages.
- Pages that only folks that are
owners
(admin
users) in that organization can access.
That means that, at least when you are at the "minimum viable product" stage, the features of adding users to groups, deleting users from groups, and designating certain users as admin users, etc. can all be delegated to github; you don't have to build those into your web app.
There are lots of things you don't have to do (store passwords, set up account creation, set up a "forgot password" function, verifying user emails, etc.)
But there are some things you DO have to do.
-
You need to create an OAuth application under your github account, which sets up a
client_id
andclient_secret
, two hexadecimal numbers that are like a username/password combination that gives you permission to communicate with github's authentication servers. These pieces of data need to be treated carefully; in particular, they should NOT be stored in a github repo (even a private one). Instead, you store these pieces of information either in:- local files that are in your
.gitignore
- Typical names for these files are
app.json
,localhost.json
- Typical names for these files are
- in configuration variables that you set on the Heroku dashboard.
You set up a Github OAuth application by going to the settings menu for your account on Github, selecting
Developer Settings
from the menu at left (it's way down the page) and then choosingCreate OAuth App
. You'll need the URL of the application, and theCallback URL
. Those steps are discussed in more detail below. - local files that are in your
-
If you want to have multiple permission levels (i.e. more than just "logged in or not") then you need to create a github organization. You do this by selecting the
New Organization
button from the dropdown menu (you can also just visit: https://github.com/organizations/new.)NOTE: You need to remove third-party restrictions in order for this to work (or else figure out how to authorize your specific application). Go to https://github.com/organizations/ORG-NAME-HERE/settings/oauth_application_policy, substituting your organization's name in place of
ORG-NAME-HERE
-
You need to make sure that your application includes the
Pac4J
dependencies in thepom.xml
file, and thatPac4J
is configured properly.See the
pom.xml
file in this repo for thePac4J
related dependencies. -
You need to configure the Pac4J client.
The java source file that configures
Pac4J
with the values ofclient_id
andclient_secret
is inPac4JConfig.java
.This is also where we set up the OAuth scope, which tells Github (and the end user, when they first login to our app via Github), what information we are allowed to see about the user. The scope can restrict our access to the user's information to a very small amount of information, or at the other extreme, it can give us access to do anything and everything the user can do on github.com, i.e. actually be that user. When doing OAuth with Facebook or Google, scope works in a similar way; the scope, for example, controls whether or not an app that uses Facebook OAuth can post things to the User's feed on Facebook.
We also set up the callback url (in this case
/callback
) in this file.Finally, in this part of the code, we set up an
AuthorizationGenerator<GitHubProfile>
, which is a templated class that works with the custom roles that we set up in the fileCustomRoles.java
(i.e.ROLE_ADMIN
andROLE_MEMBER
.) -
You need some code that sets up the custom roles that you want to have in your application, and defines what it means to have those roles. That code is in
CustomRoles.java
.In this code, we set up two roles:
ROLE_MEMBER
for users that belong to our designated github organizationROLE_ADMIN
for users that haveowner
permission in our designated github organization
The two member functions
is_admin
andis_member
use the Github API to check for membership or owner status in the designated github org. -
You need a
CustomAuthorizer
, i.e. a class that extendsProfileAuthorizer<CommonProfile>
, which is an abstract class.
The single abstract method of that class is:
protected abstract boolean
isProfileAuthorized(WebContext context, U profile)
which is supposed to return true or false based on "whether a specific profile is authorized".
In this context, CommonProfile
means the information associated with
a given user that we have authenticated; and the CommonProfile
is
information we can get about a user regardless of which login method
that used (i.e. whether its Github, or Facebook, or Google, or
whatever.)
See:
Our code simply checks the values of the is_admin
and is_member
methods we defined in CustomRoles
- You need some code that sets up the URLs in your application with various levels of permission.
This is done in the file SecurityConfig.java
. More detail on that file appears later in this README.md
- https://github.com/pac4j/spring-webmvc-pac4j-boot-demo
- https://github.com/pconrad/try2-pac4j-sb-oauth
http://pconrad-webapps.github.io/topics/spring_boot_https/
This file simply allows us to customize the pages that show up when various kinds of errors occur, such as:
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error
There are many other errors, and it would not be reasonable to create custom pages for each one. (See, for example: https://www.restapitutorial.com/httpstatuscodes.html. We may not even need that here, and it might be better to remove this from the tutorial to simplify it. Maybe that goes better in its own tutorial.
In this file, we configure the url patterns that need to be authenticated
before they may be accessed. For example, in the method
addInterceptors
, we find this code:
SecurityIntercepter gh_admin =
new SecurityInterceptor(config, "GitHubClient", "admin");
registry.addInterceptor(gh_admin).addPathPatterns("/admin/*");
This sets up a security intercepter that requires us to be logged into github in an admin role. What "admin" role is defined in Pac4JConfig.java (which may not be the best place for it, as we've discussed.)
Similarly, we have a custom role defined and we can restrict certain URL patterns to that role as well, and finally restrict some pages to only be available to when users are logged in:
SecurityIntercepter gh_custom =
new SecurityInterceptor(config, "GitHubClient", "custom");
registry.addInterceptor(gh_admin).addPathPatterns("/custom/*");
SecurityIntercepter gh_loggedIn =
new SecurityInterceptor(config, "GitHubClient");
registry.addInterceptor(gh_admin).addPathPatterns("/github/*");
This file extends WebMvcConfigurerAdapter
WebMvcConfigurerAdapter
javadoc- import statement:
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
This is the main for our app, and it looks pretty much like every other Spring Boot webapp main file
This is the @Controller
and it just sets up the routes to the pages.
The callback url should apparently be defined this way:
FacebookClient facebookClient = new FacebookClient(fbKey, fbSecret);
TwitterClient twitterClient = new TwitterClient(twKey, twSecret);
Config config = new Config("http://localhost:8080/callback", facebookClient, twitterClient);
In that case, the callback URL of the FacebookClient is
http://localhost:8080/callback?client_name=FacebookClient
and the callback URL of the TwitterClient ishttp://localhost:8080/callback?client_name=TwitterClient
.
This spring-webmvc-pac4j-boot-demo
project is a Spring Boot application secured by the spring-webmvc-pac4j security library with various authentication mechanisms: Facebook, Twitter, form, basic auth, CAS, SAML, OpenID Connect, JWT...
You can build the project and run it on http://localhost:8080 using the following commands:
cd spring-webmvc-pac4j-boot-demo
mvn clean compile exec:java
For your tests, click on the "Protected url by xxx" link to start the login process with the xxx identity provider...