Custom SAMLUserDetailsService not populating custom UserDetails - spring-security

I have a Spring project and I'm converting my current authentication to use SAML2.
I have everything working as far as authentication, but I'm having difficulty in getting the SAML2 extension to insert my custom UserDetails object into the Spring Security Context authentication object.
I have a custom UserDetailsService, defined below:
public class SAMLAuthManager implements SAMLUserDetailsService {
private static final Logger logger = Logger.getLogger(JDBCAuthManager.class);
#Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
logger.info("Credential attributes: " + credential.getAttributes());
for (int x = 0; x < credential.getAttributes().size(); x++) {
Attribute attr = credential.getAttributes().get(x);
List<XMLObject> attrValues = attr.getAttributeValues();
StringBuilder strBuilder = new StringBuilder();
for (int g = 0; g < attrValues.size(); g++) {
XMLObject currObj = attrValues.get(g);
strBuilder.append(currObj.toString()).append(",");
}
strBuilder.deleteCharAt(strBuilder.length() - 1);
logger.info(attr.getFriendlyName() + ", " + strBuilder.toString());
}
String username = credential.getNameID().getValue();
userWrapper.setStaff(s);
logger.info("Returning wrapper: " + userWrapper);
return userWrapper;
} else {
return null;
}
}
}
I have also configured this userDetails in my security context config:
<bean id="samlAuthenticationProvider" class="org.springframework.security.saml.SAMLAuthenticationProvider">
<property name="userDetails" ref="samlUserDetails" />
</bean>
However, when I inspect the SecurityContextHolder, post authentication, this line:
SecurityContextHolder.getContext().getAuthentication().getCredentials();
returns an object of type org.springframework.security.saml.SAMLCredential.
I checked to see if Spring populated the Principal with the custom object (SecurityContextHolder.getContext().getAuthentication().getPrincipal()) but it did not, that's just a String with the username populated.
Any ideas?
Thanks

The principal is by default forced to be String (in order to always permit replication of Principal which was earlier an un-serializable NameID).
This can be changed by setting forcePrincipalAsString in SAMLAuthenticationProvider to false, which will make Spring SAML include your object provided by SAMLUserDetailsService as principal in the Authentication object.
The result of call to SAMLUserDetailsService is always available under SecurityContextHolder.getContext().getAuthentication().getDetails().

Related

Spring Security 4 issue using Query Method

I am using the latest spring security 4 version and it introduces a new feature to use the logged in user details directly in the query method using expression language. Here is my spring data repository code:
public interface UserRepository extends JpaRepository<User, Long> {
#Query("select username from User u where u.username = ?#{ principal?.username }")
User findByUsername(String username);
}
In the above code, I have an entity User as below:
#Entity
#Table(name = "users")
public class User {
#Id
#Column(name = "username", nullable = false, unique = true)
private String username;
#Column(name = "password", nullable = false)
#NotNull
private String password;
#Column(name = "enabled", nullable = false)
#NotNull
private Boolean enabled;
#Column(name = "role", nullable = false)
#Enumerated(EnumType.STRING)
private Role role;
//getters and setters
Also I have this entry for enabling this feature:
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
When I run the application, I get the error:
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Authentication object cannot be null; nested exception is java.lang.IllegalArgumentException: Authentication object cannot be null
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:381)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:223)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:417)
Caused by: java.lang.IllegalArgumentException: Authentication object cannot be null
at org.springframework.security.access.expression.SecurityExpressionRoot.<init>(SecurityExpressionRoot.java:46)
at org.springframework.security.data.repository.query.SecurityEvaluationContextExtension$1.<init>(SecurityEvaluationContextExtension.java:113)
at org.springframework.security.data.repository.query.SecurityEvaluationContextExtension.getRootObject(SecurityEvaluationContextExtension.java:113)
at org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider$EvaluationContextExtensionAdapter.<init>(ExtensionAwareEvaluationContextProvider.java:463)
at org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider.toAdapters(ExtensionAwareEvaluationContextProvider.java:210)
at org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider.access$000(ExtensionAwareEvaluationContextProvider.java:58)
What could be the issue. Here I am posting to check if there is any issue in using the query method. Can i use like principal.username, is that correct?
Update: When I removed the #Query from repository it works fine. That means its problem with the new spring security 4 using principal.username. Is there anything wrong in this expression?
#Query("select username from User u where u.username = ?#{ principal?.username }")
Pls try this custom class :-
class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport {
#Override
public String getExtensionId() {
return "Security";
}
#Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication){};
}
}
Not sure whether you have solved the issue in the meantime, but I noticed that your query should look like:
select u from User u where u.username = ?#{ principal }
assuming your principal object is the plain username String.
If you created your own SecurityEvaluationContextExtension class, and did not implement getAuthentication() method, you might be getting this exception.
In this link, you can see original SecurityEvaluationContextExtension.java file, that implements all necessary methods.
So, you don't need to implement this class on your own. Instead, you can add below dependency to your pom file to have the original one;
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-data -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-data</artifactId>
<version>4.2.1.RELEASE</version>
</dependency>
If you are using any other dependency manager rather than maven, you can goto related maven repo and get the definition that you want.
I hope this helps.

