Spring security jdbcAuthentication does not work with default roles processing - spring

Using
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("dba").password("root123").roles("ADMIN","DBA");
my example works fine. For example for
http.authorizeRequests()
// ...
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.and().formLogin()
.and().exceptionHandling().accessDeniedPage("/Access_Denied");
If I have changed inMemoryAuthentication to spring jdbc default - i got an role issue than.
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
I sure I configured db and schema using spring recommendations (to be able to use default jdbc authentication).
In debug mode I can see result of loading from db in the
org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl
#loadUserByUsername(username)[line 208]
return createUserDetails(username, user, dbAuths);
It returns similar result with in memory configuration:
org.springframework.security.core.userdetails.User#183a3:
Username: dba;
Password: [PROTECTED];
Enabled: true;
AccountNonExpired: true;
credentialsNonExpired: true;
AccountNonLocked: true;
Granted Authorities: ADMIN,DBA
As you can see it loads correspond Granted Authorities, but http request redirects me to .accessDeniedPage("/Access_Denied"). I confused because It should work for user like time before.
I do not use spring boot in my project.
My logs does not contain any configuration of jdbc errors.
I have spend a lot of time to investigate details and my ideas have just finished.
Do you think I need add to build some cache libraries or something else?

There are 2 gotchas in play here.
The first is that when using hasRole('ADMIN') that first a check is done if it starts with the role prefix (for which the default is ROLE_) if not the passed in role is prefix with it (see also the reference guide). So in this case the actual authority checked is ROLE_ADMIN and not ADMIN as you expect/assume.
The second is that when using the in memory option the roles method does the same as mentioned here. It checks if the passed in roles start with the role prefix and if not adds it. So in your sample with the in memory one you end up with authorities ROLE_ADMIN and ROLE_DBA.
However in your JDBC option you have authorities ADMIN and DBA and hence the hasRole('ADMIN') check fails because ROLE_ADMIN isn't equal to ADMIN.
To fix you have several options.
Instead of hasRole use hasAuthority the latter doesn't add the role prefix and for the in memory option use authorities instead of roles.
In the JDBC option prefix the authorities in the database with ROLE_
Set the default role prefix to empty.
Using hasAuthority
First change the configuration of the in memory database to use authorities instead of roles.
auth.inMemoryAuthentication()
.withUser("dba").password("root123")
.authorities("ADMIN","DBA");
next change your expressions as well
.antMatchers("/db/**").access("hasAuthority('ADMIN') and hasAuthority('DBA')")
Prefix with ROLE_
In the script that inserts the authorities prefix the authorities with ROLE_.
Remove the default role prefix
This is a bit tricky and is extensivly described in [the migration guide].
There is no easy configuration option and requires a BeanPostProcessor.
public class DefaultRolesPrefixPostProcessor implements BeanPostProcessor, PriorityOrdered {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
// remove this if you are not using JSR-250
if(bean instanceof Jsr250MethodSecurityMetadataSource) {
((Jsr250MethodSecurityMetadataSource) bean).setDefaultRolePrefix(null);
}
if(bean instanceof DefaultMethodSecurityExpressionHandler) {
((DefaultMethodSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
}
if(bean instanceof DefaultWebSecurityExpressionHandler) {
((DefaultWebSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
}
if(bean instanceof SecurityContextHolderAwareRequestFilter) {
((SecurityContextHolderAwareRequestFilter)bean).setRolePrefix("");
}
return bean;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
#Override
public int getOrder() {
return PriorityOrdered.HIGHEST_PRECEDENCE;
}
}

You can see see what happened enabling the logging. In your application.properties add:
# ==============================================================
# = Logging springframework
# ==============================================================
logging.level.org.springframework.jdbc=DEBUG
logging.level.org.springframework.security=DEBUG
logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.http=DEBUG

Related

Howto inject Picocli parsed parameters into Spring Bean definitions?

I'm trying to use Picocli with Spring Boot 2.2 to pass command line parameters to a Spring Bean, but not sure how to structure this. For example, I have the following #Command to specify a connection username and password from the command line, however, want to use those params to define a Bean:
#Component
#CommandLine.Command
public class ClearJdoCommand extends HelpAwarePicocliCommand {
#CommandLine.Option(names={"-u", "--username"}, description = "Username to connect to MQ")
String username;
#CommandLine.Option(names={"-p", "--password"}, description = "Password to connect to MQ")
String password;
#Autowired
JMSMessagePublisherBean jmsMessagePublisher;
#Override
public void run() {
super.run();
jmsMessagePublisher.publishMessage( "Test Message");
}
}
#Configuration
public class Config {
#Bean
public InitialContext getJndiContext() throws NamingException {
// Set up the namingContext for the JNDI lookup
final Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
env.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");
env.put(Context.SECURITY_PRINCIPAL, username);
env.put(Context.SECURITY_CREDENTIALS, password);
return new InitialContext(env);
}
#Bean
public JMSPublisherBean getJmsPublisher(InitialContext ctx){
return new JMSPublisherBean(ctx);
}
}
I'm stuck in a bit of a circular loop here. I need the command-line username/password to instantiate my JMSPublisherBean, but these are only available at runtime and not available at startup.
I have managed to get around the issue by using Lazy intialization, injecting the ClearJdoCommand bean into the Configuration bean and retrieving the JMSPublisherBean in my run() from the Spring context, but that seems like an ugly hack. Additionally, it forces all my beans to be Lazy, which is not my preference.
Is there another/better approach to accomplish this?
Second option might be to use pure PicoCli (not PicoCli spring boot starter) and let it run command; command will not be Spring bean and will only be used to validate parameters.
In its call method, Command would create SpringApplication, populate it with properties (via setDefaultProperties or using JVM System.setProperty - difference is that environment variables will overwrite default properties while system properties have higher priority).
#Override
public Integer call() {
var application = new SpringApplication(MySpringConfiguration.class);
application.setBannerMode(Mode.OFF);
System.setProperty("my.property.first", propertyFirst);
System.setProperty("my.property.second", propertySecond);
try (var context = application.run()) {
var myBean = context.getBean(MyBean.class);
myBean.run(propertyThird);
}
return 0;
}
This way, PicoCli will validate input, provide help etc. but you can control configuration of Spring Boot application. You can even use different Spring configurations for different commands. I believe this approach is more natural then passing all properties to CommandLineRunner in Spring container
One idea that may be useful is to parse the command line in 2 passes:
the first pass is just to pick up the information needed for configuration/initialization
in the second pass we pick up additional options and execute the application
To implement this, I would create a separate class that "duplicates" the options that are needed for configuration. This class would have an #Unmatched field for the remaining args, so they are ignored by picocli. For example:
class Security {
#Option(names={"-u", "--username"})
static String username;
#Option(names={"-p", "--password"}, interactive = true, arity = "0..1")
static String password;
#Unmatched List<String> ignored;
}
In the first pass, we just want to extract the username/password info, we don't want to execute the application just yet. We can use the CommandLine.parseArgs or CommandLine.populateCommand methods for that.
So, our main method can look something like this:
public static void main(String[] args) throws Exception {
// use either populateCommand or parseArgs
Security security = CommandLine.populateCommand(new Security(), args);
if (security.username == null || security.password == null) {
System.err.println("Missing required user name or password");
new CommandLine(new ClearJdoCommand()).usage(System.err);
System.exit(CommandLine.ExitCode.USAGE);
}
// remainder of your normal main method here, something like this?
System.exit(SpringApplication.exit(SpringApplication.run(MySpringApp.class, args)));
}
I would still keep (duplicate) the usage and password options in the ClearJdoCommand class, so the application can print a nice usage help message when needed.
Note that I made the fields in the Security class static.
This is a workaround (hack?) that allows us to pass information to the getJndiContext method.
#Bean
public InitialContext getJndiContext() throws NamingException {
// Set up the namingContext for the JNDI lookup
final Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
env.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");
env.put(Context.SECURITY_PRINCIPAL, Security.username); // use info from 1st pass
env.put(Context.SECURITY_CREDENTIALS, Security.password);
return new InitialContext(env);
}
There is probably a better way to pass information to this method.
Any Spring experts willing to jump in and show us a nicer alternative?

BCryptPasswordEncoder affects startup time of server

Why does the strength of the BCryptPasswordEncoder affect the startup time of the server? There are no hashes generated at startup, so I'm wondering why this does have any effect on the startup.
Of course, I understand that checking whether a password matches takes time, but at start up is strange.
Code looks like this:
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(17); // Affects startup time tremendously
}
#Autowired
BCryptPasswordEncoder bcryptEncoder;
#Autowired
CustomUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(bcryptEncoder);
}
(Depending on your configuration)
Have a look at the spring DaoAuthenticationProvider
The following method is called at startup:
private void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.userNotFoundEncodedPassword = passwordEncoder.encodePassword(
USER_NOT_FOUND_PASSWORD, null);
this.passwordEncoder = passwordEncoder;
}
This was introduced so the server has an encoded password to verify the password against from a username attempting to authenticate doesn't exist.
/**
* The plaintext password used to perform
* {#link PasswordEncoder#isPasswordValid(String, String, Object)} on when the user is
* not found to avoid SEC-2056.
*/
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
Refer to:
https://github.com/spring-projects/spring-security/issues/2280
https://jira.spring.io/browse/SEC-2056
Note: If you set the strength to 17 and your server is taking an extra 5minutes to start, it will take your sever approximately 5minutes to verify each users password when they authenticate.
This issue is now resolved, so upgrade your Spring Boot/Spring Security if necessary.
Spring Security calls PasswordEncoder.matches() regardless of whether the user was found, so that hackers can't detect if the user existed or not by comparing response times. To create the dummy encrypted password or hash for non-existing users, Spring Security calls PasswordEncoder.encode("userNotFoundEncodedPassword") once and reuses the result. This call was previously made during startup but is now called lazily when first needed.
The constructor of BCryptPasswordEncoder is not doing anything on startup depending on the password strength :
public BCryptPasswordEncoder(int strength) {
this(strength, null);
}
public BCryptPasswordEncoder(int strength, SecureRandom random) {
if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {
throw new IllegalArgumentException("Bad strength");
}
this.strength = strength;
this.random = random;
}
Having seen this, I dont think that only changing the strength parameter can increase startup time as described.
But when you actually use the encrypter, 'strength' will for sure impact the performance. So may be you are encrypting many passwords somewhere at startup ?

How to remove existing sessions by specific principal name

I'm using Spring Session 1.3.0 with Redis backend in my project.
I have an use case that the super admin might update the roles of existing user who might already logged in. I want to delete the existing session records for those users after changing their roles.
Is there API of Spring Session to archive it?
#Autowired
private SessionRegistry sessionRegistry;
public void expireUserSessions(String username) {
for (Object principal : sessionRegistry.getAllPrincipals()) {
if (principal instanceof User) {
UserDetails userDetails = (UserDetails) principal;
if (userDetails.getUsername().equals(username)) {
for (SessionInformation information : sessionRegistry.getAllSessions(userDetails, true)) {
information.expireNow();
}
}
}
}
}
Also work out another way to clean sessions of specific user,
#Autowired
FindByIndexNameSessionRepository sessionRepository;
sessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
username).keySet().forEach(session -> sessionRepository.delete((String) session));

