Handling url based RBAC with Spring Security OAuth2 and Okta - spring-security

I have setup application and authorization server on Okta. I have added two groups, namely admin and users. The authentication flow is working fine but when I try to print the roles, I am getting the output as below
[SCOPE_address, SCOPE_phone, SCOPE_offline_access, SCOPE_openid, ROLE_USER, SCOPE_email, SCOPE_profile]
The Java code to print the roles is as below:
#RequestMapping("/securedPage")
public String securedPage(Model model, Principal principal) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Set<String> roles = authentication.getAuthorities().stream()
.map(r -> r.getAuthority()).collect(Collectors.toSet());
System.out.println(roles);
return roles.toString();
}
application.properties
okta.oauth2.client-id=<client-id>
okta.oauth2.client-secret=<client-secret>
okta.oauth2.issuer=<issuer-url>
okta.oauth2.redirect-uri=/login
okta.oauth2.roles-claim=groups
server.port=9222
logging.level.org.springframework.security=TRACE
When I access the login page and enter the username and password, the roles are not getting displayed. But strangely I am seeing ROLE_USER but I have added the uses into users group.
I am following this guide https://developer.okta.com/blog/2017/10/13/okta-groups-spring-security. I am not sure, how to configure the roles for authorization in Spring Security.
The below is the Spring Security Configuration
#Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter{
#Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login();
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example.demo</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.okta.spring</groupId>
<artifactId>okta-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
uestMatcher : Checking match of request : '/securedPage'; against '/'
2021-01-09 00:41:57.674 DEBUG 21292 --- [nio-9222-exec-4] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /securedPage; Attributes: [authenticated]
2021-01-09 00:41:57.678 DEBUG 21292 --- [nio-9222-exec-4] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken#dffd3eb: Principal: Name: [00u3g2nr7ISCACRyk5d6], Granted Authorities: [[ROLE_USER, SCOPE_address, SCOPE_email, SCOPE_offline_access, SCOPE_openid, SCOPE_phone, SCOPE_profile]], User Attributes: [{at_hash=dHbIlPZ1tUzL-y5vmVb1cQ, sub=00u3g2nr7ISCACRyk5d6, zoneinfo=America/Los_Angeles, ver=1, email_verified=true, amr=["pwd"], iss=https://dev-9729512.okta.com/oauth2/edukart, preferred_username=kishore#gmail.com, locale=en-US, given_name=kishore, nonce=FxcHjdKHTsisyWG8jiLgEbH84H2AxyCdISv5U0JyVA8, aud=[0oa3eaj576gLDYwsh5d6], updated_at=2021-01-07T19:46:08Z, idp=00o3ct0plX9rgiTmB5d6, auth_time=2021-01-08T18:48:46Z, name=kishore kumar, exp=2021-01-08T20:11:54Z, family_name=kumar, iat=2021-01-08T19:11:54Z, email=kishore#gmail.com, jti=ID.xLH6W1loE_ELRLCWEuyGHV42-pkw3eCDqfNVlyQOfnc}]; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: D028931C57C35976D9AB6FC2C9543B4B; Granted Authorities: ROLE_USER, SCOPE_address, SCOPE_email, SCOPE_offline_access, SCOPE_openid, SCOPE_phone, SCOPE_profile
Where am I going wrong and how to debug with Okta groups to Spring Security Roles.
Update:
I updated the okta-spring-boot-starter version to 1.4.0 and now I am able to see the admins role that was assigned to the user.
roles :: [SCOPE_address, Everyone, SCOPE_phone, SCOPE_offline_access, SCOPE_openid, ROLE_USER, SCOPE_email, SCOPE_profile, admins]
Questions:
Why is the roles fetched only when configured the claims with ID-Token in include in token type and not while sending it as part of ACCESS_TOKEN in Okta
I do not see an option to set the claims to both ID-TOKEN and ACCESS TOKEN in the claims tab. Its a drop down and I can choose only one.
Why is the granted authority showing it as admins and not as ROLE_ADMINS
Principal :: Name: [00u40teh2owUKq5ZL5d6], Granted Authorities: [[Everyone, ROLE_USER, SCOPE_address, SCOPE_email, SCOPE_offline_access, SCOPE_openid, SCOPE_phone, SCOPE_profile, admins]], User Attributes: [{at_hash=uB-Gcqt-H6ezmv8KpIpx_g, sub=00u40teh2owUKq5ZL5d6, zoneinfo=America/Los_Angeles, ver=1, email_verified=true, amr=["pwd"], iss=https://dev-7858070.okta.com/oauth2/default, groups=["Everyone","admins"], preferred_username=ramesh#gmail.com, locale=en-US, given_name=ramesh, nonce=KXqGlhOj5ZVoChXo-ATjoHW-9ABAcEi5AnukAGXxg78, aud=[0oa3mz4mtisXjRJf85d6], updated_at=2021-01-20T03:17:42Z, idp=00o3myy20pqywuN5o5d6, auth_time=2021-01-21T03:02:46Z, name=ramesh kumar, exp=2021-01-21T04:02:49Z, family_name=kumar, iat=2021-01-21T03:02:49Z, email=ramesh#gmail.com, jti=ID.XQ4cKdIMuQKJv941EkYyDFJDCtKFAzaItPdyLPkMXPQ}] roles :: [SCOPE_address, Everyone, SCOPE_phone, SCOPE_offline_access, SCOPE_openid, ROLE_USER, SCOPE_email, SCOPE_profile, admins]

Take a look at the "Authorization Server" section in the blog post you mentioned:
https://developer.okta.com/blog/2017/10/13/okta-groups-spring-security#authorization-server
The post used an older version of these libraries, but make sure you have the "groups" claim defined. You likely need to set the "include in token type" value to "both" (or follow the same steps to create one for the "ID Token" as well)
This post likely predated OIDC support in Spring Security.
Keep us posted, if that was the issue I'll tweak the post to mention that.
If that doesn't help, use the "Token Preview" tab on the "Authorization Server" configuration page in your Okta Admin/Developer Console.
You should see a "groups" claim listed, once everything is configured correctly.
Update (answering other questions):
ID Token vs Access Token claims
This gets into the weeds a bit related to the OAuth 2.0 and OIDC specs. But the TL;DR is older versions of Spring Security used OAuth 2.0 and Access Tokens, newer versions can use OIDC and ID Tokens.
There are other flows that would use Access Tokens too, for a more detailed description on the differences between the two and which flows use which tokens, checkout the Okta Dev YouTube Channel
This one was my fault, I miss remembered this page.
You can just create the claim for both token types.
It uses the names that are in group, if you want it to use ROLE_ADMIN instead you can create an Okta group with that name.

Related

Null Pointer while trying to get Principal User with Okta

After logging in on website using Okta for authentication I go to the home page and it gives a null pointer exception when I am trying to grab the logged in User Principal.
Here is my Security Dependencies: (I am using Spring boot 1.5.6)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.okta.spring</groupId>
<artifactId>okta-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
Here is my controller:
#GetMapping("/")
public String home(Model model, Principal principal) {
System.out.println(principal.getName());
return "/home";
}
Any help to point me in the right direction would be great. I have looked at the documentation and it seems I am not doing things incorrectly.

Swagger-ui doesn't provide how i pass MediaType.APPLICATION_OCTET_STREAM content

Actually i am facing a problem that Swagger-ui can't show me input for MediaType.APPLICATION_OCTET_STREAM, so lets imagine this next service:
#PUT
#Path("/performAudioQuery")
#Consumes(MediaType.APPLICATION_OCTET_STREAM)
public Response performAudioQuery(InputStream audioInputStream){
//Impl of the service
}
and here are the dependencies
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jersey2-jaxrs</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>2.22.2</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
and i am using wildfly 9.x
So what should i do to make it possible for Swagger-ui working good with this previous service?
The current version of the Swagger/OpenAPI specification (2.0) doesn't deal kindly with file uploads as what's known as body parameter. We've formulated a workable solution that is covered at https://github.com/OAI/OpenAPI-Specification/issues/50 - however that solution is currently not fully supported by swagger-core and not supported at all in swagger-ui.

Keycloak redirects me to my index url instead of to the requested one

I'm using Keycloak server (v 1.5.1) to perform an open-id-connect like authentication to my service. I've set up a basic web application which has two urls, the /index.html one and other one called /hello. I use Spring security, Spring boot and Spring MVC for all of that. That's my pom.xml configuration:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.keycloaktes</groupId>
<artifactId>keycloaktes</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
<relativePath />
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-security-adapter</artifactId>
<version>1.5.1.Final</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-tomcat8-adapter</artifactId>
<version>1.5.1.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
The issue comes when I address to /hello url when not logged in, the keycloak login screen shows properly, but instead of performing a redirection to /hello after successful login, it does it to my /index.html page. That's how I've configured the security adapter:
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(
new SessionRegistryImpl());
}
#Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(
filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(
filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests().antMatchers("/*").hasRole("ADMIN")
.anyRequest().permitAll();
http.csrf().disable();
}
}
I've been trying enabling both the KeycloakAuthenticationProcessingFilter and KeycloakPreAuthActionsFilter, but result keeps the same. Does anybody know how to solve the issue?
You have to permit access to the SSO login entrypoint.
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/sso/login*").permitAll()
.antMatchers("/*").hasRole("ADMIN")
.anyRequest().permitAll();
http.csrf().disable();
}
Spring Security roles are evaluated in the order they're declared so permitting access to /sso/login must come before more restrictive rules.