Authentication of SOAP messages

I have searched for a good example and cannopt find one. I want to take the username and password from the SOAP header, and set the spring security context after I authenticate using our exisiting service methods. I have implemented the Wss4jSecurityInterceptor and it validates the header element. WHat I need to do in the callback, or some other mechanism, is create an uthetication context so I can access it later in our endpoint.
However, I dont think that the callback is the correct place to do it, as I keep getting password supplied no password errors. I am new to spring security and integration.
Config:
<bean id="SOAPSecurityInterceptor" class="com.ps.snt.ws.interceptor.SOAPSecurityInterceptor">
<property name="validationActions" value="UsernameToken"/>
<property name="validationCallbackHandler" ref="callbackHandler"/>
</bean>
<bean id="callbackHandler" class="com.ps.snt.ws.interceptor.SOAPSecurityValidationCallbackHandler">
</bean>
callback:
public class SOAPSecurityValidationCallbackHandler extends SimplePasswordValidationCallbackHandler {
#Override
protected void handleUsernameToken(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException {
System.out.println("In security callback " + callback.getPassword());
boolean valid = true;
String token = callback.getIdentifier();
String password = callback.getPassword();
Integer zoneID = null;
String username = null;
StringBuffer errorMessages = new StringBuffer();
if(StringUtils.isEmpty(token)) {
errorMessages.append("Username token cannot be empty");
valid = false;
} else {
Pattern pattern = Pattern.compile("^[\\w]+\\d\\d\\d\\d\\d");
Matcher matcher = pattern.matcher(token);
if(!matcher.matches()) {
valid = false;
errorMessages.append("Username token must be in the format 'user#zone'.");
}
else {
String[] parts = token.split("#");
username = parts[0];
zoneID = Integer.parseInt(parts[1]);
}
}
if(StringUtils.isEmpty(password)) {
errorMessages.append("Password cannot be empty.");
valid = false;
}
if(valid && username != null && zoneID != null) {
LoginService loginService = new LoginService();
LoginContextDO loginContextDO = loginService.getAuthenticatedLoginContext(username, password, zoneID);
AbstractAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password);
authentication.setDetails(loginContextDO);
authentication.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
System.out.println("Authetnication failed!");
}
}
}
My requirements are simple:
- validate the SOAP header (works)
- retrieve the username and password
- call our legacy service to create our login context
- set the spring security context (with logincontext as details) so I can use later in an endpoint
What mechanism can I use to validate the soap header and set a security context from that header?
SpringSecurityPasswordValidationCallbackHandler is for you. From Spring WS docs:
The SpringSecurityPasswordValidationCallbackHandler validates plain text and digest passwords using a Spring Security UserDetailService to operate. It uses this service to retrieve the (digest of ) the password of the user specified in the token. The (digest of) the password contained in this details object is then compared with the digest in the message. If they are equal, the user has successfully authenticated, and a UsernamePasswordAuthenticationToken is stored in theSecurityContextHolder. You can set the service using the userDetailsService. Additionally, you can set a userCache property, to cache loaded user details.
<beans>
<bean class="org.springframework.ws.soap.security.wss4j.callback.SpringDigestPasswordValidationCallbackHandler">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
<bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
...
</beans>

