How to change the content of the 401 response to individual format? - jax-rs

I have a JAX-RS application on WildFly 10 which shall be secured by a simple Basic Auth.
It works so far, but if the authentication fails, the server responds with
<html>
<head>
<title>Error</title>
</head>
<body>Unauthorized</body>
</html>
which is not my desired response. I would prefer a customized (json) response.
How to do that?
What I did so far:
I configured a new Wildfly security domain in my server configuration with a simple UserRolesLoginModule (which is sufficient in my case):
<security-domain name="MySecurityDomain" cache-type="default">
<authentication>
<login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required">
<module-option name="usersProperties" value="${jboss.server.config.dir}/users.properties"/>
<module-option name="rolesProperties" value="${jboss.server.config.dir}/roles.properties"/>
<module-option name="hashAlgorithm" value="MD5"/>
<module-option name="hashEncoding" value="base64"/>
<module-option name="hashCharset" value="UTF-8"/>
<module-option name="unauthenticatedIdentity" value="UnauthenticatedAccess"/>
</login-module>
</authentication>
</security-domain>
I annotated all services in the app:
#SecurityDomain("MySecurityDomain")
#RolesAllowed({ "RoleFromPropertyFile", "AnotherRoleFromPropertyFile" })
I created a jboss-web.xml with the content
<jboss-web>
<security-domain>MySecurityDomain</security-domain>
</jboss-web>
I have a web.xml where I tried a lot of different things without any success... :-(
Current content:
<security-constraint>
<display-name>Deny all HTTP methods except GET and POST</display-name>
<web-resource-collection>
<web-resource-name>NextTest</web-resource-name>
<url-pattern>/mypattern/*</url-pattern>
<http-method-omission>GET</http-method-omission>
<http-method-omission>POST</http-method-omission>
</web-resource-collection>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>MySecurityRealm</realm-name>
</login-config>
<security-role>
<description>Access to all application parts</description>
<role-name>all</role-name>
</security-role>
<!-- and some more roles -->
I also implemented a ExceptionMapper<EJBAccessException> to generate my own response. But this mapper is only reached when I remove all content of web.xml.
My guess is that undertow is doing the authorization and handles the response on unauthorized access. If I remove the security configuration in the web.xml, the EJBs are accessed, but without evaluating the BasicAuth header. In this case, all requests are denied.
I possible I would avoid to write a Servlet and use an ExceptionMapper instead.
Any ideas what I missed?

I did a little experiment with some code and, while it's not pretty, you could try something like:
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
#Provider
public class AuthBodyResponseFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
if((responseContext.getStatus() == 401) &&
(responseContext.getEntity() instanceof String))
responseContext.setEntity("no services for you!");
}
}
I tested it a bit and it seems to work. Of course, the challenge is where else is there a 401 with a String response body? I'd have to test more to see if this covers everything.

Here is how I do it:
#POST
#Consumes("application/json")
#Produces("application/json")
public Response create(Entity entity) {
try {
Entity created = service().create(entity);
return Response.created(location(created)).entity(created).build();
} catch (ServiceException e) {
return Response.status(e.getStatus()).entity(e).build();
}
}
Notice the return type, Response. This allows you to customize the response, including setting headers etc. It also means you have to write some more wiring code.
I'm using a custom ServiceException here that already has the status in it and use it to set the response code. Then I pass the exception itself which will be returned as JSON.

Related

Failed (access denied response) to execute rest endpoint with annotation #RolesAllowed after a succesfull login with LoginModule JBoss Approach

I have a trivial problem about execute endpoint resteasy with a trusted SecurityDomain and a specific RolesAllowed.
After a successfull login with loginmodule approach with a login form , the response of endpoint is an access denied (HTTP Status 403 - Access to the requested resource has been denied)
Now I describe the actual case use:
Environment is Jboss AS7, there is an .ear artifact with following configuration
standalone.xml
<management>
...
<security-realm name="EJBRealm">
<authentication>
<jaas name="CustomRealm"/>
</authentication>
</security-realm>
...
</management>
<subsystem xmlns="urn:jboss:domain:security:1.1">
...
<security-domain name="CustomRealm">
<authentication>
<login-module code="Database" flag="sufficient">
<module-option name="dsJndiName" value="java:jboss/jdbc/PUDS"/>
<module-option name="principalsQuery" value="SELECT 'system' FROM dual WHERE ? = 'system'"/>
<module-option name="rolesQuery" value="SELECT 'authenticated', 'Roles' from dual WHERE ? = 'system'"/>
</login-module>
<login-module code="custom.jaas.AuthenticationProxyLoginModule" flag="sufficient" module="custom.authentication">
<module-option name="authBE_ip_port" value="${install.module.authBE_ip_port}"/>
<module-option name="authBE_ip_address" value="${install.module.authBE_ip_address}"/>
<module-option name="authBE_context_path" value="${install.module.authBE_context_path}"/>
</login-module>
</authentication>
</security-domain>
...
</subsystem>
In this ear there is a web-module artifact .war with a set of endpoint with resteasy approach with following configuration:
web.xml
<context-param>
<param-name>resteasy.role.based.security</param-name>
<param-value>true</param-value>
</context-param>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.html</form-login-page>
<form-error-page>/login.html</form-error-page>
</form-login-config>
</login-config>
<security-constraint>
<web-resource-collection>
<web-resource-name>Secured Content</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>ADMIN</role-name>
</security-role>
This role exist on database autentication realm
jboss-web.xml
<jboss-web version="7.1"
xmlns="http://www.jboss.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/schema/jbossas/jboss-web_7_1.xsd">
<security-domain>CustomRealm</security-domain>
</jboss-web>
On jboss-web.xml I setting the customrealm defined on standlalone.xml
The resteasy class is defined as followed:
#Component
#Path(value = "/endpoint")
#SecurityDomain("CustomRealm")
#DeclareRoles({"ADMIN", "DEFAULT"})
public class CustomRest implements ICustomRest
{
...
#Override
#GET
#Path(value = "/testendpoint/{id}")
#Consumes(value = MediaType.APPLICATION_JSON)
#RolesAllowed("ADMIN")
public void testendpoint(#PathParam(value = "id") Long id) throws Exception {
//code to execute
}
...
}
This class is annotated with securitydomain at class scope and on method testendpoint define the annotation #RolesAllowed with ADMIN (as defined on web.xml)
If I call the rest uri
http://localhost:8080/api/services/endpoint/testendpoint/23456
the login form is viewed, I insert correct credentials that received from custom.jaas.AuthenticationProxyLoginModule module correctly. The autentication is ok after a successfull login as aspect it.
After all ok, the endpoint don't execute but the response is Access Denied systematically.
What's my wrong?
Login module is configured correctly on standlone.xml, the login form is viewed correctly, the submit credentials is received correctly from custom loginmodule, the method login grant ok authentication, but in the final the response of endpoint is an access denied!!!! Why? It's very trivial and I have got nothing to resolve this trivial problem!
It's all ok , but access denied! I'm sure there is a few wrong that I don't able to understand!
Thanks in advances for a response!
Ok! I find the wrong!!!
I analyzed the code of custom login module and I realized that the native method of loginmodule getRoleSets define a custom role called "authenticated" and not retrieve the roles from database :| !
I fixed so the role with authenticated removing "ADMIN" and all go ok!
Finally I can execute this rest endpoint with a secure login as I aspect it!
I'm very happy to resolve this trouble! Is not a good idea to fix a role on custom method getRoleSets but this is an application on production from many years and I must integrate a webmodule rest endpoint over them!
Thanks all!!

Remove response Server header on Azure Web App from the first redirect request to HTTPS

I’m trying to remove the response Server header from an Azure Web App ( with an ASP Net core application )
After many tries of changing the web.config and removing the header in app code using a middleware, Microsoft doesn’t give up and set the response header to Server: Microsoft-IIS/10.0 :)
The problem appears only when I’m trying to access the server on http (not https). Response code from the server is 301, and this is the only response that has the Server header.
Checking the logs I was not able to find any request to http://, and perhaps this is why I’m not able to remove header, because the request is not process in my application code.
A solution that I’m thinking is to disable the azure HTTPS only and do the redirect to https in my code (I tested and is working - server header is removed)
Is there another workaround without disabling the HTTPS only option?
Here is what I tried
Startup.cs
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
context.Response.Headers.Add("server", string.Empty)
}
app.UseHttpsRedirection();
}
web.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<httpRuntime enableVersionHeader="false" />
<!-- Removes ASP.NET version header. -->
</system.web>
<system.webServer>
<httpProtocol>
<customHeaders>
<remove name="Server" />
<remove name="X-Powered-By" />
</customHeaders>
<redirectHeaders>
<clear />
</redirectHeaders>
</httpProtocol>
<security>
<requestFiltering removeServerHeader="true" />
<!-- Removes Server header in IIS10 or later and also in Azure Web Apps -->
</security>
<rewrite>
<outboundRules>
<rule name="Change Server Header"> <!-- if you're not removing it completely -->
<match serverVariable="RESPONSE_Server" pattern=".+" />
<action type="Rewrite" value="Unknown" />
</rule>
</outboundRules>
</rewrite>
</system.webServer>
</configuration>
UPDATE
When the URL of http:// is requested, IIS will process it, this time without code. So we can't control it by the code, we can only set it on the server, such as some scripts or tools. But on Azure, we have no way to directly operate as a physical server, so after exploration, I suggest that Front Door can be used to deal with this problem. Hiding server information through proxy should be a better way.
After my test, the server information is hidden, you can refer to this document . We can see from the picture that there is no 301 redirect request, and no server information in other requests.
PREVIOUS
You need to modify Global.asax.cs and Web.config file in your program.
In Global.asax.cs.
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
MvcHandler.DisableMvcResponseHeader = true;
PreSendRequestHeaders += Application_PreSendRequestHeaders;
}
protected void Application_PreSendRequestHeaders(object sender, EventArgs e)
{
//HttpContext.Current.Response.Headers.Remove("Server");
HttpContext.Current.Response.Headers.Set("Server","N/A");
}
}
And In Web.config.
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" >
</modules>
<httpProtocol>
<customHeaders>
<remove name="X-Powered-By" />
</customHeaders>
</httpProtocol>
</system.webServer>
Then u can deploy your app. After the above code modification, access to the interface or static resources can see that the server information is modified, of course, it can also be deleted by Remove.
You also can handle special event by http status code.
protected void Application_PreSendRequestHeaders(object sender, EventArgs e)
{
//HttpContext.Current.Response.Headers.Remove("Server");
int StatusCode= HttpContext.Current.Response.StatusCode;
// handle like http status code 301
HttpContext.Current.Response.Headers.Set("Server","N/A");
}

Nullpointer Exception when injecting HttpRequest

I have a Liberty JAX-RS 2.0 Application on Bluemix. My goal is to use the Bluemix Session Cache Service as a central session storage.
In my interface, I inject the HttpRequest object like this:
#Path("/resource")
public class MyResource {
#Post
public Response myOperation(..., #Context final HttpServletRequest httpRequest) {
...
}
}
This runs fine with just Liberty on its own (without Session Cache Binding on Bluemix). I do not even access the httpRequest in my test app, nor do I access httpRequest.getSession(). Once I bind the Session Cache service to the Liberty App and restage the app, I get the following upon calling the API:
java.lang.NullPointerException:
at com.ibm.ws.xs.sessionmanager.IBMHttpSessionListener.attributeAdded(IBMHttpSessionListener.java:265)
at com.ibm.ws.session.http.HttpSessionAttributeObserver.sessionAttributeSet(HttpSessionAttributeObserver.java:141)
at [internal classes]
at org.jboss.weld.context.AbstractConversationContext.copyConversationIdGeneratorAndConversationsToSession(AbstractConversationContext.java:188)
at org.jboss.weld.context.AbstractConversationContext.sessionCreated(AbstractConversationContext.java:196)
at org.jboss.weld.servlet.ConversationContextActivator.sessionCreated(ConversationContextActivator.java:190)
at [internal classes]
As requested, the server.xml - which is generated by Bluemix on deployment.
<server>
<featureManager>
<feature>jaxrs-2.0</feature>
<feature>jsonp-1.0</feature>
<feature>couchdb-1.0</feature>
<feature>ejb-3.2</feature>
<feature>cdi-1.2</feature>
<feature>icap:managementConnector-1.0</feature>
<feature>appstate-1.0</feature>
<feature>cloudAutowiring-1.0</feature>
<feature>eXtremeScale.webapp-1.1</feature>
</featureManager>
<application name='myapp' location='myapp.war' type='war' context-root='some-app'>
<classloader commonLibraryRef='cloudantNoSQLDB-library'/>
</application>
<cdi12 enableImplicitBeanArchives='false'/>
<httpEndpoint id='defaultHttpEndpoint' host='*' httpPort='${port}'/>
<webContainer trustHostHeaderPort='true' extractHostHeaderPort='true'/>
<include location='runtime-vars.xml'/>
<logging logDirectory='${application.log.dir}' consoleLogLevel='INFO'/>
<httpDispatcher enableWelcomePage='false'/>
<applicationMonitor dropinsEnabled='false' updateTrigger='mbean'/>
<config updateTrigger='mbean'/>
<appstate appName='myapp' markerPath='${home}/../.liberty.state'/>
<couchdb id='cloudantNoSQLDB-ith-auth-db' jndiName='couchdb/ith-auth-db' libraryRef='cloudantNoSQLDB-library' username='${cloud.services.ith-auth-db.connection.username}' password='${cloud.services.ith-auth-db.connection.password}' url='${cloud.services.ith-auth-db.connection.url}' enableSSL='true' host='${cloud.services.ith-auth-db.connection.host}' port='${cloud.services.ith-auth-db.connection.port}'/>
<library id='cloudantNoSQLDB-library'>
<fileset id='cloudantNoSQLDB-fileset' dir='${server.config.dir}/lib' includes='commons-codec-1.6.jar commons-io-2.0.1.jar commons-logging-1.1.3.jar httpclient-4.3.6.jar httpclient-cache-4.3.6.jar httpcore-4.3.3.jar jackson-annotations-2.2.2.jar jackson-core-2.2.2.jar jackson-databind-2.2.2.jar jcl-over-slf4j-1.6.6.jar org.ektorp-1.4.2.jar slf4j-api-1.6.6.jar slf4j-jdk14-1.6.6.jar'/>
</library>
<xsWebApp id='session-cache' objectGridName='${cloud.services.session-cache.connection.gridName}' catalogHostPort='${cloud.services.session-cache.connection.catalogEndPoint}' credentialGeneratorClass='com.ibm.websphere.objectgrid.security.plugins.builtins.UserPasswordCredentialGenerator' credentialGeneratorProps='${cloud.services.session-cache.connection.username} ${cloud.services.session-cache.connection.password}' objectGridType='REMOTE' securityEnabled='true'/>
So I guess something goes wrong with injecting the HttpRequest... how can I solve this?

How to register the custom SSL certificate of X509TrustManager in Jboss AS7

Is there anybody to know how to register a custom SSL certificate of X509TrustManager in Jboss 7?
As the requirement of my project, I need to customize the default behavior of SSL certificate on X509TrustManager. I already have a custom certificate by extending X509TrustManager and override the default behavior, but now I do not know to register this custom file that Jboss AS7 can understand my own file instead of default X509TrustManager.
public class MyManager implements com.sun.net.ssl.X509TrustManager {
public boolean isClientTrusted(X509Certificate[] chain) { return true; }
public boolean isHostTrusted(X509Certificate[] chain) { return true; }
...
}
Thanks
If you have a security domain you can change the behavior of the Trust Manager in this way:
<security-domain name="CertificateDomain">
<authentication>
<login-module code="CertificateRoles" flag="required">
<module-option name="securityDomain" value="CertificateDomain"/>
<module-option name="verifier" value="org.jboss.security.auth.certs.AnyCertVerifier"/>
....
</login-module>
</authentication>
<jsse keystore-password="..." keystore-url=".." truststore-password="..." truststore-url="..." truststore-provider="..."/>
</security-domain>
truststore-provider Provider of the truststore. The default JDK provider for the truststore type is used if this attribute is null
Security subsystem configuration
Another way is to add the file standalone.xml the following properties (not tested)
<system-properties>
<property name="javax.net.ssl.trustStoreProvider" value="..."/>
<system-properties>

Spring Security - Get username in AuthenticationSuccessHandler without UserDetails/UserDetailsService

I'm trying to set the LastLogin time for a user in a custom AuthenticationSuccessHandler, however I don't know of a way of retrieving the username (since all the authority functions seem to return null because I'm not working with UserDetails).
The user data is stored in a MYSQL table and I'm using Hibernate to retrieve/create/update users. Within my application I'm using a self-written User class that doesn't have anything to do with the Spring User class. I don't have any custom UserDetails/UserDetailsService and I would like to avoid them, since I cannot change the DB table layout (as in add additional values)
The AuthenticationSuccessHandler looks like this:
public class PostSuccessfulAuthenticationHandler extends SimpleUrlAuthenticationSuccessHandler {
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException
{
userService.trackUserLogin(authentication.getName()); //Doesn't work, getName seems to return null
super.onAuthenticationSuccess(request, response, authentication);
}
}
My applicationContext-security.xml looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<global-method-security pre-post-annotations="enabled"/>
<!-- authentication-success-handler-ref='authSuccHandler' -->
<http use-expressions="true" disable-url-rewriting="true">
<intercept-url pattern="..." access="hasRole('ROLE_USER')" />
<form-login login-page="/index.htm"
authentication-failure-handler-ref='authFailureHandler'
authentication-success-handler-ref='authSuccHandler'
default-target-url='...'
always-use-default-target='true'/>
<logout logout-success-url="..."/>
<session-management invalid-session-url="/index.htm">
<concurrency-control max-sessions="2" error-if-maximum-exceeded="true" />
</session-management>
</http>
<authentication-manager>
<authentication-provider>
<jdbc-user-service data-source-ref="mysqldataSource"
authorities-by-username-query="select username, authority from benutzer where username = ?"
users-by-username-query="select username, password, enabled from benutzer where username = ?"/>
</authentication-provider>
</authentication-manager>
</beans:beans>
The login itself works fine (even if I just comment out the trackUserLogin line in my AuthenticationSuccessHandler) which leads me to believe that there has to be a way to get that username. Can anyone help?
Try authentication.getPrincipal().