Spring Security in Standalone Application - spring

How do I use Spring Security in a standalone application. I just need to use the Authentication portion of Spring Security. I need to authenticate users against Windows Active Directory. There are lots of examples in the web for using spring security in Servlets but couldn't find much for using them in standalone applications.
I am only looking for something to complete this method
boolean isValidCredentials(String username, String password)
{
//TODO use spring security for authentication here..
}

You can use the ActiveDirectoryLdapAuthenticationProvider from spring-security-ldap if you just need to do authentication.
Just create a bean in your application context like:
<bean id="adAuthProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<constructor-arg value="your.domain" />
<constructor-arg value="ldap://your.ad.server" />
</bean>
Then use it like
try {
adAuthProvider.authenticate(new UsernamePasswordAuthenticationToken("user", "password"));
} catch (AuthenticationException ae) {
// failed
}

using-spring-security-in-a-swing-desktop-application
public Authentication authenticate( String username, String password ) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( username, password );
Authentication auth = _authProvider.authenticate( token );
if (null != auth) {
SecurityContextHolder.getContext().setAuthentication( auth );
_eventPublisher.publishEvent( new InteractiveAuthenticationSuccessEvent( auth, this.getClass() ) );
return auth;
}
throw new BadCredentialsException( "null authentication" );
}
I haven't tried above code by myself, but looks reasonable. Below link to javadoc for convenience SecurityContextHolder

Related

Adding basic auth headers to all the requests in spring boot

I am new to Spring boot application development.
I need to add the basic auth headers to all the api requests in spring boot.
Can any one share the valid documentation of how I proceed
It depends on what kind of auth u require
for something like self auth token it would look something like
public String controllerFunction(#RequestHeader("Auth-header") String authToken){
if (authToken == null) {
log.error("Self token authentication failed");
throw new Exception(TOKEN_NOT_FOUND);
}
if (!"auth_password".equals(authToken)) {
log.error("Self token authentication failed");
throw new Exception(AUTH_FAILED);
}
log.info("Self token authentication successful");
}
If it's unique to individual users u will have to fetch the "auth_password" from your database for that particular user and validate it
To use it in globally you can build annotations like this
#Before("#annotation(tokenValidation)")
public void beforeAdvice(TokenValidation tokenValidation) {
String authToken = request.getHeader("Auth-header");
if (authToken == null) {
log.error("Self token authentication failed");
throw new Exception(TOKEN_NOT_FOUND);
}
if (!"auth_password".equals(authToken)) {
log.error("Self token authentication failed");
throw new Exception(AUTH_FAILED);
}
log.info("Self token authentication successful");
}
U might have to look up how to implement the annotations in spring boot but this is a basic concept.
and in the controllers, u just have to do
#tokenValidation
public String controllerFunction(String authToken){
//your code;
}

Get current logged in user from Spring when SessionCreationPolicy.STATELESS is used

I want to implement this example using Keyclock server with Spring Security 5.
I'm going to use OAuth2.0 authentication with JWT token. I'm interested how I can get the current logged in user into the Rest Endpoint?
I have configured Spring Security not to store user sessions using http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);.
One possible way is to use this code:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
But I don't know is it going to work. Can someone give some advice for that case?
SecurityContextHolder, SecurityContext and Authentication Objects
By default, the SecurityContextHolder uses a ThreadLocal to store these details, which means that the security context is always available to methods in the same thread of execution. Using a ThreadLocal in this way is quite safe if care is taken to clear the thread after the present principal’s request is processed. Of course, Spring Security takes care of this for you automatically so there is no need to worry about it.
SessionManagementConfigurer consist of isStateless() method which return true for stateless policy. Based on that http set the shared object with NullSecurityContextRepository and for request cache NullRequestCache. Hence no value will be available within HttpSessionSecurityContextRepository. So there might not be issue with invalid/wrong details for user with static method
Code:
if (stateless) {
http.setSharedObject(SecurityContextRepository.class,
new NullSecurityContextRepository());
}
if (stateless) {
http.setSharedObject(RequestCache.class, new NullRequestCache());
}
Code:
Method to get user details
public static Optional<String> getCurrentUserLogin() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication()));
}
private static String extractPrincipal(Authentication authentication) {
if (authentication == null) {
return null;
} else if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
return springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
return (String) authentication.getPrincipal();
}
return null;
}
public static Optional<Authentication> getAuthenticatedCurrentUser() {
log.debug("Request to get authentication for current user");
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication());
}
sessionManagement
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
You might like to explore Methods with Spring Security to get current user details with SessionCreationPolicy.STATELESS
After the service validate the token, you can parse it, and put it into the securitycontext, it can contains various data, so you have to look after it what you need. For example, subject contains username etc...
SecurityContextHolder.getContext().setAuthentication(userAuthenticationObject);
The SecurityContextHolder's context maintain a ThreadLocal entry, so you can access it on the same thread as you write it in the question.
Note that if you use reactive (webflux) methodology, then you have to put it into the reactive context instead.