Keep users in Config.groovy list in Grails

Is there any way to define the users that can use my application in a list in Config.groovy? This will be using Grails 2.2.3 and the latest versions of Spring Security Core and Spring Security LDAP.
We use Active Directory for authentication, and only 2 or 3 people will use this little application, so it doesn't seem worthy of making an AD Group for just this app. It would be simpler to define a list, and any time there is a new hire instead of adding them to the AD group all I have to do is add their name to the external Grails config.
I would like to do something like the following:
SomeController.groovy
#Secured("authentication.name in grailsApplication.config.my.app.usersList")
class SomeController {
}
Then in Config.groovy put this code:
my.app.usersList = ['Bill', 'Tom', 'Rick']
Is this possible? If so, is this a terrible idea? Thanks a lot.
That seems really silly. Why not have the list of users in a table? Then you can add/remove from that table without have to modify the application.
I currently do this and in my UserDetailsContextMapper I make sure the username already exists in the Users table.
You need a custom authenticator that will try to access your Active Directory and if authenticated, will look into Grails properties to check if the username is allowed to login.
This is the class that I use. I changed the code to validate the config:
class ActiveDirectoryAuthenticator {
private DefaultSpringSecurityContextSource contextFactory
private String principalSuffix = ""
def grailsApplication
public DirContextOperations authenticate(Authentication authentication) {
// Grab the username and password out of the authentication object.
String principal = authentication.getName() + "#" + principalSuffix
String password = ""
if (authentication.getCredentials() != null) {
password = authentication.getCredentials().toString()
}
// If we have a valid username and password, try to authenticate.
if (!("".equals(principal.trim())) && !("".equals(password.trim()))) {
try {
String provider = contextFactory.getUrls()[0]
Hashtable authEnv = new Hashtable(11)
authEnv.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory")
authEnv.put(Context.PROVIDER_URL, provider)
authEnv.put(Context.SECURITY_AUTHENTICATION, "simple")
authEnv.put(Context.SECURITY_PRINCIPAL, principal)
authEnv.put(Context.SECURITY_CREDENTIALS, password)
javax.naming.directory.DirContext authContext = new InitialDirContext(authEnv)
//here validate the user against your config.
if(!authentication.getName() in grailsApplication.config.adUsersAllowed) {
throw new BadCredentialsException("User not allowed.")
}
DirContextOperations authAdapter = new DirContextAdapter()
authAdapter.addAttributeValue("ldapContext", authContext)
return authAdapter
} catch ( NamingException ex ) {
throw new BadCredentialsException(ex.message)
}
} else {
throw new BadCredentialsException("Incorrect username or password")
}
}
public DefaultSpringSecurityContextSource getContextFactory() {
return contextFactory
}
/**
* Set the context factory to use for generating a new LDAP context.
*
* #param contextFactory
*/
public void setContextFactory(DefaultSpringSecurityContextSource contextFactory) {
this.contextFactory = contextFactory
}
public String getPrincipalSuffix() {
return principalSuffix
}
/**
* Set the string to be prepended to all principal names prior to attempting authentication
* against the LDAP server. (For example, if the Active Directory wants the domain-name-plus
* backslash prepended, use this.)
*
* #param principalPrefix
*/
public void setPrincipalSuffix(String principalSuffix) {
if (principalSuffix != null) {
this.principalSuffix = principalSuffix
} else {
this.principalSuffix = ""
}
}
}
Declare it as your ldapAuthenticator in resources.groovy:
ldapAuthenticator(ActiveDirectoryAuthenticator) {
contextFactory = ref('contextSource')
principalSuffix = 'domain.local' //your domain suffix
grailsApplication = ref('grailsApplication')
}
The downside is that you need to restart your context when you change config.groovy
In your controllers just use #Secured('IS_AUTHENTICATED_FULLY')
I do not think you can do that because annotations are resolved at compile time and not in runtime. Config properties will be read during the application runtime so you I fear you have to end up doing:
#Secured(["authentication.name in ['Bill', 'Tom', 'Rick']"])
class SomeController {
}
If I remember correctly the #Secured annotation cannot be used for other things than comparing roles. But you should be able to do this with spring securities #PreAuthorize and #PostAuthorize annotations. When using grails the easiest way to setup these annotations is installing the spring security ACL plugin.
Within #PreAuthorize and #PostAuthorize you can use SPEL expressions which are more flexible. Unfortunatelly SPEL does not provide an in operator. However you can delegate the security check to a service:
#PreAuthorize('#securityService.canAccess(authentication)')
public void test() {
println "test?"
}
With the # symbol you can reference other beans like services within expression. Here the method securityService.canAccess() is called to evaluate if the logged in user can access this method.
To use this you have to configure a BeanResolver. I wrote some more details about configuring a BeanResolver here.
Within securityService you can now do:
class SecurityService {
def grailsApplication
public boolean canAccess(Authentication auth) {
return grailsApplication.config.myList.contains(auth.name)
}
}
In general I would not recommend to use a configuration value for validating the user in security checks. The groovy configuration will be compiled so you cannot easily add a new user without redeploying your application.

