I am setting up JDBC authentication with a PostgreSQL database from my Spring boot Security application.
I have created the below tables for users and roles, named 'users' and 'user_authorities', respectively.
CREATE SEQUENCE users_id_seq
INCREMENT 1
START 1
MINVALUE 1
MAXVALUE 2147483647
CACHE 1;
CREATE TABLE users (
id integer NOT NULL DEFAULT nextval('users_id_seq'),
username VARCHAR ( 100 ) UNIQUE NOT NULL,
password VARCHAR ( 100 ) NOT NULL,
enabled boolean NOT NULL,
CONSTRAINT users_pkey PRIMARY KEY (id)
);
CREATE SEQUENCE user_authorities_id_seq
INCREMENT 1
START 1
MINVALUE 1
MAXVALUE 2147483647
CACHE 1;
CREATE TABLE user_authorities(
id integer NOT NULL DEFAULT nextval('user_authorities_id_seq'),
user_id integer NOT NULL,
authority varchar(100) not null,
CONSTRAINT user_authorities_pkey PRIMARY KEY (id)
);
Then insert the data as below:
-- create user: 'user'
INSERT INTO users(username,password,enabled)
VALUES('user','$2a$10$TfjwK4p4y2xn5f6RN78gwOz0Le.cMGuhNaz51WDjChGCDF9Z0yqci',true);
-- create user: 'admin'
INSERT INTO users(username,password,enabled)
VALUES('admin','$2a$10$lbZgb/zt4jBoPjqF.RfsOOOKyKJMOZjFS8QMyO.5p7Ob/jzf7ASPC',true);
-- create role: 'USER' for the user: 'user'
INSERT INTO users_authorities(user_id,authority) VALUES((select u.id from users u where u.username =
'user'),'USER');
-- create role: 'ADMIN' for the user: 'admin'
INSERT INTO user_authorities(user_id,authority) VALUES((select u.id from users u where u.username =
'admin'),'ADMIN');
Spring Boot application side, I have the security configuration:
package com.mi.rest.webservices.restfulwebservices.security;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import
org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
DataSource dataSource;
#Autowired
BCryptPasswordEncoder bCryptEncoder;
private static final String GET_USERS_SQL = "SELECT username, password, enabled from users where
username = ?";
private static final String GET_USER_AUTHORITIES_SQL = "SELECT u.username, a.authority FROM
user_authorities a, users u WHERE u.username = ? AND u.id = a.user_id";
/**
* Specify authentication scheme:
*
* 1. In memory
* 2. JDBC
* 3. LDAP
*
*/
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/**
auth.inMemoryAuthentication()
.withUser("user").password("$2a$10$TfjwK4p4y2xn5f6RN78gwOz0Le.cMGuhNaz51WDjChGCDF9Z0yqci")
.roles("USER")
.and()
.withUser("admin")
.password("$2a$10$lbZgb/zt4jBoPjqF.RfsOOOKyKJMOZjFS8QMyO.5p7Ob/jzf7ASPC")
.roles("ADMIN");
*/
auth
.jdbcAuthentication()
.usersByUsernameQuery(GET_USERS_SQL)
.authoritiesByUsernameQuery(GET_USER_AUTHORITIES_SQL)
.dataSource(dataSource)
.passwordEncoder(bCryptEncoder);
}
//Authorization:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//HTTP Basic authentication
.httpBasic()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/todo-app/userOnly").hasRole("USER")
.antMatchers(HttpMethod.GET, "/todo-app/todos/**").hasRole("USER")
.antMatchers(HttpMethod.GET, "/todo-app/adminOnly").hasRole("ADMIN")
.antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
//.and()
//.csrf().disable()
;
}
}
Now in order to test my setup, I have a RestController with the endpoints below:
package com.mi.rest.webservices.restfulwebservices.controllers;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/todo-app")
#CrossOrigin(origins = "http://localhost:4200")
public class TodoController {
#GetMapping("/userOnly")
public TodoItem getForUserOnly() {
TodoItem todo9 = new TodoItem();
todo9.setId(9);
todo9.setDescription("USER role item");
todo9.setDone(false);
todo9.setTargetDate(new Date());
todo9.setUser("user");
return todo9;
}
#GetMapping("/adminOnly")
public TodoItem getForAdminOnly() {
TodoItem todo9 = new TodoItem();
todo9.setId(9);
todo9.setDescription("ADMIN role item");
todo9.setDone(false);
todo9.setTargetDate(new Date());
todo9.setUser("admin");
return todo9;
}
}
Testing with Postman, I keep getting 403 forbidden for all tests (with user and admin authorized endpoints).
What is missing from this picture? any hints and advises are highly appreciated.
Make not that i am appending "ROLE" to each authority. Spring expects the authorities to have a prefix of "ROLE...." .
reference - Spring doc
Edit the inserts for your users_authorities table
-- create role: 'USER' for the user: 'user'
INSERT INTO users_authorities(user_id,authority) VALUES((select u.id from users u where u.username =
'user'),'ROLE_USER');
-- create role: 'ADMIN' for the user: 'admin'
INSERT INTO user_authorities(user_id,authority) VALUES((select u.id from users u where u.username =
'admin'),'ROLE_ADMIN');
Related
I'm following a Spring Course that the teacher makes an example of Spring Security (loging page with spring mvc) ... he's using mysql and i'm trying to do it but with Oracle Database 19c, width ojdbc10, but i'm missing something.
I have the same tables (user and password), with the same data in it, but when i try to log in, i failed in the authentication, like the user name or password is wrong.
Properties file
#
#JDBC connection properties
#
jdbc.driver=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:#192.168.240.11:1521:orcl
jdbc.user=jcataldo
jdbc.password=Pola1095
App config .java
ort org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import com.mchange.v2.c3p0.ComboPooledDataSource;
#Configuration
#EnableWebMvc
#ComponentScan(basePackages="com.luv2code.springsecurity.demo")
#PropertySource("classpath:persistence-oracle.properties")
public class DemoAppConfig {
// define a bean for ViewResolver ==> Indica que es lo que debería mostrar, en este caso todo los
// .jsp ubicados en /WEB-INF/view
#Autowired
private Environment env; //hold data read from properties file
// set up a logger for diagnostics
private Logger logger = Logger.getLogger(getClass().getName());
// define a bean for ViewResolver
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
// define a bean for our security datasource
#Bean
public DataSource securityDataSource() {
// create a connection pool
ComboPooledDataSource securityDataSource
= new ComboPooledDataSource();
// set the jdbc driver class
try {
securityDataSource.setDriverClass(env.getProperty("jdbc.driver"));
} catch (PropertyVetoException exc) {
throw new RuntimeException(exc);
}
// log the connection props
// check if we are really reading data from properties file
logger.info(">>>>> jdbc.url=" + env.getProperty("jdbc.url"));
logger.info(">>>>> jdbc.user=" + env.getProperty("jdbc.user"));
// set database connection props
securityDataSource.setJdbcUrl(env.getProperty("jdbc.url"));
securityDataSource.setUser(env.getProperty("jdbc.user"));
securityDataSource.setPassword(env.getProperty("jdbc.password"));
// set connection pool props
securityDataSource.setInitialPoolSize(
getIntProperty("connection.pool.initialPoolSize"));
securityDataSource.setMinPoolSize(
getIntProperty("connection.pool.minPoolSize"));
securityDataSource.setMaxPoolSize(
getIntProperty("connection.pool.maxPoolSize"));
securityDataSource.setMaxIdleTime(
getIntProperty("connection.pool.maxIdleTime"));
return securityDataSource;
}
private int getIntProperty(String propName) {
String propVal = env.getProperty(propName);
// convert to int
int intPropVal = Integer.parseInt(propVal);
return intPropVal;
}
}
I have NO CONNECTION ERRORS, but its like the data in the table user and password is not matching with my input in the mvc login page.
Hope someone can help me.
thanks in advance and sorry if i misslead something, i'm new in this.
Well, i messed up with the table's database, didn't follow the right schema, wich is:
CREATE TABLE USERS (
USERNAME NVARCHAR2(128) PRIMARY KEY,
PASSWORD NVARCHAR2(128) NOT NULL,
ENABLED CHAR(1) CHECK (ENABLED IN ('Y','N') ) NOT NULL);
CREATE TABLE AUTHORITIES (
USERNAME NVARCHAR2(128) NOT NULL,
AUTHORITY NVARCHAR2(128) NOT NULL);
ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_UNIQUE UNIQUE (USERNAME, AUTHORITY);
ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_FK1 FOREIGN KEY (USERNAME) REFERENCES USERS (USERNAME) ENABLE;
Client is sending SHA1 token which is valid upto 24 hours, need to authorize that SHA1 token using Spring Boot.
Ex: 1st service is sending request to 2nd service by passing SHA1 token(valid 24 hour) as part of the header/query param. So 2nd service need to authorize that SHA1 token using Spring Boot.
I have tried with Filter approach. Please help the code snippet, If any new approach also fine.
Please refer below code.
TokenAuthConfig.java file
package com.scheduler.config;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import com.scheduler.filter.config.TokenAuthFilter;
import lombok.extern.slf4j.Slf4j;
#Configuration
#EnableWebSecurity
#Order(1)
#Slf4j
public class TokenAuthConfig extends WebSecurityConfigurerAdapter {
#Value("${report.rest.key.secret_key}")
private String SECRET_KEY;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
TokenAuthFilter filter = new TokenAuthFilter("Authorization");
filter.setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
log.info("TokenAuthConfig.configure.AuthenticationManager.authenticate SECRET_KEY: " + SECRET_KEY);
String principal = (String) authentication.getPrincipal();
if (!generateSHA1Token().equals(principal)) {
throw new BadCredentialsException("The key was not found or not the expected value.");
}
authentication.setAuthenticated(true);
return authentication;
}
});
httpSecurity.antMatcher("/schedule/v1/status/learner/getAllLearnersReport").csrf().disable().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().addFilter(filter).authorizeRequests()
.anyRequest().authenticated();
}
private String generateSHA1Token() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
String utcStringDate = simpleDateFormat.format(new java.util.Date());
String sha1Token = DigestUtils.sha1Hex((SECRET_KEY + utcStringDate));
log.info("SHA1Token: "+ sha1Token);
return sha1Token;
}
}
TokenAuthFilter.java file
package com.scheduler.filter.config;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import lombok.extern.slf4j.Slf4j;
#Slf4j
public class TokenAuthFilter extends AbstractPreAuthenticatedProcessingFilter{
public String principalRequestHeaderKey;
public TokenAuthFilter(String principalRequestHeaderKey) {
log.info("TokenAuthFilter.getPreAuthenticatedPrincipal executed");
this.principalRequestHeaderKey = principalRequestHeaderKey;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
log.info("TokenAuthFilter.getPreAuthenticatedPrincipal executed");
return request.getHeader("token");
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "N/A";
}
}
I am creating an SPA with Keycloak 5, Spring Boot 2 and Angular 7.
Everything was fine, even keycloack configuration in application.properties and roles securing. But when I tried to create a Bean to get the User Token Data, I am receiving a null bean of it. Can't understand why, it is just like the code in Keycloak documentation...
import javax.servlet.http.HttpServletRequest;
import org.keycloak.KeycloakSecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
#Configuration
public class KeycloakConfig {
/**
* Retorna o contexto de segurança do Keycloak.
*
* #return
*/
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public KeycloakSecurityContext accessToken() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
}
}
Boot Config:
keycloak.enabled = true
keycloak.auth-server-url = http://acesso.tre-pa.jus.br/auth
keycloak.realm = TRE-PA
keycloak.resource = acesso-sistemas-service
keycloak.credentials.secret = ca70294a-af51-4abb-81f9-234566de2c7c
keycloak.ssl-required = external
keycloak.use-resource-role-mappings = false
keycloak.bearer-only = true
keycloak.autodetect-bearer-only = true
keycloak.principal-attribute = preferred_username
logging.level.org.keycloak = DEBUG
spring.main.allow-bean-definition-overriding = true
# spring.autoconfigure.exclude = org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration
keycloak.securityConstraints[0].securityCollections[0].name = secured-area
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /secured/*
keycloak.securityConstraints[1].securityCollections[0].patterns[1] = /admin/*
keycloak.securityConstraints[1].authRoles[0] = DEVELOPER
keycloak.securityConstraints[1].securityCollections[0].name = service-area
keycloak.securityConstraints[1].securityCollections[0].patterns[0] = /service/*
I did something similar in my application using Keycloak 4.8.1.Final where I could access the AccessToken and expose it as a bean for injection into other classes:
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public AccessToken getAccessToken() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
KeycloakPrincipal<RefreshableKeycloakSecurityContext> keycloakPrincipal = (KeycloakPrincipal) request.getUserPrincipal();
if (keycloakPrincipal == null) {
throw new NotAuthenticatedException("KeycloakPrincipal not set");
}
KeycloakSecurityContext keycloakSecurityContext = keycloakPrincipal.getKeycloakSecurityContext();
return keycloakSecurityContext.getToken();
}
After upgrading Keycloak to 5 (and above all the way to 15.0.1) the code above no longer works. The only way I managed to get a reference to the AccessToken was by using Spring Security as described (mostly) here
Basically adding spring security deps to gradle
implementation('org.springframework.boot:spring-boot-starter-security')
Adding this config class...
import org.keycloak.adapters.KeycloakConfigResolver
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper
import org.springframework.security.core.session.SessionRegistryImpl
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy
import javax.inject.Inject
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true)
class KeycloakConfig : KeycloakWebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
super.configure(http)
http.authorizeRequests()
.anyRequest()
.permitAll()
http.csrf().disable()
}
#Inject
fun configureGlobal(authManager: AuthenticationManagerBuilder) {
val authProvider = keycloakAuthenticationProvider()
authProvider.setGrantedAuthoritiesMapper(SimpleAuthorityMapper())
authManager.authenticationProvider(authProvider)
}
#Bean
override fun sessionAuthenticationStrategy(): SessionAuthenticationStrategy {
return RegisterSessionAuthenticationStrategy(SessionRegistryImpl())
}
}
And then updating my method in my other config class:
import org.springframework.amqp.rabbit.annotation.EnableRabbit
import org.springframework.cache.annotation.EnableCaching
import org.springframework.context.annotation.EnableAspectJAutoProxy
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.web.context.WebApplicationContext
import org.springframework.context.annotation.ScopedProxyMode
import org.keycloak.representations.AccessToken
import javax.servlet.http.HttpServletRequest
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.context.request.ServletRequestAttributes
import org.keycloak.KeycloakPrincipal
import org.keycloak.adapters.RefreshableKeycloakSecurityContext
import org.keycloak.KeycloakSecurityContext
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Scope
import javax.inject.Inject
import javax.ws.rs.NotAuthorizedException
import javax.ws.rs.core.Context
import javax.ws.rs.core.SecurityContext
#Configuration
class ApplicationConfig {
//needed for keycloak adaptor 7.0.1+
// added in this here rather than in KeycloakConfig above due to issue here:
// https://stackoverflow.com/questions/57957006/unable-to-build-spring-based-project-for-authentication-using-keycloak
#Bean
fun keycloakConfigResolver(): KeycloakSpringBootConfigResolver {
return KeycloakSpringBootConfigResolver()
}
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
fun accessToken(): AccessToken {
val request = (RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes).request
val token = request.userPrincipal as KeycloakAuthenticationToken? ?: throw NotAuthorizedException("KeycloakPrincipal not set")
val principal = token.principal as KeycloakPrincipal<*>
val keycloakSecurityContext = principal.keycloakSecurityContext
return keycloakSecurityContext.token
}
}
The above codes works for me with Springboot versions 2.1.2.RELEASE and 2.5.3
I'm working on porting an application with ThymeLeaf templates to use Mustache templates but I'm not finding a way good way to port the sec:authorize tags of ThymeLeaf. Is there a way to get at the SecurityContext information in Mustache like what is provided by the Thymeleaf Spring Security extras?
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
I thought I could use #ControllerAdvice to inject model attributes like this, but SecurityContextHolder.getContents().getAuthentication() is null. However I'm able to check roles on the HttpServletRequest object just fine. It seems like a timing issue because in my main #Controller I'm able to access the Authentication object. I can pull out the principal name, and isAuthenticated() returns the correct value. Here's what I tried with #ControllerAdvice:
package com.example;
import java.util.Map.Entry;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import org.example.security.entity.UserRole;
import org.example.security.entity.UserRole.Role;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
#ControllerAdvice
public class TemplateAdvice {
private static String ANONYMOUS_USER = "anonymousUser";
private Authentication authentication;
private Object principal;
private String username;
private boolean isAuthenticated;
private boolean isAnonymous;
private HttpServletRequest request;
public TemplateAdvice() {
this.authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (this.authentication != null) {
this.principal = this.authentication.getPrincipal();
this.username = this.authentication.getName();
}
this.isAnonymous = this.principal == null
|| this.username.equals(ANONYMOUS_USER);
}
#ModelAttribute
public void addDefaultAttributes(HttpServletRequest request,
Model model) {
this.request = request;
model.addAttribute("isAuthenticated", this.authentication.isAuthenticated());
model.addAttribute("isAnonymous", this.isAnonymous);
model.addAttribute("username", this.username);
model.addAttribute("isAuthenticated", hasAnyRole()); // hack
model.addAttribute("isAdminOrSuper",
hasRole(UserRole.Role.ADMIN)
|| hasRole(UserRole.Role.SUPER)); // this works
}
private boolean hasRole(Role role) {
return this.request.isUserInRole(role.name());
}
private boolean hasAnyRole() {
return Stream.of(UserRole.Role.values())
.anyMatch(role -> hasRole(role));
}
}
I'm pulling out the contextPath and _csrf.token by using,
spring.mustache.expose-request-attributes=true
spring.mustache.request-context-attribute=req
and was hoping some of the security context could be exposed similarly.
I'm not finding many examples on Google. Is there a good way to check if a user is authenticated and what roles they have in Mustache templates?
I determined one of the issues was that the security context was not resolving in the TemplateAdvice constructor. After moving it to addDefaultAttributes everything functioned as expected. Here's what I came up with for now:
package com.example.authentication.server.controller;
import javax.servlet.http.HttpServletRequest;
import com.example.authentication.server.security.SecurityHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
#ControllerAdvice
public class TemplateAdvice {
private SecurityHelper securityHelper;
#ModelAttribute
public void addDefaultAttributes(HttpServletRequest request, Model model) {
securityHelper = new SecurityHelper(SecurityContextHolder.getContext());
model.addAttribute("isLoggedIn", securityHelper.isAuthenticated()
&& !securityHelper.isAnonymous());
model.addAttribute("username", securityHelper.username());
model.addAttribute("isAdminOrSuper", securityHelper.isAdminOrSuper());
}
}
And the corresponding SecurityHelper class:
package com.example.authentication.server.security;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
public class SecurityHelper {
public static enum Role {
ROLE_USER, ROLE_ADMIN;
}
private Collection<? extends GrantedAuthority> authorities =
Collections.emptyList();
private Authentication authentication;
public SecurityHelper(SecurityContext context) {
authentication = context.getAuthentication();
if (authentication != null) {
authorities = authentication.getAuthorities();
}
}
public boolean isAuthenticated() {
return authentication == null ? false : authentication.isAuthenticated();
}
public boolean isAnonymous() {
return authentication == null ? true :
authentication instanceof AnonymousAuthenticationToken;
}
public String username() {
return authentication == null ? "" : authentication.getName();
}
public boolean isAdminOrSuper() {
return hasAnyRole(Arrays.asList(Role.ROLE_ADMIN, Role.ROLE_SUPER));
}
/**
* Checks if user contains the given role.
*
* #param role
* A user role.
* #return True if the user contains the role.
*/
public boolean hasRole(Role role) {
return authorities == null ? false
: authorities.stream().anyMatch(authority ->
authority.getAuthority().equals(role.name()));
}
/**
* Checks if a user contains at least one of the roles.
*
* #param roles
* A list of user roles.
* #return True if the user contains one of the roles.
*/
public boolean hasAnyRole(List<Role> roles) {
return roles.stream().anyMatch(role -> hasRole(role));
}
}
I just made a starter for mustache to use spring security's 'Expression-Based Access Control' feature, please check the codes here:
https://github.com/iceant/mustache-security-spring-boot-starter
Here is the examples:
{{#sec:hasRole('ADMIN')}}<li>ADMIN CONTENT</li>{{/sec:hasRole('ADMIN')}}
{{#sec:hasRole('ADMIN') and hasRole('USER')}}<li>ADMIN & USER CONTENT</li>{{/sec:hasRole('ADMIN') and hasRole('USER')}}
{{#sec:hasAnyRole('ADMIN', 'USER')}}<li>ADMIN OR USER CONTENT</li>{{/sec:hasAnyRole('ADMIN', 'USER')}}
{{#sec:hasRole('USER')}}<li>USER CONTENT</li>{{/sec:hasRole('USER')}}
{{#sec:isAnonymous()}}<li>isAnonymous</li>{{/sec:isAnonymous()}}{{^sec:isAnonymous()}}<li>isAnonymous=false</li>{{/sec:isAnonymous()}}
{{#sec:isRememberMe()}}<li>isRememberMe</li>{{/sec:isRememberMe()}}{{^sec:isRememberMe()}}<li>isRememberMe=false</li>{{/sec:isRememberMe()}}
{{#sec:isAuthenticated()}}<li>isAuthenticated</li>{{/sec:isAuthenticated()}}
{{^sec:isAuthenticated()}}<li>isAuthenticated=false</li>{{/sec:isAuthenticated()}}
{{#sec:isFullyAuthenticated()}}<li>isFullyAuthenticated</li>{{/sec:isFullyAuthenticated()}}
{{^sec:isFullyAuthenticated()}}<li>isFullyAuthenticated=false</li>{{/sec:isFullyAuthenticated()}}
{{#sec:principal}}<li>principal={{username}}{{/sec:principal}}
{{#sec:authentication}}<li>authentication={{.}}{{/sec:authentication}}
{{#sec:permitAll}}<li>permitAll</li>{{/sec:permitAll}}
{{#sec:denyAll}}<li>denyAll</li>{{/sec:denyAll}}
{{^sec:denyAll}}<li>denyAll=false</li>{{/sec:denyAll}}
{{^sec:hasIpAddress('192.168.2.1')}}<li>hasIpAddress('192.168.2.1')=false</li>{{/sec:hasIpAddress('192.168.2.1')}}
{{#sec:isMember(3)}}<li>isMember(3){{/sec:isMember(3)}}
{{#sec:#webSecurity.check(authentication,request)}}<li>#webSecurity.check(authentication,request){{/sec:#webSecurity.check(authentication,request)}}
We have a requirement to Cache requests to a max of upto 2 categories per userId-sessionID pair. We are planning to use EhCache for this approach. The project is a spring boot application.
#Cacheable(value="products")
ProductList getProducts(String userID, String sessionID, String categoryID) {
return getProductListForThisUserAndSessionAndCategory(userId, sessionId, categoryId;)
}
The problem is how can I set the limit to a max of 2 cache elements per userID-sessionId when there could be more than one categories per user and session id pair?
One approach:
Setting a partial key of sessionId and userID and create a custom cache which can accept a max two values per sessionID-userID key. How does EhCache support custom Caches?
Any other approaches?
If I got your question right you will change the maximum number of mappings to cache you can config like this
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.expiry.Duration;
import org.ehcache.expiry.Expirations;
import org.ehcache.jsr107.Eh107Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;
import java.util.concurrent.TimeUnit;
#Configuration
#EnableCaching
public class CacheConfiguration {
private final Logger log = LoggerFactory.getLogger(CacheConfiguration.class);
private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;
#PreDestroy
public void destroy() {
log.info("Remove Cache Manager metrics");
log.info("Closing Cache Manager");
}
public CacheConfiguration(JHipsterProperties jHipsterProperties) {
jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(
CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class,
ResourcePoolsBuilder.heap(2))
.withExpiry(Expirations.timeToLiveExpiration(Duration.of(3600, TimeUnit.SECONDS)))
.build()
);
}
#Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
log.debug("Starting Ehcache");
return cm -> {
cm.createCache("products", jcacheConfiguration);
};
}
}