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

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.

Related

Spring OAuth2.0: Getting User Roles based on ClientId (Authorization Code Grant Type)

I have a setup of spring boot OAuth for AuthServer and it is resposible for serving a number of few resource server for authentication using spring-security-jwt.
My problem is while authenticating I need to load the roles of a user but specific to the clientId.
eg: If user1 have roles ROLE_A, ROLE_B for client1 and ROLE_C, ROLE_D for client2, then when the user logins either using client1 or client2 he is able to see all the four roles ie. ROLE_A, ROLE_B, ROLE_C, ROLE_D because I am getting roles based on username.
If I need to have a role based on the client then I need clientId.
FYI,
I am using the authorization code flow for authentication.
I have seen similar question but that is based on password grant but I am trying on authorization code flow and that solution doesn't work for me.
Password grant question link
Below is my code where I need clientId
MyAuthenticationProvider.java
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
String userName = ((String) authentication.getPrincipal()).toLowerCase();
String password = (String) authentication.getCredentials();
String clientId = ? // how to get it
....
}
}
MyUserDetailsService.java
#Override
public UserDetails loadUserByUsername(String username) {
String clientId = ? // how to get it
....
}
}
You probably need to see OAuth2Authentication in Spring-security. When your client is authenticated by oauth2, then your "authentication" is actually instance of OAuth2Authentication that eventually implements Authentication.
If you see the implementation of OAuth2Authentication, it's done as below;
public Object getPrincipal() {
return this.userAuthentication == null ? this.storedRequest.getClientId() : this.userAuthentication
.getPrincipal();
}
so if request included "clientId', then you should be able to get clientId by calling getPrincipal() and typecasting to String as long as your request didn't include user authentication.
For your 2nd case, username is actually considered as clientId. You need to call in-memory, RDBMS, or whatever implementation that has clientId stored and returns ClientDetails. You'll be able to have some idea by looking into Spring security's ClientDetailsUserDetailsService class.
Since I didn't get any appropriate solution for my question, I am posting the solution that I used after digging source code and research.
MyJwtAccessTokenConverter.java (Extend JwtAccessTokenConverter and implement enhance method)
public class OAuthServerJwtAccessTokenConverter extends JwtAccessTokenConverter {
....
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String clientId = authentication.getOAuth2Request().getClientId();
// principal can be string or UserDetails based on whether we are generating access token or refreshing access token
Object principal = authentication.getUserAuthentication().getPrincipal();
....
}
....
}
Info:
In enhance method, we will get clientId from authentication.getOAuth2Request() and userDetails/user_name from authentication.getUserAuthentication().
Along with JwtAccessTokenConverter, AuthenticationProvider and UserDetailsService are required for authentication in generating access token step and refresh token step respectively.
get authorization header from request then parse from base64 to get the client-id.
something like this:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes())
.getRequest();
String authHeader = request
.getHeader("Authorization");

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).

Spring: Check in code if url has security set to none

