Mobilefirst 7.0 protecting a Java Adapter fails with Custom Authenticator - ibm-mobilefirst

Im following and using the sample code from Custom Authenticator and Login Module and UserAdapter from Java SQL Adapter.
I want to get the user list after authenticated.
My configuring the authenticationConfig.xml file
<realms>
<realm loginModule="CustomLoginModule" name="CustomAuthenticatorRealm">
<className>com.mypackage.MyCustomAuthenticator</className>
</realm>
</realms>
<loginModules>
<loginModule name="CustomLoginModule">
<className>com.mypackage.MyCustomLoginModule</className>
</loginModule>
</loginModules>
My configuring the Java adapter, UserAdapterResource.java file
#GET
#Produces("application/json")
#OAuthSecurity(scope="CustomAuthenticatorRealm")
public Response getAllUsers() throws SQLException{
JSONArray results = new JSONArray();
Connection con = ds.getConnection();
PreparedStatement getAllUsers = con.prepareStatement("SELECT * FROM users");
ResultSet data = getAllUsers.executeQuery();
while(data.next()){
JSONObject item = new JSONObject();
item.put("userId", data.getString("userId"));
item.put("firstName", data.getString("firstName"));
item.put("lastName", data.getString("lastName"));
item.put("password", data.getString("password"));
results.add(item);
}
getAllUsers.close();
con.close();
return Response.ok(results).build();
}
But when I invoke the procedure above on client-side, it still return a response without authentication require, while it have to show a login module

