From 38a9e4ca2a191ba1fa9f2eec1b05f3d742775eaa Mon Sep 17 00:00:00 2001 From: Daniel Gerhardt <code@dgerhardt.net> Date: Wed, 30 Jan 2019 16:16:06 +0100 Subject: [PATCH] Add support for OpenID Connect authentication Only OIDC providers that support configuration discovery are supported. --- .../de/thm/arsnova/config/SecurityConfig.java | 28 ++++++++++++++++++- .../arsnova/controller/LoginController.java | 24 ++++++++++++++++ .../java/de/thm/arsnova/entities/User.java | 17 +++++++---- .../de/thm/arsnova/services/UserService.java | 6 ++-- src/main/resources/arsnova.properties.example | 11 ++++++++ src/test/resources/arsnova.properties.example | 11 ++++++++ 6 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/thm/arsnova/config/SecurityConfig.java b/src/main/java/de/thm/arsnova/config/SecurityConfig.java index b040e5efc..f871342c2 100644 --- a/src/main/java/de/thm/arsnova/config/SecurityConfig.java +++ b/src/main/java/de/thm/arsnova/config/SecurityConfig.java @@ -30,6 +30,7 @@ import org.pac4j.core.config.Config; import org.pac4j.oauth.client.FacebookClient; import org.pac4j.oauth.client.TwitterClient; import org.pac4j.oidc.client.GoogleOidcClient; +import org.pac4j.oidc.client.OidcClient; import org.pac4j.oidc.config.OidcConfiguration; import org.pac4j.springframework.security.web.CallbackFilter; import org.slf4j.Logger; @@ -89,6 +90,7 @@ import java.util.List; @Profile("!test") public class SecurityConfig extends WebSecurityConfigurerAdapter { private static final String OAUTH_CALLBACK_PATH_SUFFIX = "/auth/oauth_callback"; + private static final String OIDC_DISCOVERY_PATH_SUFFIX = "/.well-known/openid-configuration"; private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class); @Autowired @@ -111,6 +113,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Value("${security.cas.enabled}") private boolean casEnabled; @Value("${security.cas-server-url}") private String casUrl; + @Value("${security.oidc.enabled}") private boolean oidcEnabled; + @Value("${security.oidc.issuer}") private String oidcIssuer; + @Value("${security.oidc.client-id}") private String oidcClientId; + @Value("${security.oidc.secret}") private String oidcSecret; + @Value("${security.facebook.enabled}") private boolean facebookEnabled; @Value("${security.facebook.key}") private String facebookKey; @Value("${security.facebook.secret}") private String facebookSecret; @@ -142,7 +149,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { http.addFilter(casLogoutFilter()); } - if (facebookEnabled || googleEnabled || twitterEnabled) { + if (oidcEnabled || facebookEnabled || googleEnabled || twitterEnabled) { CallbackFilter callbackFilter = new CallbackFilter(oauthConfig()); callbackFilter.setSuffix(OAUTH_CALLBACK_PATH_SUFFIX); callbackFilter.setDefaultUrl(rootUrl + apiPath + "/"); @@ -165,6 +172,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { providers.add("cas"); auth.authenticationProvider(casAuthenticationProvider()); } + if (oidcEnabled) { + providers.add("oidc"); + } if (googleEnabled) { providers.add("google"); } @@ -376,6 +386,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public Config oauthConfig() { List<Client> clients = new ArrayList<>(); + if (oidcEnabled) { + clients.add(oidcClient()); + } if (facebookEnabled) { clients.add(facebookClient()); } @@ -389,6 +402,19 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { return new Config(rootUrl + apiPath + OAUTH_CALLBACK_PATH_SUFFIX, clients); } + @Bean + public OidcClient oidcClient() { + OidcConfiguration config = new OidcConfiguration(); + config.setDiscoveryURI(oidcIssuer + OIDC_DISCOVERY_PATH_SUFFIX); + config.setClientId(oidcClientId); + config.setSecret(oidcSecret); + config.setScope("openid"); + OidcClient client = new OidcClient(config); + client.setCallbackUrl(rootUrl + apiPath + OAUTH_CALLBACK_PATH_SUFFIX + "?client_name=OidcClient"); + + return client; + } + @Bean public FacebookClient facebookClient() { final FacebookClient client = new FacebookClient(facebookKey, facebookSecret); diff --git a/src/main/java/de/thm/arsnova/controller/LoginController.java b/src/main/java/de/thm/arsnova/controller/LoginController.java index a2c4f0902..8fcfb6d67 100644 --- a/src/main/java/de/thm/arsnova/controller/LoginController.java +++ b/src/main/java/de/thm/arsnova/controller/LoginController.java @@ -26,6 +26,7 @@ import org.pac4j.core.exception.HttpAction; import org.pac4j.oauth.client.FacebookClient; import org.pac4j.oauth.client.TwitterClient; import org.pac4j.oidc.client.GoogleOidcClient; +import org.pac4j.oidc.client.OidcClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -106,6 +107,12 @@ public class LoginController extends AbstractController { @Value("${security.cas.image:}") private String casImage; @Value("${security.cas.order}") private int casOrder; + @Value("${security.oidc.enabled}") private boolean oidcEnabled; + @Value("${security.oidc.allowed-roles:speaker,student}") private String[] oidcRoles; + @Value("${security.oidc.title:OIDC}") private String oidcTitle; + @Value("${security.oidc.image:}") private String oidcImage; + @Value("${security.oidc.order}") private int oidcOrder; + @Value("${security.facebook.enabled}") private boolean facebookEnabled; @Value("${security.facebook.allowed-roles:speaker,student}") private String[] facebookRoles; @Value("${security.facebook.order}") private int facebookOrder; @@ -124,6 +131,9 @@ public class LoginController extends AbstractController { @Autowired(required = false) private DaoAuthenticationProvider daoProvider; + @Autowired(required = false) + private OidcClient oidcClient; + @Autowired(required = false) private TwitterClient twitterClient; @@ -276,6 +286,9 @@ public class LoginController extends AbstractController { if (casEnabled && "cas".equals(type)) { casEntryPoint.commence(request, response, null); + } else if (oidcEnabled && "oidc".equals(type)) { + result = new RedirectView( + oidcClient.getRedirectAction(new J2EContext(request, response)).getLocation()); } else if (twitterEnabled && "twitter".equals(type)) { result = new RedirectView( twitterClient.getRedirectAction(new J2EContext(request, response)).getLocation()); @@ -379,6 +392,17 @@ public class LoginController extends AbstractController { services.add(sdesc); } + if (oidcEnabled) { + ServiceDescription sdesc = new ServiceDescription( + "oidc", + oidcTitle, + MessageFormat.format(dialogUrl, "oidc"), + oidcRoles + ); + sdesc.setOrder(oidcOrder); + services.add(sdesc); + } + if (facebookEnabled) { ServiceDescription sdesc = new ServiceDescription( "facebook", diff --git a/src/main/java/de/thm/arsnova/entities/User.java b/src/main/java/de/thm/arsnova/entities/User.java index 26bb83317..6d51da093 100644 --- a/src/main/java/de/thm/arsnova/entities/User.java +++ b/src/main/java/de/thm/arsnova/entities/User.java @@ -21,6 +21,7 @@ import de.thm.arsnova.services.UserSessionService; import org.jasig.cas.client.authentication.AttributePrincipal; import org.pac4j.oauth.profile.facebook.FacebookProfile; import org.pac4j.oauth.profile.twitter.TwitterProfile; +import org.pac4j.oidc.profile.OidcProfile; import org.pac4j.oidc.profile.google.GoogleOidcProfile; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -36,6 +37,7 @@ public class User implements Serializable { public static final String FACEBOOK = "facebook"; public static final String THM = "thm"; public static final String LDAP = "ldap"; + public static final String OIDC = "oidc"; public static final String ARSNOVA = "arsnova"; public static final String ANONYMOUS = "anonymous"; public static final String GUEST = "guest"; @@ -48,12 +50,17 @@ public class User implements Serializable { private UserSessionService.Role role; private boolean isAdmin; - public User(GoogleOidcProfile profile) { - if (!profile.getEmailVerified()) { - throw new IllegalArgumentException("Email is not verified."); + public User(OidcProfile profile) { + if (profile instanceof GoogleOidcProfile) { + if (!profile.getEmailVerified()) { + throw new IllegalArgumentException("Email is not verified."); + } + setUsername(profile.getEmail()); + setType(User.GOOGLE); + } else { + setUsername(User.OIDC + ":" + profile.getId()); + setType(User.OIDC); } - setUsername(profile.getEmail()); - setType(User.GOOGLE); } public User(TwitterProfile profile) { diff --git a/src/main/java/de/thm/arsnova/services/UserService.java b/src/main/java/de/thm/arsnova/services/UserService.java index f690cb434..d33734baf 100644 --- a/src/main/java/de/thm/arsnova/services/UserService.java +++ b/src/main/java/de/thm/arsnova/services/UserService.java @@ -32,7 +32,7 @@ import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.pac4j.oauth.profile.facebook.FacebookProfile; import org.pac4j.oauth.profile.twitter.TwitterProfile; -import org.pac4j.oidc.profile.google.GoogleOidcProfile; +import org.pac4j.oidc.profile.OidcProfile; import org.pac4j.springframework.security.authentication.Pac4jAuthenticationToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -257,8 +257,8 @@ public class UserService implements IUserService { private User getOAuthUser(final Authentication authentication) { User user = null; final Pac4jAuthenticationToken token = (Pac4jAuthenticationToken) authentication; - if (token.getProfile() instanceof GoogleOidcProfile) { - final GoogleOidcProfile profile = (GoogleOidcProfile) token.getProfile(); + if (token.getProfile() instanceof OidcProfile) { + final OidcProfile profile = (OidcProfile) token.getProfile(); user = new User(profile); } else if (token.getProfile() instanceof TwitterProfile) { final TwitterProfile profile = (TwitterProfile) token.getProfile(); diff --git a/src/main/resources/arsnova.properties.example b/src/main/resources/arsnova.properties.example index 93d8c87c9..51d64b6d2 100644 --- a/src/main/resources/arsnova.properties.example +++ b/src/main/resources/arsnova.properties.example @@ -142,6 +142,17 @@ security.cas.image= security.cas.order=0 security.cas-server-url=https://example.com/cas +# OpenID Connect authentication +# +security.oidc.enabled=false +security.oidc.allowed-roles=speaker,student +security.oidc.title=OIDC +security.oidc.image= +security.oidc.order=0 +security.oidc.issuer=https://example.com/oidc +security.oidc.client-id= +security.oidc.secret= + # OAuth authentication with third party services # Specific parameters: # key: OAuth key/id provided by a third party auth service diff --git a/src/test/resources/arsnova.properties.example b/src/test/resources/arsnova.properties.example index 93d8c87c9..51d64b6d2 100644 --- a/src/test/resources/arsnova.properties.example +++ b/src/test/resources/arsnova.properties.example @@ -142,6 +142,17 @@ security.cas.image= security.cas.order=0 security.cas-server-url=https://example.com/cas +# OpenID Connect authentication +# +security.oidc.enabled=false +security.oidc.allowed-roles=speaker,student +security.oidc.title=OIDC +security.oidc.image= +security.oidc.order=0 +security.oidc.issuer=https://example.com/oidc +security.oidc.client-id= +security.oidc.secret= + # OAuth authentication with third party services # Specific parameters: # key: OAuth key/id provided by a third party auth service -- GitLab