Password Policy errors not being thrown with LDAP Spring Security

I have am fairly new to Spring Security with LDAP and I am trying to authenticate with a user who has a password expired on the LDAP server(FreeIPA).
I cannot seem to trigger any password expired exception etc. The Password Policy Response Controls always return null....
Here is my code, perhaps I am doing something incorrectly. How do I handle password policy errors? They don't fire at all currently.
<bean id="freeIpaContextSource" class="org.springframework.security.ldap.ppolicy.PasswordPolicyAwareContextSource">
<constructor-arg value="${logon.freeipa.zone.ldap.connection.url}"/>
<property name="base" value="${logon.freeipa.zone.user.dn.base}"/>
</bean>
<bean id="freeIpaLdapTemplate" class="org.springframework.security.ldap.SpringSecurityLdapTemplate">
<constructor-arg name="contextSource" ref="freeIpaContextSource"/>
</bean>
I have a custom LdapAuthenticator below which uses a ldaptemplate to authenticate users.
#Override
public DirContextOperations authenticate(Authentication authentication) {
checkForIllegalStateDuringAuthentication(authentication);
logger.info(String.format("*** Beginning to authenticate against LDAP zone %s ***", authorizationZone.getName()));
zoneAuthenticationService.saveRequestDataInSession((UsernamePasswordAuthenticationToken) authentication, authorizationZone.getName());
CollectingAuthenticationErrorCallback errorCallback = new CollectingAuthenticationErrorCallback();
boolean isAuthenticated = false;
String userName = authentication.getName();
String password = authentication.getCredentials().toString();
String filterLookup = buildLDAPFilterLookupString(userName);
if (StringUtils.isNotBlank(password)) {
logger.info(String.format("*** Attempting authentication for user %s ***", userName));
try {
isAuthenticated = ldapTemplate.authenticate(StringUtils.EMPTY, filterLookup, password, errorCallback);
} catch (Exception exception) {
errorCallback.execute(exception);
}
}
if (!isAuthenticated) {
if (errorCallback.getError() == null) {
errorCallback.execute(new AuthenticationException(null));
}
//Any LDAP exception caught are stored inside the errorCallBack for use later to display an appropriate error.
logger.error(String.format("*** Authentication for user %s has failed. Exception has occurred while system performed LDAP authentication. ***", userName), errorCallback.getError());
throw new LdapAuthenticationException(errorCallback.getError().getMessage(), errorCallback.getError());
}
logger.info(String.format("*** Authentication for user %s has succeeded ***", userName));
return new DirContextAdapter(buildAuthenticatedDN(userName));
}
No matter what I do I cannot get any password policy errors to return. From my understanding you need to set a request control with PasswordPolicyControl when attempting to authenticate, but I never receive any response controls from the server. I have tried implementing something like below, but no luck on anything.
LdapContext context = (LdapContext)ldapTemplate.getContextSource().getContext(buildAuthenticatedDN(userName).toString(), password);
Control[] rctls = new Control[]{new PasswordPolicyControl(false)};
context.reconnect(rctls);
PasswordPolicyResponseControl ctrl = PasswordPolicyControlExtractor.extractControl(context);
//ctrl is always null
if (ctrl.isExpired()) {
throw new
PasswordPolicyException(ctrl.getErrorStatus());
}
What am I doing wrong? I am struggling greatly with this and any help would very much be appreciated.
If your client really sends the correct response control you might hit this issue (open since 7 years):
#1539 [RFE] Add code to check password expiration on ldap bind
IIRC FreeIPA enforces password expiry only during Kerberos pre-authc (kinit).