How to set expire_in in OAUTH 2.0?

I am using OAuth 2.0 with spring for token generation and I want to set expire_in manually so token can expire as per my criteria. Any one help me?
This is my response:
{
access_token: "c7a6cb95-1506-40e7-87d1-ddef0a239f64"
token_type: "bearer"
expires_in: 43199
scope: "read"
}
It can be set with a ClientBuilder obtained from a ClientDetailsServiceConfigurer.
#Configuration
#EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client")
.secret("secret")
.authorizedGrantTypes("authorization_code", "refresh_token", "password")
.scopes("app")
.accessTokenValiditySeconds(30);
}
// ... additional configuration
}
or directly on DefaultTokenServices depending on your need.
#Configuration
#EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// optionally here you could just get endpoints.getConsumerTokenService()
// and cast to DefaultTokenServices and just set values needed
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAccessTokenValiditySeconds(60);
endpoints.tokenServices(tokenServices);
}
}
configure your oauth configuration changing your Bean TokenServices and setting accessTokenValiditySeconds property :
<bean id="tokenServices"
class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
<property name="accessTokenValiditySeconds" value="1" />
<property name="tokenStore" ref="tokenStore" />
<property name="supportRefreshToken" value="true" />
<property name="clientDetailsService" ref="clientDetails" />
</bean>
You can also configure the DefaultTokenServices in the application.yaml file.
security:
oauth2:
client:
clientId: client-id
clientSecret: client-secret
authorized-grant-types: authorization_code,refresh_token,password
scope: openid
access-token-validity-seconds: 30
Create a custom class of AuthorizationCodeAccessTokenProvider and override the parent
public method obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
In the overridden method of your custom class, call upon the program logic of its parent class:
DefaultOAuth2AccessToken token = super.obtainAccessToken(details, request);
This will return an AccessToken.
Now, you just have to manipulate the expired value of that token directly, by providing a timestamp from the past
token.setExpiresIn(int timestamp)
Also was searching for this answer and tried proposed solution from DeezCashews. But it didn't work for me, because there is a part of code which firstly check if this value is set in in column access_token_validity table oauth_client_details and only then greps value from tokenServices. So if your "expires_in" is set in oauth_client_details table, then you need to change it there.
Code which checks validity property in db :
protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {
if (clientDetailsService != null) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
Integer validity = client.getAccessTokenValiditySeconds();
if (validity != null) {
return validity;
}
}
return accessTokenValiditySeconds;
}
If you are using grails security oauth2 provider
you can only change grails-app/conf/spring/resources.groovy
import org.springframework.security.oauth2.provider.token.DefaultTokenServices
// Place your Spring DSL code here
beans = {
tokenServices(DefaultTokenServices){
accessTokenValiditySeconds = 600;
tokenStore = ref('tokenStore')
supportRefreshToken = true;
clientDetailsService = ref('clientDetailsService')
}
}
As such I don't think there is any policy to do that so. But there is one way which can lead to success.
Just use refresh_token API to make the current access_token invalid. :D
Simple is that.
public interface OAuth2AccessToken {
public static String BEARER_TYPE = "Bearer";
public static String OAUTH2_TYPE = "OAuth2";
/**
* The access token issued by the authorization server. This value is REQUIRED.
*/
public static String ACCESS_TOKEN = "access_token";
/**
* The type of the token issued as described in <a
* href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-7.1">Section 7.1</a>. Value is case insensitive.
* This value is REQUIRED.
*/
public static String TOKEN_TYPE = "token_type";
/**
* The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will
* expire in one hour from the time the response was generated. This value is OPTIONAL.
*/
public static String EXPIRES_IN = "expires_in";
/**
* The refresh token which can be used to obtain new access tokens using the same authorization grant as described
* in Section 6. This value is OPTIONAL.
*/
public static String REFRESH_TOKEN = "refresh_token";
/**
* The scope of the access token as described by <a
* href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.3">Section 3.3</a>
*/
public static String SCOPE = "scope";
/**
* The additionalInformation map is used by the token serializers to export any fields used by extensions of OAuth.
* #return a map from the field name in the serialized token to the value to be exported. The default serializers
* make use of Jackson's automatic JSON mapping for Java objects (for the Token Endpoint flows) or implicitly call
* .toString() on the "value" object (for the implicit flow) as part of the serialization process.
*/
Map<String, Object> getAdditionalInformation();
Set<String> getScope();
OAuth2RefreshToken getRefreshToken();
String getTokenType();
boolean isExpired();
Date getExpiration();
int getExpiresIn();
String getValue();
}