Spring Boot + Security + Thymeleaf and CSRF token not injected automatically

Disclaimer: I know how to inject the token in a form with thymeleaf manually with this:
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />`
The goal of this post is to improve the knowledge of the platform and get a better understanding of what's going on inside Spring Boot
I haven't tried Spring Boot, but recently i just decided to give it a try, and have to admit its awesome, but with Thymeleaf and Security on Spring MVC, i didn't need to inject CSRF token on forms (POST), because Thymeleaf took care of it automatically, but now in Spring Boot for some reason it doesn't.
From the Spring Boot Reference, i found a list of the common properties used on application.properties file, and the ones related to thymeleaf and security are:
Thymeleaf Properties
spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.excluded-view-names= # comma-separated list of view names that should be excluded from resolution
spring.thymeleaf.view-names= # comma-separated list of view names that can be resolved
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html # ;charset=<encoding> is added
spring.thymeleaf.cache=true # set to false for hot refresh
Security Properties
security.user.name=user # login username
security.user.password= # login password
security.user.role=USER # role assigned to the user
security.require-ssl=false # advanced settings ...
security.enable-csrf=false
security.basic.enabled=true
security.basic.realm=Spring
security.basic.path= # /**
security.basic.authorize-mode= # ROLE, AUTHENTICATED, NONE
security.filter-order=0
security.headers.xss=false
security.headers.cache=false
security.headers.frame=false
security.headers.content-type=false
security.headers.hsts=all # none / domain / all
security.sessions=stateless # always / never / if_required / stateless
security.ignored= # Comma-separated list of paths to exclude from the default secured paths
But if the solution to make Thymeleaf inject the token again is there, i fail to see it.
Edit: adding my configuration
The project was created using the initializer that was shipped in the last STS version (which in my opinion is awesome), with Web, Thymeleaf, Security, JPA, MySQL, H2, Mail, Facebook, Twitter, LinkedIn and Actuator items checked, and added some extras aftwerwards
Using Java 7 and Tomcat 7 because i intend to deploy the project on Openshift in a near future, and next there are my config files:
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>com.adrisasws.springmvc.WebApplication</start-class>
<java.version>1.7</java.version>
<tomcat.version>7.0.59</tomcat.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>1.1.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-facebook</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-linkedin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-twitter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-google</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>openshift</id>
<build>
<finalName>webapp</finalName>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<outputDirectory>webapps</outputDirectory>
<warName>ROOT</warName>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Security config (exactly the same security file i'm using in a non-boot project in which the CSRF token actually gets injected automatically)
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
//////////////////////////////////////////////////////////////////////////
// DEPENDENCIES //
//////////////////////////////////////////////////////////////////////////
#Autowired private DataSource dataSource;
#Autowired private UserRepository userRepository;
//////////////////////////////////////////////////////////////////////////
// PROPERTIES //
//////////////////////////////////////////////////////////////////////////
#Value("${custom.security.rememberme-secret}") private String secret;
#Value("${custom.security.rememberme-create-tables}") private String createTables;
private final static String[] adminRequests = new String[] { ... some matchers here... };
private final static String[] userRequests = new String[] { ... some matchers here... };
private final static String[] publicRequests = new String[] { ...some matchers here... };
//////////////////////////////////////////////////////////////////////////
// AUTHORIZATION //
//////////////////////////////////////////////////////////////////////////
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/images/**", "/js/**", "/error**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(adminRequests).access("hasRole('"+Role.ADMIN.toString()+"')")
.antMatchers(userRequests).access("hasRole('"+Role.USER.toString()+"')")
.antMatchers(publicRequests).permitAll()
.anyRequest().authenticated()
.and()
.requiresChannel()
.anyRequest().requiresSecure()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/", false)
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.rememberMe()
.rememberMeServices(rememberMeService())
.and()
.apply(new SpringSocialConfigurer());
}
//////////////////////////////////////////////////////////////////////////
// AUTHENTICATION //
//////////////////////////////////////////////////////////////////////////
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService())
.passwordEncoder(bCryptPasswordEncoder());
}
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder(11);
}
#Bean
public UserDetailsService userDetailsService() {
return new UserRepositoryUserDetailsService(userRepository);
}
#Bean
public SocialUserDetailsService socialUserDetailsService() {
return new UserRepositorySocialUserDetailsService(userDetailsService());
}
//////////////////////////////////////////////////////////////////////////
// REMEMBER ME //
//////////////////////////////////////////////////////////////////////////
#Bean
public JdbcTokenRepositoryImpl jdbcTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
jdbcTokenRepository.setCreateTableOnStartup(Boolean.valueOf(createTables));
return jdbcTokenRepository;
}
#Bean
public RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
return new RememberMeAuthenticationProvider(secret);
}
#Bean
public PersistentTokenBasedRememberMeServices rememberMeService() {
PersistentTokenBasedRememberMeServices service =
new PersistentTokenBasedRememberMeServices(secret, userDetailsService(), jdbcTokenRepository());
service.setUseSecureCookie(true);
service.setParameter("rememberme");
service.setTokenValiditySeconds(AbstractRememberMeServices.TWO_WEEKS_S);
return service;
}
#Bean
public RememberMeAuthenticationFilter authenticationFilter() throws Exception {
return new RememberMeAuthenticationFilter(authenticationManager(), rememberMeService());
}
}
in my spring boot configt at the moment related to thymeleaf, and for development purposes
spring.thymeleaf.cache=false
and thymeleaf templates look like this (my login page at the moment, will include only the relevant content for the sake of clarity)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security/"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="thymeleaf/layouts/default">
<head>
... css and meta tags ...
</head>
<body>
... some html ...
<th:block sec:authorize="isAnonymous()">
<!-- Bad Credentials -->
<div th:if="${param.error}" class="alert alert-danger text-center">
Invalid username and/or password.
</div>
<!-- Logout -->
<div th:if="${param.logout}" class="alert alert-success text-center">
You have been logged out.
</div>
<!-- Login Form -->
<form id="f" th:action="#{/login}" method="post" role="form" autocomplete="off">
<!-- Username -->
<input type="text" class="form-control text-center" id="username" name="username" th:placeholder="#{form.login.username}" />
<!-- Password -->
<input type="password" class="form-control text-center" id="password" name="password" th:placeholder="#{form.login.password}" />
<!-- Remember me -->
<input type="checkbox" id="rememberme" name="rememberme" />
<!-- Submit -->
<button type="submit" class="btn btn-primary" th:utext="#{form.login.submit}">Login</button>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
</form>
... more html and javascript ...
</body>
</html>
Edit2 - after doing some debugging in the direction Faraj Farook pointed, i found out that, in a project with the configuration i posted, in the Spring Boot version, in this class org.thymeleaf.spring4.requestdata.RequestDataValueProcessor4Delegate, the following function returns a null processor
public Map<String, String> getExtraHiddenFields(
final RequestContext requestContext, final HttpServletRequest request) {
final RequestDataValueProcessor processor = requestContext.getRequestDataValueProcessor();
if (processor == null) {
return null;
}
return processor.getExtraHiddenFields(request);
}
whereas the non Spring boot version, it returns a processor which is an instance of org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor.
I had a similar issue. After some investigation I've found out that only forms that were using 'th:action' attribute (not plain 'action') had the csrf token injected.
For login forms it seems that you need to inject the csrf manually (link).
In the official spring docs (link) there is a suggestion to retrieve the csrf token just before login form submission to prevent session timeouts. In this scenario there would be no csrf token in hidden input on the form.
Using Spring Boot + Thymeleaf + Spring Security it worked with this:
Application Properties
security.enable-csrf=true
Update 30/03/2017:
One important thing is: use th:action inside your form, this will tell the Spring Security to inject CSRF inside the form without the need of manual insertion.
For manual insertion:
html template
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />
Update 25/01/2017:
pom.xml
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
According to the Thymeleaf developers, RequestDataValueProcessor interface is used by Thymeleaf to find the extra hidden fields which is automatically added to the form post back.
The below code in org/thymeleaf/spring3/processor/attr/SpringActionAttrProcessor.java shows this.
final Map<String,String> extraHiddenFields =
RequestDataValueProcessorUtils.getExtraHiddenFields(arguments.getConfiguration(), arguments);
To sort the issue, and automatically add the CSRF Token; In your application create a custom request data value processor and register it with spring. To do this, you may go through the tutorial below.
Csrf Defense in Spring-MVC
I also suggest you to check your previous spring MVC code without the spring boot, to confirm that project's configuration XML has a custom made RequestDataValueProcessor or not.
You'll have to do 2 things. Declare a bean
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
... other beans ...
#Bean
public RequestDataValueProcessor requestDataValueProcessor() {
return new CsrfRequestDataValueProcessor();
}
}
Make sure the html form in your themeleaf template uses "th:action"
<form th:action="#{/youractionurl}">
... input tags
</form>
This automatically inserts _csrf token like this
<input type="hidden" name="_csrf" value="4568ad84-b300-48c4-9532-a9dcb58366f3" />

Portlet preferences

I'm trying to store portlet preferences in a backing bean (JSF) as mentioned in this tutorial
But, I can not understand how they imported Preference class here
Map<String, Preference> mutablePreferenceMap =
(Map<String, Preference>) elResolver.getValue(
facesContext.getELContext(), null, elExpression);
the package javax.portlet.* don't contain faces.preference.Preference
Anyone has an idea about that, specially how to save portlet preferences
thanks in advance
You have to add the Liferay Faces Bridge JAR to your project.
Add the following dependencies:
<dependencies>
<dependency>
<groupId>com.liferay.faces</groupId>
<artifactId>liferay-faces-alloy</artifactId>
<version>3.1.0-ga1</version>
</dependency>
<dependency>
<groupId>com.liferay.faces</groupId>
<artifactId>liferay-faces-bridge-impl</artifactId>
<version>3.1.0-ga1</version>
</dependency>
<dependency>
<groupId>com.liferay.faces</groupId>
<artifactId>liferay-faces-portal</artifactId>
<version>3.1.0-ga1</version>
</dependency>
</dependencies>
and the liferay-faces-bridge-api jar which is a dependency for liferay-faces-bridge-impl has this Preference interface.
More info:
http://www.liferay.com/community/liferay-projects/liferay-faces/download

Resources