Setting OAuth2 token for RestTemplate in an app that uses both #ResourceServer and #EnableOauth2Sso

On my current project I have an app that has a small graphical piece that users authenticate using SSO, and a portion that is purely API where users authenticate using an Authorization header.
For example:
/ping-other-service is accessed using SSO.
/api/ping-other-service is accessed using a bearer token
Being all cloud native our app communicates with other services that uses the same SSO provider using JWT tokens (UAA), so I figured we'd use OAuth2RestTemplate since according to the documentation it can magically insert the authentication credentials. It does do that for all endpoints that are authenticated using SSO. But when we use an endpoint that is authed through bearer token it doesn't populate the rest template.
My understanding from the documentation is that #EnableOAuth2Client will only extract the token from a SSO login, not auth header?
What I'm seeing
Failed request and what it does:
curl -H "Authorization: Bearer <token>" http://localhost/api/ping-other-service
Internally uses restTemplate to call http://some-other-service/ping which responds 401
Successful request and what it does:
Chrome http://localhost/ping-other-service
Internally uses restTemplate to call http://some-other-service/ping which responds 200
How we worked around it
To work around this I ended up creating the following monstrosity which will extract the token from the OAuth2ClientContext if it isn't available from an authorization header.
#PostMapping(path = "/ping-other-service")
public ResponseEntity ping(#PathVariable String caseId, HttpServletRequest request, RestTemplate restTemplate) {
try {
restTemplate.postForEntity(adapterUrl + "/webhook/ping", getRequest(request), Map.class);
} catch (HttpClientErrorException e) {
e.printStackTrace();
return new ResponseEntity(HttpStatus.SERVICE_UNAVAILABLE);
}
return new ResponseEntity(HttpStatus.OK);
}
private HttpEntity<?> getRequest(HttpServletRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + getRequestToken(request));
return new HttpEntity<>(null, headers);
}
private String getRequestToken(HttpServletRequest request) {
Authentication token = new BearerTokenExtractor().extract(request);
if (token != null) {
return (String) token.getPrincipal();
} else {
OAuth2AccessToken accessToken = oAuth2ClientContext.getAccessToken();
if (accessToken != null) {
return accessToken.getValue();
}
}
throw new ResourceNotFound("No valid access token found");
}
In the /api/** resources there is an incoming token, but because you are using JWT the resource server can authenticate without calling out to the auth server, so there is no OAuth2RestTemplate just sitting around waiting for you to re-use the context in the token relay (if you were using UserInfoTokenServices there would be one). You can create one though quite easily, and pull the incoming token out of the SecurityContext. Example:
#Autowired
private OAuth2ProtectedResourceDetails resource;
private OAuth2RestTemplate tokenRelayTemplate(Principal principal) {
OAuth2Authentication authentication = (OAuth2Authentication) principal;
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
details.getTokenValue();
OAuth2ClientContext context = new DefaultOAuth2ClientContext(new DefaultOAuth2AccessToken(details.getTokenValue()));
return new OAuth2RestTemplate(resource, context);
}
You could probably turn that method into #Bean (in #Scope("request")) and inject the template with a #Qualifier if you wanted.
There's some autoconfiguration and a utility class to help with this pattern in Spring Cloud Security, e.g: https://github.com/spring-cloud/spring-cloud-security/blob/master/spring-cloud-security/src/main/java/org/springframework/cloud/security/oauth2/client/AccessTokenContextRelay.java
I came across this problem when developing a Spring resource server, and I needed to pass the OAuth2 token from a request to the restTemplate for a call to a downstream resource server. Both resource servers use the same auth server, and I found Dave's link helpful but I had to dig a bit to find out how to implement this. I ended up finding the documentation here, and it turn's out the implemetation was very simple. I was using #EnableOAuth2Client, so I had to create the restTemplate bean with the injected OAuth2ClientContext and create the appropriate resource details. In my case it was ClientCredentialsResourceDetails. Thanks for all great work Dave!
#Bean
public OAuth2RestOperations restTemplate (OAuth2ClientContext context) {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
// Configure the details here
return new OAuth2RestTemplate(details, context)
}
#Dave Syer
My UAA service is also an oauth2 client, which needs to relay JWT tokens coming in from Zuul. When configuring the oauth2 client the following way
#Configuration
#EnableOAuth2Client
#RibbonClient(name = "downstream")
public class OAuthClientConfiguration {
#Bean
public OAuth2RestTemplate restTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
return new OAuth2RestTemplate(resource, context);
}
}
I do get a 401 response from the downstream service as my access token has a very short validity and the AccessTokenContextRelay does not update an incoming access token (Zuul does renew expired access tokens by the refresh token).
The OAuth2RestTemplate#getAccessToken will never acquire a new access token as the isExpired on the access token stored by the AccessTokenContextRelay drops the validity and refresh token information.
How can this by solved?

Does OAuth2 allow for authorization using non-password or custom credentials?

I'm using Spring Security OAuth2. The client application (that we own) makes a "password" grant request that passes the user's username and password. Just like the draft specifies.
I need this mechanism to also support other types of credentials, like card number, PIN, and even a pre-authenticated, password not required grant.
Please keep in mind, these requests will only be permitted by a privileged client_id, one that will only be used from the application we own.
Dave, thanks for the quick response. I actually found the perfect solution, one which you took part in. It has to do with "custom-grant" token granters... https://jira.spring.io/browse/SECOAUTH-347
Had I updated my rather old 1.0.0.M5 version I might have known about those.
My approach was to extend AbstractTokenGranter with a class that supports a custom grant type (I call it "studentCard"). Once an authentication request makes it here, I examine the parameter list just like ResourceOwnerPasswordTokenGranter, but instead look for my custom "cardNumber" parameter. I then pass my own, id-based version of UsernamePasswordAuthenticationToken to my AuthenticationProvider, which knows how to authenticate users based on id card.
Here is the custom token granter class I came up with:
public class StudentCardTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "studentCard";
private final AuthenticationManager authenticationManager;
public StudentCardTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService) {
super(tokenServices, clientDetailsService, GRANT_TYPE);
this.authenticationManager = authenticationManager;
}
#Override
protected OAuth2Authentication getOAuth2Authentication(AuthorizationRequest clientToken) {
Map<String, String> parameters = clientToken.getAuthorizationParameters();
String cardNumber = parameters.get("cardNumber");
Authentication userAuth = new StudentCardAuthenticationToken(cardNumber);
try {
userAuth = authenticationManager.authenticate(userAuth);
} catch (BadCredentialsException e) {
// If the username/password are wrong the spec says we should send 400/bad grant
throw new InvalidGrantException(e.getMessage());
}
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate student: " + cardNumber);
}
return new OAuth2Authentication(clientToken, userAuth);
}
}
And my authorization server config:
<!-- Issues tokens for both client and client/user authorization requests -->
<oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices">
<oauth:refresh-token />
<oauth:client-credentials />
<oauth:password authentication-manager-ref="myUserManager" />
<oauth:custom-grant token-granter-ref="studentCardGranter" />
</oauth:authorization-server>
<bean id="studentCardGranter" class="com.api.security.StudentCardTokenGranter">
<constructor-arg name="authenticationManager" ref="myUserManager" />
<constructor-arg name="tokenServices" ref="tokenServices" />
<constructor-arg name="clientDetailsService" ref="clientDetails" />
</bean>
The spec doesn't explicitly allow direct, non-password-based exchange of user tokens between a client and the auth server directly. I think it would be quite natural to extend the password grant to other forms of authentication though. It's in the spirit of the spec, if not by the letter, so if you own both sides of the relationship there isn't much to go wrong. Spring OAuth doesn't explictly support anything that extends the password grant in this way, but its not hard to do (it's really just about the security of the /token endpoint). An alternative approach I've seen is to stick to the password grant protocol, but make the "password" a one-time token that the client can only get by knowing the user has authenticated in one of those alternative ways.

Resources