├── .gitignore ├── README.md ├── pom.xml ├── renovate.json └── src └── main ├── java └── org │ └── pac4j │ └── demo │ └── spring │ ├── Application.java │ ├── CustomAuthorizer.java │ ├── MyErrorController.java │ ├── Pac4jConfig.java │ ├── SecurityConfig.java │ └── SpringBootPac4jDemo.java └── resources ├── application.properties ├── metadata-okta.xml ├── samlKeystore.jks ├── templates ├── error401.html ├── error403.html ├── error500.html ├── form.html ├── index.html ├── jwt.html ├── notProtected.html └── protectedIndex.html └── testshib-providers.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings/ 4 | target/ 5 | test-output/ 6 | bin/ 7 | *.iml 8 | .idea 9 | sp-metadata.xml 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | This `spring-webmvc-pac4j-boot-demo` project is a Spring Boot application secured by the [spring-webmvc-pac4j](https://github.com/pac4j/spring-webmvc-pac4j) security library. 6 | 7 | ## Run and test 8 | 9 | You can build the project and run it on [http://localhost:8080](http://localhost:8080) using the following commands: 10 | 11 | cd spring-webmvc-pac4j-boot-demo 12 | mvn clean compile exec:java 13 | 14 | or 15 | 16 | cd spring-webmvc-pac4j-boot-demo 17 | mvn spring-boot:run 18 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | org.pac4j.demo 4 | spring-webmvc-pac4j-boot-demo 5 | 8.0.0-SNAPSHOT 6 | jar 7 | spring-webmvc-pac4j demo for Spring boot webapp 8 | 9 | 10 | 11 | sonatype-nexus-snapshots 12 | Sonatype Nexus Snapshots 13 | https://oss.sonatype.org/content/repositories/snapshots 14 | 15 | false 16 | 17 | 18 | true 19 | 20 | 21 | 22 | spring-milestone 23 | Spring Milestone Repository 24 | https://repo.spring.io/milestone 25 | 26 | 27 | 28 | 29 | spring-milestones 30 | https://repo.spring.io/milestone 31 | 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-parent 37 | 3.5.0 38 | 39 | 40 | 41 | 17 42 | 8.0.1 43 | 6.1.3 44 | 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-web 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-mustache 54 | 55 | 56 | org.pac4j 57 | spring-webmvc-pac4j 58 | ${spring-webmvc-pac4j.version} 59 | 60 | 61 | org.pac4j 62 | pac4j-jakartaee 63 | ${pac4j.version} 64 | 65 | 66 | org.pac4j 67 | pac4j-oauth 68 | ${pac4j.version} 69 | 70 | 71 | org.pac4j 72 | pac4j-cas 73 | ${pac4j.version} 74 | 75 | 76 | org.pac4j 77 | pac4j-saml 78 | ${pac4j.version} 79 | 80 | 81 | org.pac4j 82 | pac4j-gae 83 | ${pac4j.version} 84 | 85 | 86 | org.pac4j 87 | pac4j-oidc 88 | ${pac4j.version} 89 | 90 | 91 | org.pac4j 92 | pac4j-http 93 | ${pac4j.version} 94 | 95 | 96 | org.pac4j 97 | pac4j-ldap 98 | ${pac4j.version} 99 | 100 | 101 | org.pac4j 102 | pac4j-jwt 103 | ${pac4j.version} 104 | 105 | 106 | org.pac4j 107 | pac4j-sql 108 | ${pac4j.version} 109 | 110 | 111 | org.pac4j 112 | pac4j-mongo 113 | ${pac4j.version} 114 | 115 | 116 | org.pac4j 117 | pac4j-kerberos 118 | ${pac4j.version} 119 | 120 | 121 | org.pac4j 122 | pac4j-couch 123 | ${pac4j.version} 124 | 125 | 126 | 127 | ${project.artifactId} 128 | 129 | 130 | org.springframework.boot 131 | spring-boot-maven-plugin 132 | 133 | 134 | org.apache.maven.plugins 135 | maven-compiler-plugin 136 | 3.14.0 137 | 138 | ${java.version} 139 | UTF-8 140 | 141 | 142 | 143 | org.codehaus.mojo 144 | exec-maven-plugin 145 | 3.5.1 146 | 147 | 148 | 149 | java 150 | 151 | 152 | 153 | 154 | org.pac4j.demo.spring.SpringBootPac4jDemo 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/pac4j/demo/spring/Application.java: -------------------------------------------------------------------------------- 1 | package org.pac4j.demo.spring; 2 | 3 | import org.pac4j.core.client.Client; 4 | import org.pac4j.core.config.Config; 5 | import org.pac4j.core.context.CallContext; 6 | import org.pac4j.core.exception.http.HttpAction; 7 | import org.pac4j.core.profile.ProfileManager; 8 | import org.pac4j.core.profile.UserProfile; 9 | import org.pac4j.core.util.Pac4jConstants; 10 | import org.pac4j.http.client.indirect.FormClient; 11 | import org.pac4j.jee.context.JEEContext; 12 | import org.pac4j.jee.context.session.JEESessionStore; 13 | import org.pac4j.jee.http.adapter.JEEHttpActionAdapter; 14 | import org.pac4j.jwt.config.encryption.SecretEncryptionConfiguration; 15 | import org.pac4j.jwt.config.signature.SecretSignatureConfiguration; 16 | import org.pac4j.jwt.profile.JwtGenerator; 17 | import org.pac4j.springframework.annotation.RequireAnyRole; 18 | import org.pac4j.springframework.web.LogoutController; 19 | import org.springframework.beans.factory.annotation.Autowired; 20 | import org.springframework.beans.factory.annotation.Value; 21 | import org.springframework.stereotype.Controller; 22 | import org.springframework.web.bind.annotation.ExceptionHandler; 23 | import org.springframework.web.bind.annotation.RequestMapping; 24 | import org.springframework.web.bind.annotation.ResponseBody; 25 | 26 | import javax.annotation.PostConstruct; 27 | import java.util.Map; 28 | import java.util.Optional; 29 | 30 | @Controller 31 | public class Application { 32 | 33 | @Value("${salt}") 34 | private String salt; 35 | 36 | @Value("${pac4j.centralLogout.defaultUrl:#{null}}") 37 | private String defaultUrl; 38 | 39 | @Value("${pac4j.centralLogout.logoutUrlPattern:#{null}}") 40 | private String logoutUrlPattern; 41 | 42 | @Autowired 43 | private Config config; 44 | 45 | @Autowired 46 | private JEEContext webContext; 47 | 48 | @Autowired 49 | private ProfileManager profileManager; 50 | 51 | private LogoutController logoutController; 52 | 53 | @PostConstruct 54 | protected void afterPropertiesSet() { 55 | logoutController = new LogoutController(); 56 | logoutController.setDefaultUrl(defaultUrl); 57 | logoutController.setLogoutUrlPattern(logoutUrlPattern); 58 | logoutController.setLocalLogout(false); 59 | logoutController.setCentralLogout(true); 60 | logoutController.setConfig(config); 61 | logoutController.setDestroySession(false); 62 | } 63 | 64 | @RequestMapping("/") 65 | public String root(final Map map) throws HttpAction { 66 | return index(map); 67 | } 68 | 69 | @RequestMapping("/index.html") 70 | public String index(final Map map) throws HttpAction { 71 | map.put("profiles", profileManager.getProfiles()); 72 | map.put("sessionId", new JEESessionStore().getSessionId(webContext, false).orElse("nosession")); 73 | return "index"; 74 | } 75 | 76 | @RequestMapping("/facebook/index.html") 77 | public String facebook(final Map map) { 78 | return protectedIndex(map); 79 | } 80 | 81 | @RequestMapping("/facebook/notprotected.html") 82 | public String facebookNotProtected(final Map map) { 83 | map.put("profiles", profileManager.getProfiles()); 84 | return "notProtected"; 85 | } 86 | 87 | @RequestMapping("/facebookadmin/index.html") 88 | @RequireAnyRole("ROLE_ADMIN") 89 | public String facebookadmin(final Map map) { 90 | return protectedIndex(map); 91 | } 92 | 93 | @RequestMapping("/facebookcustom/index.html") 94 | public String facebookcustom(final Map map) { 95 | return protectedIndex(map); 96 | } 97 | 98 | @RequestMapping("/twitter/index.html") 99 | public String twitter(final Map map) { 100 | return protectedIndex(map); 101 | } 102 | 103 | @RequestMapping("/form/index.html") 104 | public String form(final Map map) { 105 | return protectedIndex(map); 106 | } 107 | 108 | @RequestMapping("/basicauth/index.html") 109 | public String basicauth(final Map map) { 110 | return protectedIndex(map); 111 | } 112 | 113 | @RequestMapping("/cas/index.html") 114 | public String cas(final Map map) { 115 | return protectedIndex(map); 116 | } 117 | 118 | @RequestMapping("/saml/index.html") 119 | public String saml(final Map map) { 120 | return protectedIndex(map); 121 | } 122 | 123 | @RequestMapping("/oidc/index.html") 124 | public String oidc(final Map map) { 125 | return protectedIndex(map); 126 | } 127 | 128 | @RequestMapping("/protected/index.html") 129 | public String protect(final Map map) { 130 | return protectedIndex(map); 131 | } 132 | 133 | @RequestMapping("/loginForm") 134 | public String loginForm(final Map map) { 135 | final FormClient formClient = (FormClient) config.getClients().findClient("FormClient").get(); 136 | map.put("callbackUrl", formClient.getCallbackUrl()); 137 | return "form"; 138 | } 139 | 140 | @RequestMapping("/forceLogin") 141 | @ResponseBody 142 | public void forceLogin() { 143 | try { 144 | final String name = webContext.getRequestParameter(Pac4jConstants.DEFAULT_CLIENT_NAME_PARAMETER) 145 | .map(String::valueOf).orElse(""); 146 | final Client client = config.getClients().findClient(name).get(); 147 | JEEHttpActionAdapter.INSTANCE.adapt(client.getRedirectionAction(new CallContext(webContext, new JEESessionStore())).get(), webContext); 148 | } catch (final HttpAction e) { 149 | } 150 | } 151 | 152 | protected String protectedIndex(final Map map) { 153 | map.put("profiles", profileManager.getProfiles()); 154 | return "protectedIndex"; 155 | } 156 | 157 | @RequestMapping("/centralLogout") 158 | @ResponseBody 159 | public void centralLogout() { 160 | logoutController.logout(webContext.getNativeRequest(), webContext.getNativeResponse()); 161 | } 162 | 163 | @RequestMapping("/dba/index.html") 164 | public String dba(final Map map) { 165 | return protectedIndex(map); 166 | } 167 | 168 | @RequestMapping("/rest-jwt/index.html") 169 | public String restJwt(final Map map) { 170 | return protectedIndex(map); 171 | } 172 | 173 | @RequestMapping("/casrest/index.html") 174 | public String casrest(final Map map) { 175 | return protectedIndex(map); 176 | } 177 | 178 | @RequestMapping("/jwt.html") 179 | public String jwt(final Map map) { 180 | final SecretSignatureConfiguration secretSignatureConfiguration = new SecretSignatureConfiguration(salt); 181 | final SecretEncryptionConfiguration secretEncryptionConfiguration = new SecretEncryptionConfiguration(salt); 182 | final JwtGenerator generator = new JwtGenerator(); 183 | generator.setSignatureConfiguration(secretSignatureConfiguration); 184 | generator.setEncryptionConfiguration(secretEncryptionConfiguration); 185 | String token = ""; 186 | // by default, as we are in a REST API controller, profiles are retrieved only in the request 187 | // here, we retrieve the profile from the session as we generate the token from a profile saved by an indirect client (from the UserInterfaceApplication) 188 | final Optional profile = profileManager.getProfile(); 189 | if (profile.isPresent()) { 190 | token = generator.generate(profile.get()); 191 | } 192 | map.put("token", token); 193 | return "jwt"; 194 | } 195 | 196 | @ExceptionHandler(HttpAction.class) 197 | public void httpAction() { 198 | // do nothing 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/main/java/org/pac4j/demo/spring/CustomAuthorizer.java: -------------------------------------------------------------------------------- 1 | package org.pac4j.demo.spring; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.pac4j.core.authorization.authorizer.ProfileAuthorizer; 5 | import org.pac4j.core.context.WebContext; 6 | import org.pac4j.core.context.session.SessionStore; 7 | import org.pac4j.core.profile.UserProfile; 8 | 9 | import java.util.List; 10 | 11 | public class CustomAuthorizer extends ProfileAuthorizer { 12 | 13 | @Override 14 | public boolean isAuthorized(final WebContext context, final SessionStore sessionStore, final List profiles) { 15 | return isAnyAuthorized(context, sessionStore, profiles); 16 | } 17 | 18 | @Override 19 | public boolean isProfileAuthorized(final WebContext context, final SessionStore sessionStore, final UserProfile profile) { 20 | if (profile == null) { 21 | return false; 22 | } 23 | return StringUtils.startsWith(profile.getUsername(), "jle"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/pac4j/demo/spring/MyErrorController.java: -------------------------------------------------------------------------------- 1 | package org.pac4j.demo.spring; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | import jakarta.servlet.http.HttpServletResponse; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.autoconfigure.web.ErrorProperties; 7 | import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; 8 | import org.springframework.boot.web.servlet.error.ErrorAttributes; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.servlet.ModelAndView; 13 | 14 | @Controller 15 | @RequestMapping({"${server.error.path:${error.path:/error}}"}) 16 | public class MyErrorController extends BasicErrorController { 17 | 18 | @Autowired 19 | public MyErrorController(ErrorAttributes errorAttributes) { 20 | super(errorAttributes, new ErrorProperties()); 21 | } 22 | 23 | @RequestMapping( 24 | produces = {"text/html"} 25 | ) 26 | @Override 27 | public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { 28 | final HttpStatus status = getStatus(request); 29 | if (status.equals(HttpStatus.UNAUTHORIZED)) { 30 | return new ModelAndView("error401"); 31 | } else if (status.equals(HttpStatus.FORBIDDEN)) { 32 | return new ModelAndView("error403"); 33 | } else { 34 | return new ModelAndView("error500"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/pac4j/demo/spring/Pac4jConfig.java: -------------------------------------------------------------------------------- 1 | package org.pac4j.demo.spring; 2 | 3 | import com.nimbusds.jose.JWSAlgorithm; 4 | import org.pac4j.cas.client.CasClient; 5 | import org.pac4j.cas.config.CasConfiguration; 6 | import org.pac4j.core.client.Clients; 7 | import org.pac4j.core.client.direct.AnonymousClient; 8 | import org.pac4j.core.config.Config; 9 | import org.pac4j.http.client.direct.DirectBasicAuthClient; 10 | import org.pac4j.http.client.direct.ParameterClient; 11 | import org.pac4j.http.client.indirect.FormClient; 12 | import org.pac4j.http.client.indirect.IndirectBasicAuthClient; 13 | import org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator; 14 | import org.pac4j.jwt.config.encryption.SecretEncryptionConfiguration; 15 | import org.pac4j.jwt.config.signature.SecretSignatureConfiguration; 16 | import org.pac4j.jwt.credentials.authenticator.JwtAuthenticator; 17 | import org.pac4j.oauth.client.FacebookClient; 18 | import org.pac4j.oauth.client.TwitterClient; 19 | import org.pac4j.oidc.client.GoogleOidcClient; 20 | import org.pac4j.oidc.config.OidcConfiguration; 21 | import org.pac4j.saml.client.SAML2Client; 22 | import org.pac4j.saml.config.SAML2Configuration; 23 | import org.springframework.beans.factory.annotation.Value; 24 | import org.springframework.context.annotation.Bean; 25 | import org.springframework.context.annotation.Configuration; 26 | import org.springframework.core.io.ClassPathResource; 27 | import org.springframework.core.io.FileSystemResource; 28 | 29 | import java.io.File; 30 | import java.util.Optional; 31 | 32 | @Configuration 33 | public class Pac4jConfig { 34 | 35 | @Value("${salt}") 36 | private String salt; 37 | 38 | @Bean 39 | public Config config() { 40 | /*final OidcConfiguration oidcConfiguration = new OidcConfiguration(); 41 | oidcConfiguration.setClientId("test"); 42 | oidcConfiguration.setSecret("secret"); 43 | oidcConfiguration.setUseNonce(true); 44 | oidcConfiguration.setDiscoveryURI("http://localhost:5000/.well-known/openid-configuration"); 45 | oidcConfiguration.setScope("openid api1"); 46 | oidcConfiguration.setClientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); 47 | oidcConfiguration.addCustomParam("prompt", "consent"); 48 | final OidcClient oidcClient = new OidcClient(oidcConfiguration); 49 | oidcClient.setName("test"); 50 | oidcClient.setCallbackUrl("http://localhost:8080/callback");*/ 51 | 52 | final OidcConfiguration oidcConfiguration = new OidcConfiguration(); 53 | oidcConfiguration.setClientId("167480702619-8e1lo80dnu8bpk3k0lvvj27noin97vu9.apps.googleusercontent.com"); 54 | oidcConfiguration.setSecret("MhMme_Ik6IH2JMnAT6MFIfee"); 55 | oidcConfiguration.setPreferredJwsAlgorithm(JWSAlgorithm.PS384); 56 | oidcConfiguration.addCustomParam("prompt", "consent"); 57 | final GoogleOidcClient oidcClient = new GoogleOidcClient(oidcConfiguration); 58 | oidcClient.setAuthorizationGenerator((ctx, profile) -> { 59 | profile.addRole("ROLE_ADMIN"); 60 | return Optional.of(profile); 61 | }); 62 | 63 | final SAML2Configuration cfg = new SAML2Configuration(new ClassPathResource("samlKeystore.jks"), 64 | "pac4j-demo-passwd", 65 | "pac4j-demo-passwd", 66 | new ClassPathResource("metadata-okta.xml")); 67 | cfg.setMaximumAuthenticationLifetime(3600); 68 | cfg.setServiceProviderEntityId("http://localhost:8080/callback?client_name=SAML2Client"); 69 | cfg.setServiceProviderMetadataResource(new FileSystemResource(new File("sp-metadata.xml").getAbsoluteFile())); 70 | final SAML2Client saml2Client = new SAML2Client(cfg); 71 | 72 | final FacebookClient facebookClient = new FacebookClient("145278422258960", "be21409ba8f39b5dae2a7de525484da8"); 73 | facebookClient.setMultiProfile(true); 74 | final TwitterClient twitterClient = new TwitterClient("CoxUiYwQOSFDReZYdjigBA", 75 | "2kAzunH5Btc4gRSaMr7D7MkyoJ5u1VzbOOzE8rBofs"); 76 | // HTTP 77 | final FormClient formClient = new FormClient("http://localhost:8080/loginForm", new SimpleTestUsernamePasswordAuthenticator()); 78 | final IndirectBasicAuthClient indirectBasicAuthClient = new IndirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator()); 79 | 80 | // CAS 81 | final CasConfiguration configuration = new CasConfiguration("https://casserverpac4j.herokuapp.com/login"); 82 | final CasClient casClient = new CasClient(configuration); 83 | // casClient.setGateway(true); 84 | 85 | // REST authent with JWT for a token passed in the url as the token parameter 86 | final SecretSignatureConfiguration secretSignatureConfiguration = new SecretSignatureConfiguration(salt); 87 | final SecretEncryptionConfiguration secretEncryptionConfiguration = new SecretEncryptionConfiguration(salt); 88 | final JwtAuthenticator authenticator = new JwtAuthenticator(); 89 | authenticator.setSignatureConfiguration(secretSignatureConfiguration); 90 | authenticator.setEncryptionConfiguration(secretEncryptionConfiguration); 91 | final ParameterClient parameterClient = new ParameterClient("token", authenticator); 92 | parameterClient.setSupportGetRequest(true); 93 | parameterClient.setSupportPostRequest(false); 94 | 95 | // basic auth 96 | final DirectBasicAuthClient directBasicAuthClient = new DirectBasicAuthClient(new SimpleTestUsernamePasswordAuthenticator()); 97 | 98 | final Clients clients = new Clients("http://localhost:8080/callback", oidcClient, saml2Client, facebookClient, 99 | twitterClient, formClient, indirectBasicAuthClient, casClient, parameterClient, directBasicAuthClient, new AnonymousClient()); 100 | 101 | return new Config(clients); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/org/pac4j/demo/spring/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package org.pac4j.demo.spring; 2 | 3 | import org.pac4j.core.authorization.authorizer.RequireAnyRoleAuthorizer; 4 | import org.pac4j.core.config.Config; 5 | import org.pac4j.springframework.annotation.AnnotationConfig; 6 | import org.pac4j.springframework.component.ComponentConfig; 7 | import org.pac4j.springframework.web.SecurityInterceptor; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.annotation.ComponentScan; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.Import; 12 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 13 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 14 | 15 | @Configuration 16 | @Import({ComponentConfig.class, AnnotationConfig.class}) 17 | @ComponentScan(basePackages = "org.pac4j.springframework.web") 18 | public class SecurityConfig implements WebMvcConfigurer { 19 | 20 | @Autowired 21 | private Config config; 22 | 23 | @Override 24 | public void addInterceptors(InterceptorRegistry registry) { 25 | registry.addInterceptor(buildInterceptor("FacebookClient")) 26 | .addPathPatterns("/facebook/*") 27 | .excludePathPatterns("/facebook/notprotected.html"); 28 | 29 | SecurityInterceptor interceptor = SecurityInterceptor.build(config, 30 | "FacebookClient", new RequireAnyRoleAuthorizer("ROLE_ADMIN")); 31 | 32 | registry.addInterceptor(interceptor).addPathPatterns("/facebookadmin/*"); 33 | registry.addInterceptor(buildInterceptor("FacebookClient")).addPathPatterns("/facebookadmin/*"); 34 | 35 | interceptor = SecurityInterceptor.build(config, 36 | "FacebookClient", new CustomAuthorizer()); 37 | 38 | registry.addInterceptor(interceptor).addPathPatterns("/facebookcustom/*"); 39 | registry.addInterceptor(buildInterceptor("TwitterClient,FacebookClient")).addPathPatterns("/twitter/*"); 40 | registry.addInterceptor(buildInterceptor("FormClient")).addPathPatterns("/form/*"); 41 | registry.addInterceptor(buildInterceptor("IndirectBasicAuthClient")).addPathPatterns("/basicauth/*"); 42 | registry.addInterceptor(buildInterceptor("CasClient")).addPathPatterns("/cas/*"); 43 | registry.addInterceptor(buildInterceptor("SAML2Client")).addPathPatterns("/saml/*"); 44 | registry.addInterceptor(buildInterceptor("GoogleOidcClient")).addPathPatterns("/oidc/*"); 45 | registry.addInterceptor(new SecurityInterceptor(config)).addPathPatterns("/protected/*"); 46 | registry.addInterceptor(buildInterceptor("DirectBasicAuthClient,ParameterClient")).addPathPatterns("/dba/*"); 47 | registry.addInterceptor(buildInterceptor("ParameterClient")).addPathPatterns("/rest-jwt/*"); 48 | 49 | registry.addInterceptor(buildInterceptor("AnonymousClient")).addPathPatterns("/*").excludePathPatterns("/callback*"); 50 | } 51 | 52 | private SecurityInterceptor buildInterceptor(final String client) { 53 | return SecurityInterceptor.build(config, client); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/pac4j/demo/spring/SpringBootPac4jDemo.java: -------------------------------------------------------------------------------- 1 | package org.pac4j.demo.spring; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; 6 | import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; 7 | 8 | @SpringBootApplication(exclude = { 9 | MongoAutoConfiguration.class, 10 | MongoDataAutoConfiguration.class 11 | }) 12 | public class SpringBootPac4jDemo { 13 | 14 | public static void main(final String[] args) { 15 | SpringApplication.run(SpringBootPac4jDemo.class, args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | salt=12345678901234567890123456789012 2 | 3 | pac4j.logout.defaultUrl=/?defaulturlafterlogout 4 | pac4j.logout.destroySession=true 5 | 6 | pac4j.centralLogout.defaultUrl=http://localhost:8080/?defaulturlafterlogoutafteridp 7 | pac4j.centralLogout.logoutUrlPattern=http://localhost:8080/.* 8 | 9 | spring.mustache.suffix=.html 10 | 11 | logging.level.org.pac4j.core.engine=DEBUG 12 | -------------------------------------------------------------------------------- /src/main/resources/metadata-okta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | MIIDpDCCAoygAwIBAgIGAVFtfuF5MA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG 9 | A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU 10 | MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi00MjU5NTQxHDAaBgkqhkiG9w0BCQEW 11 | DWluZm9Ab2t0YS5jb20wHhcNMTUxMjA0MTQ1NTUwWhcNMjUxMjA0MTQ1NjUwWjCBkjELMAkGA1UE 12 | BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV 13 | BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtNDI1OTU0MRwwGgYJ 14 | KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 15 | gtbqJHZYUwLGA9zb4OX2Xdb/2+8gIx8d9g6q3INMRxoPvElh7znRHLOpL8h0iEyhMyXfmgXkJf1U 16 | eUOWUZa0rFIKoY0NSqCSlX44ZuseVwUsDA/vtVq/FSZ5/RAU/iMfbvWfCITVoLsjLHKr+3cnaN/0 17 | 6coe6mOtMJGqWoN/EeH+3lwyFDuk0vbxGqlrn/aXlHLWaSyFJ4CnMU/y0gxiF7kDXFGrj44fkDV9 18 | MJ8k7MjM6WDC5b9eBcfjCCSSR0DZ+0XeGi8VsewRNvmmlGvMuNJJv0TaJNBtOCP4kEClHQIyaBQZ 19 | X3wXi9XiQNwofQTj4qoJ1fHriGVwhS7UI8Xe3wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQB19OcZ 20 | j6x0wVOhjSXTPbJtC1m1E01lgk3+kHzGAk+U5JsYDhEtEOZ2cZ7XIAYjab4RcdAd4hrRbPcupfIB 21 | dHLBeBnWur6C7DpN1mZbfxwvDp1d60wRi1A5PmcrewoQDuQSI3zrIhb+FcqtCewl7Ku1pNX0Nng4 22 | NL95pwiYkOoqfoBTz1WOFq+dsAygPoyneHIARyftdbFQpHmlN+/RRudH6WqAQJ1mXYP9+8tv+yuY 23 | jhy0VURe5ZkFEIO3WtxFxMBj6Z8L1edngYVj3f8t8FmuFxYCqHw0Ex98XLMd0XlL06z4qmE4uJdg 24 | VlDGyllCF+le88VWtD7AB+QxoA/nZxMs 25 | 26 | 27 | 28 | 29 | 30 | urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress 31 | 32 | 33 | urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/resources/samlKeystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pac4j/spring-webmvc-pac4j-boot-demo/81eaa019b6eb8038333af353b761a47737ace462/src/main/resources/samlKeystore.jks -------------------------------------------------------------------------------- /src/main/resources/templates/error401.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

unauthorized

4 |
5 | Home 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/templates/error403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

forbidden

4 |
5 | Home 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/templates/error500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

internal error

4 |
5 | Home 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/templates/form.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | 5 |

6 | 7 |

8 | -------------------------------------------------------------------------------- /src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 |

index

2 | Protected url by Facebook: facebook/index.html (use a real account)
3 | Not protected page: facebook/notprotected.html (no authentication required)
4 | Protected url by Facebook with ROLE_ADMIN: facebookadmin/index.html (use a real account)
5 | Protected url by Facebook with custom authorizer (= must be a CommonProfile where the username starts with "jle"): facebookcustom/index.html (login with form or basic authentication before with jle* username)
6 | Protected url by Twitter: twitter/index.html or by Facebook: twitter/index.html?force_client=FacebookClient (use a real account)
7 | Protected url by form authentication: form/index.html (use login = pwd)
8 | Protected url by indirect basic auth: basicauth/index.html (use login = pwd)
9 | Protected url by CAS: cas/index.html (use login = pwd)
10 | Protected url by SAML2: saml/index.html (use testpac4j at gmail.com / Pac4jtest)
11 | Protected url by Google OpenID Connect: oidc/index.html (use a real account)
12 | Protected url: protected/index.html
13 |
14 | Generate a JWT token (won't start any login process)
15 | Protected url by DirectBasicAuthClient: /dba/index.html (POST the Authorization header with value: Basic amxlbGV1OmpsZWxldQ==) then by ParameterClient: /dba/index.html (with request parameter: token=jwt_generated_token)
16 | Protected url by ParameterClient: /rest-jwt/index.html (with request parameter: token=jwt_generated_token)
17 |
18 | Force Facebook login (even if already authenticated)
19 |
20 | local logout
21 |
22 | central logout 23 |

24 | profiles: {{profiles}}
25 |
26 | sessionId: {{sessionId}} 27 | -------------------------------------------------------------------------------- /src/main/resources/templates/jwt.html: -------------------------------------------------------------------------------- 1 |

Generate JWT token

2 | Back
3 |

4 | token: {{token}}
5 | -------------------------------------------------------------------------------- /src/main/resources/templates/notProtected.html: -------------------------------------------------------------------------------- 1 |

Not protected page

2 | Back
3 |

4 | profiles: {{profiles}} 5 | -------------------------------------------------------------------------------- /src/main/resources/templates/protectedIndex.html: -------------------------------------------------------------------------------- 1 |

protected area

2 | Back
3 |

4 | profiles: {{profiles}} 5 | -------------------------------------------------------------------------------- /src/main/resources/testshib-providers.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | testshib.org 27 | 28 | TestShib Test IdP 29 | TestShib IdP. Use this as a source of attributes 30 | for your test SP. 31 | https://www.testshib.org/testshibtwo.jpg 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | MIIEDjCCAvagAwIBAgIBADANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzEV 41 | MBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1cmdoMREwDwYD 42 | VQQKEwhUZXN0U2hpYjEZMBcGA1UEAxMQaWRwLnRlc3RzaGliLm9yZzAeFw0wNjA4 43 | MzAyMTEyMjVaFw0xNjA4MjcyMTEyMjVaMGcxCzAJBgNVBAYTAlVTMRUwEwYDVQQI 44 | EwxQZW5uc3lsdmFuaWExEzARBgNVBAcTClBpdHRzYnVyZ2gxETAPBgNVBAoTCFRl 45 | c3RTaGliMRkwFwYDVQQDExBpZHAudGVzdHNoaWIub3JnMIIBIjANBgkqhkiG9w0B 46 | AQEFAAOCAQ8AMIIBCgKCAQEArYkCGuTmJp9eAOSGHwRJo1SNatB5ZOKqDM9ysg7C 47 | yVTDClcpu93gSP10nH4gkCZOlnESNgttg0r+MqL8tfJC6ybddEFB3YBo8PZajKSe 48 | 3OQ01Ow3yT4I+Wdg1tsTpSge9gEz7SrC07EkYmHuPtd71CHiUaCWDv+xVfUQX0aT 49 | NPFmDixzUjoYzbGDrtAyCqA8f9CN2txIfJnpHE6q6CmKcoLADS4UrNPlhHSzd614 50 | kR/JYiks0K4kbRqCQF0Dv0P5Di+rEfefC6glV8ysC8dB5/9nb0yh/ojRuJGmgMWH 51 | gWk6h0ihjihqiu4jACovUZ7vVOCgSE5Ipn7OIwqd93zp2wIDAQABo4HEMIHBMB0G 52 | A1UdDgQWBBSsBQ869nh83KqZr5jArr4/7b+QazCBkQYDVR0jBIGJMIGGgBSsBQ86 53 | 9nh83KqZr5jArr4/7b+Qa6FrpGkwZzELMAkGA1UEBhMCVVMxFTATBgNVBAgTDFBl 54 | bm5zeWx2YW5pYTETMBEGA1UEBxMKUGl0dHNidXJnaDERMA8GA1UEChMIVGVzdFNo 55 | aWIxGTAXBgNVBAMTEGlkcC50ZXN0c2hpYi5vcmeCAQAwDAYDVR0TBAUwAwEB/zAN 56 | BgkqhkiG9w0BAQUFAAOCAQEAjR29PhrCbk8qLN5MFfSVk98t3CT9jHZoYxd8QMRL 57 | I4j7iYQxXiGJTT1FXs1nd4Rha9un+LqTfeMMYqISdDDI6tv8iNpkOAvZZUosVkUo 58 | 93pv1T0RPz35hcHHYq2yee59HJOco2bFlcsH8JBXRSRrJ3Q7Eut+z9uo80JdGNJ4 59 | /SJy5UorZ8KazGj16lfJhOBXldgrhppQBb0Nq6HKHguqmwRfJ+WkxemZXzhediAj 60 | Geka8nz8JjwxpUjAiSWYKLtJhGEaTqCYxCCX2Dw+dOTqUzHOZ7WKv4JXPK5G/Uhr 61 | 8K/qhmFT2nIQi538n6rVYLeWj8Bbnl+ev0peYzxFyF5sQA== 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 76 | 79 | 80 | urn:mace:shibboleth:1.0:nameIdentifier 81 | urn:oasis:names:tc:SAML:2.0:nameid-format:transient 82 | 83 | 85 | 87 | 89 | 91 | 92 | 93 | 94 | 95 | 97 | 98 | 99 | 100 | 101 | 102 | MIIEDjCCAvagAwIBAgIBADANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzEV 103 | MBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1cmdoMREwDwYD 104 | VQQKEwhUZXN0U2hpYjEZMBcGA1UEAxMQaWRwLnRlc3RzaGliLm9yZzAeFw0wNjA4 105 | MzAyMTEyMjVaFw0xNjA4MjcyMTEyMjVaMGcxCzAJBgNVBAYTAlVTMRUwEwYDVQQI 106 | EwxQZW5uc3lsdmFuaWExEzARBgNVBAcTClBpdHRzYnVyZ2gxETAPBgNVBAoTCFRl 107 | c3RTaGliMRkwFwYDVQQDExBpZHAudGVzdHNoaWIub3JnMIIBIjANBgkqhkiG9w0B 108 | AQEFAAOCAQ8AMIIBCgKCAQEArYkCGuTmJp9eAOSGHwRJo1SNatB5ZOKqDM9ysg7C 109 | yVTDClcpu93gSP10nH4gkCZOlnESNgttg0r+MqL8tfJC6ybddEFB3YBo8PZajKSe 110 | 3OQ01Ow3yT4I+Wdg1tsTpSge9gEz7SrC07EkYmHuPtd71CHiUaCWDv+xVfUQX0aT 111 | NPFmDixzUjoYzbGDrtAyCqA8f9CN2txIfJnpHE6q6CmKcoLADS4UrNPlhHSzd614 112 | kR/JYiks0K4kbRqCQF0Dv0P5Di+rEfefC6glV8ysC8dB5/9nb0yh/ojRuJGmgMWH 113 | gWk6h0ihjihqiu4jACovUZ7vVOCgSE5Ipn7OIwqd93zp2wIDAQABo4HEMIHBMB0G 114 | A1UdDgQWBBSsBQ869nh83KqZr5jArr4/7b+QazCBkQYDVR0jBIGJMIGGgBSsBQ86 115 | 9nh83KqZr5jArr4/7b+Qa6FrpGkwZzELMAkGA1UEBhMCVVMxFTATBgNVBAgTDFBl 116 | bm5zeWx2YW5pYTETMBEGA1UEBxMKUGl0dHNidXJnaDERMA8GA1UEChMIVGVzdFNo 117 | aWIxGTAXBgNVBAMTEGlkcC50ZXN0c2hpYi5vcmeCAQAwDAYDVR0TBAUwAwEB/zAN 118 | BgkqhkiG9w0BAQUFAAOCAQEAjR29PhrCbk8qLN5MFfSVk98t3CT9jHZoYxd8QMRL 119 | I4j7iYQxXiGJTT1FXs1nd4Rha9un+LqTfeMMYqISdDDI6tv8iNpkOAvZZUosVkUo 120 | 93pv1T0RPz35hcHHYq2yee59HJOco2bFlcsH8JBXRSRrJ3Q7Eut+z9uo80JdGNJ4 121 | /SJy5UorZ8KazGj16lfJhOBXldgrhppQBb0Nq6HKHguqmwRfJ+WkxemZXzhediAj 122 | Geka8nz8JjwxpUjAiSWYKLtJhGEaTqCYxCCX2Dw+dOTqUzHOZ7WKv4JXPK5G/Uhr 123 | 8K/qhmFT2nIQi538n6rVYLeWj8Bbnl+ev0peYzxFyF5sQA== 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 138 | 140 | 141 | urn:mace:shibboleth:1.0:nameIdentifier 142 | urn:oasis:names:tc:SAML:2.0:nameid-format:transient 143 | 144 | 145 | 146 | 147 | TestShib Two Identity Provider 148 | TestShib Two 149 | http://www.testshib.org/testshib-two/ 150 | 151 | 152 | Nate 153 | Klingenstein 154 | ndk@internet2.edu 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 174 | 175 | 176 | 177 | 181 | 182 | 183 | TestShib Test SP 184 | TestShib SP. Log into this to test your machine. 185 | Once logged in check that all attributes that you expected have been 186 | released. 187 | https://www.testshib.org/testshibtwo.jpg 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | MIIEPjCCAyagAwIBAgIBADANBgkqhkiG9w0BAQUFADB3MQswCQYDVQQGEwJVUzEV 196 | MBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1cmdoMSIwIAYD 197 | VQQKExlUZXN0U2hpYiBTZXJ2aWNlIFByb3ZpZGVyMRgwFgYDVQQDEw9zcC50ZXN0 198 | c2hpYi5vcmcwHhcNMDYwODMwMjEyNDM5WhcNMTYwODI3MjEyNDM5WjB3MQswCQYD 199 | VQQGEwJVUzEVMBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1 200 | cmdoMSIwIAYDVQQKExlUZXN0U2hpYiBTZXJ2aWNlIFByb3ZpZGVyMRgwFgYDVQQD 201 | Ew9zcC50ZXN0c2hpYi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 202 | AQDJyR6ZP6MXkQ9z6RRziT0AuCabDd3x1m7nLO9ZRPbr0v1LsU+nnC363jO8nGEq 203 | sqkgiZ/bSsO5lvjEt4ehff57ERio2Qk9cYw8XCgmYccVXKH9M+QVO1MQwErNobWb 204 | AjiVkuhWcwLWQwTDBowfKXI87SA7KR7sFUymNx5z1aoRvk3GM++tiPY6u4shy8c7 205 | vpWbVfisfTfvef/y+galxjPUQYHmegu7vCbjYP3On0V7/Ivzr+r2aPhp8egxt00Q 206 | XpilNai12LBYV3Nv/lMsUzBeB7+CdXRVjZOHGuQ8mGqEbsj8MBXvcxIKbcpeK5Zi 207 | JCVXPfarzuriM1G5y5QkKW+LAgMBAAGjgdQwgdEwHQYDVR0OBBYEFKB6wPDxwYrY 208 | StNjU5P4b4AjBVQVMIGhBgNVHSMEgZkwgZaAFKB6wPDxwYrYStNjU5P4b4AjBVQV 209 | oXukeTB3MQswCQYDVQQGEwJVUzEVMBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYD 210 | VQQHEwpQaXR0c2J1cmdoMSIwIAYDVQQKExlUZXN0U2hpYiBTZXJ2aWNlIFByb3Zp 211 | ZGVyMRgwFgYDVQQDEw9zcC50ZXN0c2hpYi5vcmeCAQAwDAYDVR0TBAUwAwEB/zAN 212 | BgkqhkiG9w0BAQUFAAOCAQEAc06Kgt7ZP6g2TIZgMbFxg6vKwvDL0+2dzF11Onpl 213 | 5sbtkPaNIcj24lQ4vajCrrGKdzHXo9m54BzrdRJ7xDYtw0dbu37l1IZVmiZr12eE 214 | Iay/5YMU+aWP1z70h867ZQ7/7Y4HW345rdiS6EW663oH732wSYNt9kr7/0Uer3KD 215 | 9CuPuOidBacospDaFyfsaJruE99Kd6Eu/w5KLAGG+m0iqENCziDGzVA47TngKz2v 216 | PVA+aokoOyoz3b53qeti77ijatSEoKjxheBWpO+eoJeGq/e49Um3M2ogIX/JAlMa 217 | Inh+vYSYngQB2sx9LGkR9KHaMKNIGCDehk93Xla4pWJx1w== 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 233 | 235 | 237 | 239 | 240 | 241 | 242 | urn:oasis:names:tc:SAML:2.0:nameid-format:transient 243 | urn:mace:shibboleth:1.0:nameIdentifier 244 | 245 | 251 | 252 | 255 | 258 | 261 | 264 | 267 | 270 | 271 | 272 | 273 | 276 | 279 | 280 | 281 | 282 | 283 | 284 | TestShib Two Service Provider 285 | TestShib Two 286 | http://www.testshib.org/testshib-two/ 287 | 288 | 289 | Nate 290 | Klingenstein 291 | ndk@internet2.edu 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | --------------------------------------------------------------------------------