From your code you only have a challenge handler for the CustomAuthenticatorRealm realm. Why not updated your adapter and protect it with that same realm instead of using myRealm.
Updated UserAdapterResource.java skeleton
#Path("/")
public class UserAdapterResource {
// ...
#POST
#OAuthSecurity(scope="CustomAuthenticatorRealm")
public Response createUser(#FormParam("userId") String userId,
#FormParam("firstName") String firstName,
#FormParam("lastName") String lastName,
#FormParam("password") String password)
throws SQLException{
// ...
}
#GET
#Produces("application/json")
#Path("/{userId}")
public Response getUser(#PathParam("userId") String userId) throws SQLException{
// ...
}
#GET
#Produces("application/json")
#OAuthSecurity(scope="CustomAuthenticatorRealm")
public Response getAllUsers() throws SQLException{
// ...
}
// it's a good practice to protect this operation
#PUT
#Path("/{userId}")
#OAuthSecurity(scope="CustomAuthenticatorRealm")
public Response updateUser(#PathParam("userId") String userId,
#FormParam("firstName") String firstName,
#FormParam("lastName") String lastName,
#FormParam("password") String password)
throws SQLException{
// ...
}
// it's a good practice to protect this operation
#DELETE
#Path("/{userId}")
#OAuthSecurity(scope="CustomAuthenticatorRealm")
public Response deleteUser(#PathParam("userId") String userId) throws SQLException {
// ...
}
}
With these changes, when the application launches it will show the login form to authenticate before showing the list of users.
UPDATE:
The Java Adapter protection is using OAuth and so the MobileFirst server issues a token for authentication. This token has a lifespan with an expiration. Logging out of a realm doesn't affect the token.
One way to implement this based on your needs is to decrease the TTL (time to live) of your token to something like 10 or 15 seconds (or whatever you want). You can do this by setting the expirationInSeconds attribute in your login module inside authenticationConfig.xml.
authenticationConfig.xml
<!-- token will expire 10 seconds after being issued -->
<loginModule name="CustomLoginModule" expirationInSeconds="10">
<className>com.mypackage.MyCustomLoginModule</className>
</loginModule>
If 10 seconds have passed since the app connected to the server via adapter invocation or any other method then the user will need to reauthenticate.

Related

How to trigger Quarkus OIDC-Client trigger on Header Authorization: Basic

I'm working to convert a Spring application to Quarkus. I want both Bearer and Basic Authentication to work. This will also use Keycloak Authorization Service (main reason I'm moving to Quarkus).
The quarkus.oidc configuration works great and I have no problem with the Bearer Token Authorization.
The quarkus.oidc-client configuration does contain grant-type password option that I assume I could use to convert a Authorization: Basic Header to username/password and generate a Bearer Token. This is the spot I think I'm stuck.
Do I need to extend HttpAuthenticationMechanism? The extended class would invoke the oidc-client with the client credentials, grant-type=password and username/password from the Authorization: Basic header?
application.properties
quarkus.http.auth
quarkus.http.auth.basic=true
quarkus.http.auth.permission.basic-bearer.paths=/api/config/*
quarkus.http.auth.permission.basic-bearer.policy=authenticated
#end quarkus.http.auth
#quarkus.oidc
quarkus.oidc.enabled=true
quarkus.oidc.auth-server-url=${keycloakAuthServerUrl}/realms/${keycloakRealm}
quarkus.oidc.discovery-enabled=true
quarkus.oidc.client-id=${keycloakResource}
quarkus.oidc.credentials.secret=${keycloakCredentialsSecret}
#end quarkus.oidc
#quarkus.oidc-client
quarkus.oidc-client.enabled=true
quarkus.oidc-client.auth-server-url=${keycloakAuthServerUrl}/realms/${keycloakRealm}
quarkus.oidc-client.discovery-enabled=true
quarkus.oidc-client.client-id=${keycloakResource}
quarkus.oidc-client.credentials.secret=${keycloakCredentialsSecret}
quarkus.oidc-client.grant.type=password
Do I need to add SecurityIdentity below?
#Path("/api/config")
public class ConfigController {
#Inject
Tokens tokens;
String success;
public String getSuccess() {
return success;
}
#GET
#Path("")
#Produces(MediaType.APPLICATION_JSON)
public Response sendOk() throws JsonProcessingException {
success = tokens.getAccessToken();
ObjectMapper objectMapper = new ObjectMapper();
return Response.ok(objectMapper.writeValueAsString(this)).build();
}
#GET
#Path("/owners")
#Produces(MediaType.APPLICATION_JSON)
public Response sendOwners() throws JsonProcessingException {
success = tokens.getAccessToken();
ObjectMapper objectMapper = new ObjectMapper();
return Response.ok(objectMapper.writeValueAsString(this)).build();
}
}
I was trying to get the quarkus.oidc-client to manage Basic Authorization header without need to add my own HttpAuthenticationMechanism

Is it ok to add access_token authorities to the OAuth2LoginAuthenticationToken?

I have a simple spring boot application with two services - ui and resource.
I trying to configure oauth2+oidc authentication using uaa server.
When I login in the ui service, spring security creates authentication result (in OidcAuthorizationCodeAuthenticationProvider) using id_token and it doesn't contain any scopes except openid. When the authentication result is created it contains only one authority - ROLE_USER so a can't use authorization on the client side.
Is is ok to override OidcUserService and add to the user's authorities scopes from the access_token to check access on the client side?
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser user = super.loadUser(userRequest);
Collection<? extends GrantedAuthority> authorities = buildAuthorities(
user,
userRequest.getAccessToken().getScopes()
);
return new DefaultOidcUser(
authorities,
userRequest.getIdToken(),
user.getUserInfo()
);
}
Security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers("/protected/**").hasAuthority("SCOPE_protected")
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint().oidcUserService(oidcUserService())
.and()
...
It works but I'm not sure it's a good idea.
It is the approach as outlined in the Spring Security documentation, so the approach is fine.
The only thing is that when I have implemented it, I didn't add all the scopes to the authorities set - I pulled out the specific claim that had the role information - a custom groups claim that I configured in the identity provider's authorization server.
I include some example code for how to do this with Spring Webflux as most examples show how to do it with Spring MVC as per your code.
note: I'm very inexperienced with using reactor!
public class CustomClaimsOidcReactiveOAuth2UserService implements ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
private final OidcReactiveOAuth2UserService service = new OidcReactiveOAuth2UserService();
public Mono<OidcUser> loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
log.debug("inside CustomClaimsOidcReactiveOAuth2UserService..");
Mono<OidcUser> mOidcUser = service.loadUser(userRequest);
return mOidcUser
.log()
.cast(DefaultOidcUser.class)
.map(DefaultOidcUser::getClaims)
.flatMapIterable(Map::entrySet)
.filter(entry -> entry.getKey().equals("groups"))
.flatMapIterable(roleEntry -> (JSONArray) roleEntry.getValue())
.map(roleString -> {
log.debug("roleString={}", roleString);
return new OidcUserAuthority((String) roleString, userRequest.getIdToken(), null);
})
.collect(Collectors.toSet())
.map(authorities -> {
log.debug("authorities={}", authorities);
return new DefaultOidcUser(authorities, userRequest.getIdToken());
});
}
}
...
#Bean
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService() {
return new CustomClaimsOidcReactiveOAuth2UserService();
}

Spring boot ldap security

Hello I have a problem creating simple login with Ldap. I have downloaded getting started project from spring.io website: Getting started LDAP.
It is working perfectly with ldif file but I want to replace it with running ldap server. I have tried it for days with no progress. I get best results with this piece of code (replaced in WebSecurityConfig of getting started project)
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).userDetailsService(userDetailsService());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(null, "ldap://ip:port/", "ou=GROUP,dc=domain,dc=com");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
If i try to login with good username and password in format "username" "password" console output: ActiveDirectoryLdapAuthenticationProvider : Active Directory authentication failed: Supplied password was invalid
If I use "username#domain.com" and good password, page just reloads with no output to console.
If I use random username and password console: Active Directory authentication failed: Supplied password was invalid
Can someone help?
As suggested in comment I have turned on logging and found out that the problem is same with "username#domain.com" too.
Problem was in aciveDirectoryLdapAuthenticationProvider() there were 3 problems in it.
I have removed OU group from rootDn and added domain so we can use only username to log in.
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("domain.com", "ldap://ip:port/", "dc=domain,dc=com");
changed searchfilter of provider
provider.setSearchFilter("(&(objectClass=user)(sAMAccountName={0}))");
and finally I had to change ActiveDirectoryLdapAuthProvider searchForUser method because it was matching "username#domain.com" with sAMAccountName istead of "username". This:
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context,
searchControls, searchRoot, searchFilter,
new Object[] { bindPrincipal });
replaced with this:
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context,
searchControls, searchRoot, searchFilter,
new Object[] { username });
Complete aciveDirectoryLdapAuthenticationProvider:
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("domain.com", "ldap://ip:port/", "dc=domain,dc=com");
provider.setSearchFilter("(&(objectClass=user)(sAMAccountName={0}))");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
Can someone provide better solution for the second/third problem? maybe better searchfilter? I dont have any field in ldap that matches "username#domain.com" format of bindPrincipal that is using ActiveDirectoryLdapAuthProvider.

can not find entity using seam managed persistence context

I'm trying to change the authentication approach in my seam application. I currently use a login form to authenticate. In the future, I'd like to delegate the authentication to another layer that will rewrite every request with a specific HTTP header containing the username of authenticated user.
I'm facing a weird problem: when using login page to authenticate, I'm able to extract the user through the entityManager. But when I query the entityManager using the information off the header, I'm unable to find the user. The entityManager behave like the user does not exist.
I already tried two approaches:
Creating a fake login page which triggers the authentication process
Creating a servlet which gets the request and starts the
authentication process
Both times, the entityManager fails to return me any user.
I read a lot about how seam manages the persistence context, but I didn't find a single explanation which make this issue clear. Do you have any ideas? suggestions? or even guesses?
the code which uses the entityManager is the following:
#Name("userService")
#AutoCreate
public class UserService {
#Logger
private Log logger;
#In
private EntityManager entityManager;
public User getUser(String email) {
try {
return entityManager
.createQuery("SELECT u FROM User u where u.email=:email",
User.class).setParameter("email", email.trim())
.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
}
The configuration for persistence context is:
<persistence:managed-persistence-context startup="false" scope="stateless"
auto-create="true" name="entityManager" persistence-unit-jndi-name="java:/EntityManagerFactory" />
I created an empty fake login page which executes a page action (authentication) in which i get the request user header as the following:
#Name("applicationAuthenticator")
public class ApplicationAuthenticator {
#Logger
private Log log;
#In
private Identity identity;
#In
private Credentials credentials;
#In(required=true)
private UserService userService;
#Begin
public void login() throws LoginException {
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
String userName=request.getHeader("user");
identity.unAuthenticate();
credentials.setUsername(userName);
credentials.setPassword("fake");
identity.acceptExternallyAuthenticatedPrincipal(new SimplePrincipal(credentials.getUsername()));
User user=userService.getUserByEmail(credentials.getUsername());
identity.authenticate();
identity.quietLogin();
}
}
Thx in advance :-)
Thx #DaveB for your reply, the code which uses the entityManager is the following:
public User getUser(String email) {
try {
return entityManager
.createQuery("SELECT u FROM User u where u.email=:email",
User.class).setParameter("email", email.trim())
.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
The configuration for persistence context is:
<persistence:managed-persistence-context startup="false" scope="stateless"
auto-create="true" name="entityManager" persistence-unit-jndi-name="java:/EntityManagerFactory" />
I created an empty fake login page which executes a page action (authentication) in which i get the request user header as the following:
HttpServletRequest request = (HttpServletRequest) FacesContext
.getCurrentInstance().getExternalContext().getRequest();
String userName = request.getHeader("user");

Work with OData secured service

I want to generate entity classes and Service class of OData secured service.
In OData Java extension page it is written that I need to use org.restlet.ext.odata.Generator class that should get uri and output directory parameters.
But if my OData service is secured the generator instance is not able to generate service classes without username and password of the service.
I did not find any way to pass username and password to generator class.
I get 401 HTTP response code.
Please help.
In the org.restlet.ext.odata.Generator class, in the method main,
The following code would clear the credential details set in the setCredentials() method.
Service service = new Service(dataServiceUri);
if(service.getMetadata() == null)
{
errorMessage = "Cannot retrieve the metadata.";
}
Kindly provide a solution for this issue as I am currently unable to generate the classes for my rest service as the service is secured with an user password.
I tried the following code to generate the code for my secured service uri:
import org.restlet.ext.odata.Generator;
import org.restlet.ext.odata.Service;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
public class ODataRestletGenerator extends Service {
public ODataRestletGenerator(String serviceUri) {
super(serviceUri);
}
public static final String APPLICATION_URI = "http://ldcigkd.xxx.yyy.corp:50033/xxx/opu/sdata/IWCNT/CUSTOMER/";
public static void main(String[] args) {
// Add the client authentication to the call
ChallengeScheme scheme = ChallengeScheme.HTTP_BASIC;
ChallengeResponse credentials = new ChallengeResponse(scheme, "user", "pwd");
new ODataRestletGenerator(APPLICATION_URI).setauth(credentials);
String[] arguments = { APPLICATION_URI, "/customer/src" };
Generator.main(arguments);
}
private void setauth(ChallengeResponse credentials) {
super.setCredentials(credentials);
}
}
In the org.restlet.ext.odata.Service subclass that is generated by OData extension, you can call setCredentials() and pass an instance of ChallengeResponse including scheme (BASIC?), login (identifier) and password (secret).