Implementing simple ldap user details mapper - grails

In my previous question i was working on adding roles to users logging from ldap. hopefully ive managed to register custom AuthoritiesPopulator for ldap. But now i would like to add some more functionallity to application and for that i need some more information about users than login-name.
Following this guide:
http://grails-plugins.github.io/grails-spring-security-ldap/docs/manual.106/guide/2.%20Usage.html
Im guessing i have to implement my own ldap details context mapper, and i did as shown below:
class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
// FETCHING DATA and ADDING ROLE
return new CustomUserDetails(trueUsername, null, true, true, true, true, list, email)
}
#Override
public void mapUserToContext(UserDetails arg0, DirContextAdapter arg1) {
throw new IllegalStateException("Only retrieving data from AD is currently supported")
}
}
And adding mapping in resource.config:
ldapUserDetailsMapper(amelinium1.grails.CustomUserDetailsContextMapper)
But it doesn't seem to work. Application seem to use GormUserDetailsService to return User not the Context. Am i missing something here ?
Would appreciate any help!

"Debug symbol information is required (...)" after injecting modified Spring security expression language implementation

I have an example class to test #PreAuthorize annotations, which looks more or less like this one:
class BankService {
#PreAuthorize("hasCustomRole('ROLE_CUSTOM') or hasRole('ROLE_EXAMPLE')")
Double getAccountBalance(Integer accountNumber) {
return 1234;
}
#PreAuthorize("#accountNumber > 400")
int getValue(Integer accountNumber) {
return 1234;
}
}
You can notice hasCustomRole(String expression) in the #PreAuthorize annotation, which I'm adding in:
public class CustomSecurityExpressionRoot extends SecurityExpressionRoot {
public CustomSecurityExpressionRoot(Authentication auth) {
super(auth);
}
public boolean hasCustomRole(String expression) {
return /* some magic */;
}
}
Also, I'm extending DefaultMethodSecurityExpressionHandler in the following way:
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
public CustomMethodSecurityExpressionHandler() {
super();
}
#Override
public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
StandardEvaluationContext ctx = (StandardEvaluationContext) super.createEvaluationContext(auth, mi);
ctx.setRootObject(new CustomSecurityExpressionRoot(auth));
return ctx;
}
}
In the end, everything is wrapped in resources.groovy:
beans = {
/* ... some stuff ... */
xmlns security:'http://www.springframework.org/schema/security'
security.'global-method-security'('pre-post-annotations': 'enabled') {
security.'expression-handler'(ref: 'expressionHandler')
}
expressionHandler(my.package.plugin.security.expression.CustomMethodSecurityExpressionHandler)
}
Now, if I remove the security part from resources.groovy, I naturally lose the ability to use the hasCustomRole() method, but the following works:
assert bankService.getValue(500) == 1234
But if I inject my own implementation, the previous statement causes:
Access is denied
org.springframework.security.access.AccessDeniedException: Access is denied
After further investigation I found this:
prepost.PrePostAnnotationSecurityMetadataSource Looking for Pre/Post annotations for method 'getValue' on target class 'class my.package.plugin.security.test.BankService'
prepost.PrePostAnnotationSecurityMetadataSource #org.springframework.security.access.prepost.PreAuthorize(value=#accountNumber > 400) found on specific method: public int my.package.plugin.security.test.BankService.getValue(java.lang.Integer)
method.DelegatingMethodSecurityMetadataSource Adding security method [CacheKey[my.package.plugin.security.test.BankService; public int my.package.plugin.security.test.BankService.getValue(java.lang.Integer)]] with attributes [[authorize: '#accountNumber > 400', filter: 'null', filterTarget: 'null']]
aopalliance.MethodSecurityInterceptor Secure object: ReflectiveMethodInvocation: public int my.package.plugin.security.test.BankService.getValue(java.lang.Integer); target is of class [my.package.plugin.security.test.BankService$$EnhancerByCGLIB$$c590f9ac]; Attributes: [[authorize: '#accountNumber > 400', filter: 'null', filterTarget: 'null']]
aopalliance.MethodSecurityInterceptor Previously Authenticated: org.springframework.security.authentication.TestingAuthenticationToken#b35bafc3: Principal: test; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_TELLER
method.MethodSecurityEvaluationContext Unable to resolve method parameter names for method: public final int my.package.plugin.security.test.BankService$$EnhancerByCGLIB$$c590f9ac.getValue(java.lang.Integer). Debug symbol information is required if you are using parameter names in expressions.
The interesting part is Debug symbol information is required if you are using parameter names in expressions., which suggests that classes are compiled without debug information about variable names. But everything works fine if I don't inject my own bean.
What could be the reason for the missing debugging info, and how to fix it?
It's a Grails plugin, developed for Grails 2.0.4, using spring-security-core plugin at version 1.2.7.3, spring-security-acl plugin at version 1.1, and Spring Security 3.0.7.RELEASE.
EDIT:
To make the issue more interesting, this is what I discovered later: the "missing" debug information is actually there, if you look into .class files with javap. So classes are compiled correctly, but Spring complains anyway...
I fixed the issue, however, I'm not exactly sure why the exceptions and messages in logs I had been getting were so far from the problem.
I did one mistake assuming that grails-app/conf/spring/resources.groovy can be used in similar way as in applications built with Grails. And although the documentation doesn't explicitly say that beans configured in resources.groovy won't work in this case, it states that resources.groovy (among some other files) will be by default excluded from packaging.
It doesn't explain the strange behavior while running tests, but it's not a good place for this kind of configuration.
After moving the Spring Security configuration from resources.groovy into the plugin descriptor, in the following way:
class MyOwnGrailsPlugin {
/* ... some stuff ... */
def doWithSpring = {
/* ... some spring stuff ... */
xmlns security:'http://www.springframework.org/schema/security'
security.'global-method-security'('pre-post-annotations': 'enabled') {
security.'expression-handler'(ref: 'expressionHandler')
}
expressionHandler(my.package.plugin.security.expression.CustomMethodSecurityExpressionHandler)
}
}
everything works fine and the test passes.

Resources