Is it possible to use an object's attribute value to decide access?

I am new to Spring Security. I have been working on creating a custom voter that will decide whether to grant permission or not based on the value of an attribute of the object. That is, if object instance A has attribute X with value i, user with ROLE_MGR has access. If object instance B has value j in the X attribute, then ROLE_MGR does not have access. is it possible to do that and if so, what do I need to do? if this is not possible we may decide not to use Spring Security.
Thats possible, but first have a look at Spring Securitys domain object security. This is used to grant fine grained access on your objects, see here: http://static.springsource.org/spring-security/site/docs/3.0.x/reference/domain-acls.html
I figured it out. I need to use a custom permission evaluator. The snippets from my code are provided below for anyone that might be trying to do something similar:
security.xml
<security:global-method-security
pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler" />
</security:global-method-security>
<bean id="expressionHandler"
class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator">
<bean id="permissionEvaluator"
class="org.krams.tutorial.infrastructure.SomePermissionsEvaluator" />
</property>
</bean>
Service Interface
#PostFilter("hasPermission(filterObject, 'READ')")
public List getAll();
Custom Permissions Evaluator
#Override
public boolean hasPermission(Authentication authorities,
Object targetDomainObject, Object permission) {
boolean Decision = false;
System.out.println("Initial Decision: " + Decision);
Date cutoffDate = null;
try {
cutoffDate = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH)
.parse("January 1, 2012");
System.out.println("Cutoff Date: " + cutoffDate.toString());
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("Domain Object Date: "
+ Post.class.cast(targetDomainObject).getDate());
if (Post.class.cast(targetDomainObject).getDate().before(cutoffDate)) {
Decision = false;
System.out.println("In before");
} else {
Decision = true;
System.out.println("In after");
}
System.out.println("Final Decision: " + Decision);
System.out.println("--------");
return Decision;
}

Resources