It is possible to check in Spring Interceptor preHandle() method if requested URL is secured by Spring Security or not (has set security="none") ?
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(isSecured(request) && !paymentRegistered())
response.sendRedirect("/payment")
return super.preHandle(request, response, handler);
}
private boolean isSecured(HttpServletRequest request){
//how to check if url has security=none
}
My problem is that after successful login I want to check if user has payed for service. If not I want to redirect to payment page. My idea is to write custom request scope filter or interceptor and check if user has registered payment in database. Problem is that I do not want to filter non secured URLs such as resources, login page, error pages etc. Also payment page (which is secured) should be available.
Maybe better idea is to write custom security filter and add custom flag to Principal object such as servicePayed alongside with other security flags: enabed, accountNonExipired etc.
I would do it writing a custom AuthenticationSuccessHandler, mainly based in the simple implementation SimpleUrlAuthenticationSuccessHandler.
In your implementation, you should overwrite onAuthenticationSuccess method, and there check if you should redirect the user to the payment page or not.
/**
* Calls the parent class {#code handle()} method to forward or redirect to the target
* URL, and then calls {#code clearAuthenticationAttributes()} to remove any leftover
* session data.
*/
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
if(mustCompletePayment(authentication)){
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
}
Then just write a kind of mustCompletePayment using the authentication object, from which you must be able to check if the user must complete payment or not, or if you already made a custom UserDetailsService to check it during authentication, just check that indicator in your authentication object
EDIT:
If what you really want to do is to avoid any action for the logged user while he does not complete the payment, I would manage with granted authorities.
As I see, the key here is to translate the fact that the user has yet not paid into the authorization layer in a way you could take advantage of it.
You already have implemented the logic to discover if a user has completed payment information or not, so you could write your own UserDetailsService, so in the
UserDetails loadUserByUsername(String username)throws UsernameNotFoundException
you could check that and in case the user has not complete the payment, just erase any returning granthedAuthority from the UserDetails and let only one stating that the user must complete the payment, let's say ROLE_USER_HAS_NOT_PAID.
Then, in security http config (this is xml version but maybe you are using java config), make such a kind of mappings:
<security:http ...>
...
<security:intercept-url pattern="/secured/payment/**" access="ROLE_USER,ROLE_USER_HAS_NOT_PAID" />
<security:intercept-url pattern="/secured/**" access="ROLE_USER_HAS_PAID" />
...
</security:http>
With this config, payment page would be accessible for any user, wherever the user has paid or not, while other pages are available only for users who had already paid. Only, be carefull as you must renew the user's granthed authorities once the user has paid to made him available every page.
This way, the AuthenticationSuccessHandler should not eval other than the user granthed authorities to decide where to redirect the user. I have made this several times by building a AuthenticationSuccessHandler based on a ordered map where I configured a landing page for each of the granthed authorities which need their own landing page.
Now any logged user action is forbidden if he has cont complete payment, so a HTTP 403 would be raised while trying to access any other secured resource. But you want don't want just to block the user from doing anything else, you want to redirect it to the payment page. Here you need an AccessDeniedHandler, where you could do more or less the same check:
public class CustomAuthenticationAccessDeniedHandler extends
AccessDeniedHandlerImpl implements AccessDeniedHandler {
private String errorPage = "/error/403";
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void handle(HttpServletRequest arg0, HttpServletResponse arg1,
AccessDeniedException arg2) throws IOException, ServletException {
SecurityContext context = SecurityContextHolder.getContext();
if(context.getAuthentication() != null && context.getAuthentication().isAuthenticated()){
if(context.getAuthentication().getAuthorities().contains("ROLE_USER_HAS_NOT_PAID")){
this.redirectStrategy.sendRedirect(arg0, arg1, "/secured/payment/pay");
return;
}
}
this.redirectStrategy.sendRedirect(arg0, arg1, this.getErrorPage());
}
public RedirectStrategy getRedirectStrategy() {
return redirectStrategy;
}
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
#Override
public void setErrorPage(String errorPage) {
this.errorPage = errorPage;
}
public String getErrorPage() {
return errorPage;
}
}
This way you would redirect users which still must pay to your payment page and in any other case to a default 403 page
Don't know if there's a way to get such information from Spring Security. But maybe if you do not have a lot of urls which are not secured than you can do something like this:
private boolean isSecured(HttpServletRequest request) {
String requestUri = request.getRequestURI();
return !(request.getMethod().equalsIgnoreCase("GET")
&& (requestUri.contains("error/")
|| requestUri.startsWith("resources/"))
);
}
Or move those non-secured resources to some common start path and use the idea described in the code above.
Maybe you will find a way to do that, but IMHO you should not, because it is likely to require to dive in Spring Security internals.
If you want to only use Spring Security the way it was designed for, you could implement a custom AccessDecisionVoter. For example, if could only vote for one single security attributes starting with PAYMENT. You put that security attribute in spring security configuration:
<security:intercept-url pattern="/..." access="PAYMENT,ROLE_ADMIN"/>
to restrict access to user having payed or having the ADMIN role
To declare a custom voter, you must replace the default access decision manager. First you declare it:
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg>
<list>
<bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
<bean class="org.springframework.security.access.vote.RoleVoter"/>
<bean class="your.package.PaymentVoter"/>
</list>
</constructor-arg>
</bean>
Then you insert it in your <http> element:
<http access-decision-manager-ref="accessDecisionManager"/>

Spring Security in Standalone Application

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

Spring Security: put additional attributes(properties) in the session on success Authentication

Just simple question: what is the best way to add attributes(properties) to the HttpSession on success authentication? The userID for example.
For now i'm using my own SimpleUrlAuthenticationSuccessHandler implementation in UsernamePasswordAuthenticationFilter and doing it like this:
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication auth)
throws IOException, ServletException {
PersonBean person = (PersonBean) auth.getPrincipal();
request.getSession().setAttribute("currentUserId", person .getId().toString());
super.onAuthenticationSuccess(request, response, auth);
But I dont think this is good approach as there is another ways to do authentication(RememberMe for example).
So what do I need to use here?
The answer was given on spring forum. Link.
Generally, need to implement an ApplicationListener which listens for succes events and put additional attributes in the session there.
But in my case its not required to store attributes in the session. I can retrieve userID like here:
var userId = ${pageContext.request.userPrincipal.principal.id}
Spring does all this for you, you'll have to create a table *persistent_logins*, here is a snippet from app context that might help. And the official doc's lay describe in detail what is required :
<security:http auto-config='true'>
<security:intercept-url pattern="/**" access="ROLE_USER" />
<security:form-login login-page="/Login"
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-url="/Login?login_error=1" />
<security:remember-me data-source-ref="dataSource"
user-service-ref="myUserService" />
</security:http>
and then you can access the principal object from your anywhere in your app, eg below shows the tag to output username in jsp :
<sec:authentication property="principal.username" />
and from your java code this can be done :
MyUser user = (MyUser) authentication.getPrincipal();

Resources