I have implemented spring security oauth2 to protect different services.
Now I need to log client ip, client id, request service, request URL and Event type like "Authentication Failure", "Authorization failure" and so on.
I came up with following
public class Events implements ApplicationListener<ApplicationEvent>{
private static Logger log = LoggerFactory.getLogger(Events.class);
#Override
public void onApplicationEvent(ApplicationEvent appEvent) {
if (appEvent instanceof AuthenticationSuccessEvent) {
AuthenticationSuccessEvent event = (AuthenticationSuccessEvent) appEvent;
long timestamp = event.getTimestamp();
User user = (User) event.getAuthentication().getPrincipal();
System.out.println("client " + user.getUsername() + " has been authenticated successfully.");
UsernamePasswordAuthenticationToken source = (UsernamePasswordAuthenticationToken) event.getSource();
WebAuthenticationDetails details = (WebAuthenticationDetails) source.getDetails();
System.out.println("remote ip is " + details.getRemoteAddress());
}
if (appEvent instanceof AuthorizationFailureEvent) {
//TODO
((AuthorizationFailureEvent) appEvent).getAccessDeniedException();
}
if (appEvent instanceof AbstractAuthenticationFailureEvent) {
System.out.println("Sorry, authenticated for client " + appEvent.getSource().toString() + " failure.");
}
}
}
Because the request is asynchronous, I don't know how to get request from context. If I can get HttpServletRequest request, I can get almost every thing.
Refer to this. I have add RequestContextFilter in web.xml.
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
<init-param>
<param-name>threadContextInheritable</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
It works well.
I use,
Spring Framework 4.0.0 RELEASE (GA)
Spring Security 3.2.0 RELEASE (GA)
Struts 2.3.16
In which, I use an in-built security token to guard against CSRF attacks.
<s:form namespace="/admin_side"
action="Category"
enctype="multipart/form-data"
method="POST"
validate="true"
id="dataForm"
name="dataForm">
<s:hidden name="%{#attr._csrf.parameterName}"
value="%{#attr._csrf.token}"/>
</s:form>
It is a multipart request in which the CSRF token is unavailable to Spring security unless MultipartFilter along with MultipartResolver is properly configured so that the multipart request is processed by Spring.
MultipartFilter in web.xml is configured as follows.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext.xml
/WEB-INF/spring-security.xml
</param-value>
</context-param>
<filter>
<filter-name>MultipartFilter</filter-name>
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>MultipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>AdminLoginNocacheFilter</filter-name>
<filter-class>filter.AdminLoginNocacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AdminLoginNocacheFilter</filter-name>
<url-pattern>/admin_login/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>NoCacheFilter</filter-name>
<filter-class>filter.NoCacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>NoCacheFilter</filter-name>
<url-pattern>/admin_side/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<description>Description</description>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>struts.devMode</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
And in applicationContext.xml, MultipartResolver is registered as follows.
<bean id="filterMultipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="-1" />
</bean>
The CSRF token is now received by Spring security but doing so incurs another problem in Struts.
Uploaded file(s) is now null in Struts action classes like as follows.
#Namespace("/admin_side")
#ResultPath("/WEB-INF/content")
#ParentPackage(value="struts-default")
public final class CategoryAction extends ActionSupport implements Serializable, ValidationAware, ModelDriven<Category>
{
private File fileUpload;
private String fileUploadContentType;
private String fileUploadFileName;
private static final long serialVersionUID = 1L;
//Getters and setters.
//Necessary validators as required.
#Action(value = "AddCategory",
results = {
#Result(name=ActionSupport.SUCCESS, type="redirectAction", params={"namespace", "/admin_side", "actionName", "Category"}),
#Result(name = ActionSupport.INPUT, location = "Category.jsp")},
interceptorRefs={
#InterceptorRef(value="defaultStack", "validation.validateAnnotatedMethodOnly", "true"})
})
public String insert(){
//fileUpload, fileUploadContentType and fileUploadFileName are null here after the form is submitted.
return ActionSupport.SUCCESS;
}
#Action(value = "Category",
results = {
#Result(name=ActionSupport.SUCCESS, location="Category.jsp"),
#Result(name = ActionSupport.INPUT, location = "Category.jsp")},
interceptorRefs={
#InterceptorRef(value="defaultStack", params={ "validation.validateAnnotatedMethodOnly", "true", "validation.excludeMethods", "load"})})
public String load() throws Exception{
//This method is just required to return an initial view on page load.
return ActionSupport.SUCCESS;
}
}
This happens because to my guess, the multipart request is already processed and consumed by Spring hence, it is not available to Struts as a multipart request and therefore, the file object in a Struts action class is null.
Is there a way to get around this situation? Otherwise, I have now left with the only option to append the token to a URL as a query-string parameter which is highly discouraged and not recommended at all.
<s:form namespace="/admin_side"
action="Category?%{#attr._csrf.parameterName}=%{#attr._csrf.token}"
enctype="multipart/form-data"
method="POST"
validate="true"
id="dataForm"
name="dataForm">
...
<s:form>
Long story short : How to get files in a Struts action class, if Spring is made to process a mulipart request? On the other hand, if Spring is not made to process a multipart request then, it lakes the security token. How to overcome this situation?
It seems your best bet is to create a custom MultiPartRequest implementation that delegates to Spring's MultipartRequest. Here is an example implementation:
sample/SpringMultipartParser.java
package sample;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.util.WebUtils;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
public class SpringMultipartParser implements MultiPartRequest {
private static final Logger LOG = LoggerFactory.getLogger(MultiPartRequest.class);
private List<String> errors = new ArrayList<String>();
private MultiValueMap<String, MultipartFile> multipartMap;
private MultipartHttpServletRequest multipartRequest;
private MultiValueMap<String, File> multiFileMap = new LinkedMultiValueMap<String, File>();
public void parse(HttpServletRequest request, String saveDir)
throws IOException {
multipartRequest =
WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
if(multipartRequest == null) {
LOG.warn("Unable to MultipartHttpServletRequest");
errors.add("Unable to MultipartHttpServletRequest");
return;
}
multipartMap = multipartRequest.getMultiFileMap();
for(Entry<String, List<MultipartFile>> fileEntry : multipartMap.entrySet()) {
String fieldName = fileEntry.getKey();
for(MultipartFile file : fileEntry.getValue()) {
File temp = File.createTempFile("upload", ".dat");
file.transferTo(temp);
multiFileMap.add(fieldName, temp);
}
}
}
public Enumeration<String> getFileParameterNames() {
return Collections.enumeration(multipartMap.keySet());
}
public String[] getContentType(String fieldName) {
List<MultipartFile> files = multipartMap.get(fieldName);
if(files == null) {
return null;
}
String[] contentTypes = new String[files.size()];
int i = 0;
for(MultipartFile file : files) {
contentTypes[i++] = file.getContentType();
}
return contentTypes;
}
public File[] getFile(String fieldName) {
List<File> files = multiFileMap.get(fieldName);
return files == null ? null : files.toArray(new File[files.size()]);
}
public String[] getFileNames(String fieldName) {
List<MultipartFile> files = multipartMap.get(fieldName);
if(files == null) {
return null;
}
String[] fileNames = new String[files.size()];
int i = 0;
for(MultipartFile file : files) {
fileNames[i++] = file.getOriginalFilename();
}
return fileNames;
}
public String[] getFilesystemName(String fieldName) {
List<File> files = multiFileMap.get(fieldName);
if(files == null) {
return null;
}
String[] fileNames = new String[files.size()];
int i = 0;
for(File file : files) {
fileNames[i++] = file.getName();
}
return fileNames;
}
public String getParameter(String name) {
return multipartRequest.getParameter(name);
}
public Enumeration<String> getParameterNames() {
return multipartRequest.getParameterNames();
}
public String[] getParameterValues(String name) {
return multipartRequest.getParameterValues(name);
}
public List getErrors() {
return errors;
}
public void cleanUp() {
for(List<File> files : multiFileMap.values()) {
for(File file : files) {
file.delete();
}
}
// Spring takes care of the original File objects
}
}
Next you need to ensure that Struts is using it. You can do this in your struts.xml file as shown below:
struts.xml
<constant name="struts.multipart.parser" value="spring"/>
<bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest"
name="spring"
class="sample.SpringMultipartParser"
scope="default"/>
WARNING: It is absolutely necessary to ensure that a new instance of MultipartRequest is created for every multipart request by properly setting the scope of the bean otherwise you will see race conditions.
After doing this, your Struts actions will have the file information added just as it was before. Keep in mind that validation of file (i.e. file size) is now done with filterMultipartResolver instead of Struts.
Using Themes to auto include the CSRF token
You might consider creating a custom theme so that you can automatically include the CSRF token in forms. For more information on how to do this see http://struts.apache.org/release/2.3.x/docs/themes-and-templates.html
Complete Example on Github
You can find a complete working sample on github at https://github.com/rwinch/struts2-upload
The form encoding multipart/formdata is meant to be used for file upload scenarios, this is according to the W3C documentation:
The content type "multipart/form-data" should be used for submitting
forms that contain files, non-ASCII data, and binary data.
The MultipartResolver class expects a file upload only, and not other form fields, this is from the javadoc:
/**
* A strategy interface for multipart file upload resolution in accordance
* with RFC 1867.
*
*/
So this is why adding the CSRF as a form field would not work, the usual way to secure file upload requests against CSRF attacks is to send the CSRF token in a HTTP request header instead of the POST body. For that you need to make it an ajax POST.
For a normal POST there is no way to do this, see this answer. Either make the POST an ajax request and add the header with some Javascript, or send the CSRF token as a URL parameter as you mentioned.
If the CSRF token is frequently regenerated as it should ideally be between requests, then sending it in as request parameter is less of a problem and might be acceptable.
On the server side, you would need to configure the CSRF solution to read the token from the header, this is usually foreseen by the CSRF solution being used.
At a first glance your configuration looks correct to me. I therefore think that the problem might be some tiny bit of misconfiguration somewhere.
I faced a similar problem with Spring MVC instead of Struts, which I was able to solve with help from the Spring Security team. For full details see this answer.
You may also compare your set up with a working sample available on Github. I have tested this on Tomcat 7, JBoss AS 7, Jetty and Weblogic.
If these do not work, it will be helpful if you can create a single controller, single page application with your configuration that demonstrates the problem and upload it somewhere.
I'm not a Struts user, but I think you can use the fact that the Spring MultipartFilter wraps the request in a MultipartHttpServletRequest.
First get a hold of the HttpServletRequest, in Struts I think you can do it something like this:
ServletRequest request = ServletActionContext.getRequest();
Then retreive the MultipartRequest out of it, wrapping up wrappers if necessary:
MultipartRequest multipart = null;
while (multipart == null)
{
if (request instanceof MultipartRequest)
multipart = (MultipartRequest)request;
else if (request instanceof ServletRequestWrapper)
request = ((ServletRequestWrapper)request).getRequest();
else
break;
}
If this request was a multipart, get the file by the form input name:
if (multipart != null)
{
MultipartFile mf = multipart.getFile("forminputname");
// do your stuff
}
here is the url-pattern in web.xml
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Here is my controller
#Controller
public class HelloController
{
#RequestMapping("/*.km")
public String handleKm()
{
System.out.println("km ext called");
return "aaa";
}
#RequestMapping("/*.jsp")
public String handleJsp()
{
System.out.println("jsp pages called");
return "bbb";
}
}
while accessing the url with /requestMapping/a.km , it is works, calls the handleKm() method.but with /requestMapping/a.jsp , it should call handleJsp().but it does not work. result : HTTP Status 404 - /requestMapping/a.jsp.
why ??
If I chane the url-pattern from "/" to "/*" , although both method are called , but dont get to the appropriate pages.
May be the "org.springframework.web.servlet.view.InternalResourceViewResolver" is not working .
Check out this related SO post (possibly a duplicate). I think the .jsp extension is confusing the dispatcher servlet. Try using an extension that isn't .jsp and see if that works.
I want to implement Spring REST methods. I tried Get and it works:
#RequestMapping(value = "{systemId}", method = RequestMethod.GET)
public
#ResponseBody
Configuration getConfigurationInJSON(HttpServletResponse response, #PathVariable long systemId) throws IOException {
if (configurationMap.containsKey(systemId)) {
return configurationMap.get(systemId);
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
}
}
However when I want to implement DELETE it doesn't. My method is that:
#RequestMapping(value = "{systemId}", method = RequestMethod.DELETE)
public void deleteConfiguration(HttpServletResponse response, #PathVariable long systemId) throws IOException {
if (configurationMap.containsKey(systemId)) {
configurationMap.remove(systemId);
response.setStatus(HttpServletResponse.SC_FOUND);
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
My web.xml as follows:
mvc-dispatcher
org.springframework.web.servlet.DispatcherServlet
1
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/ui/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Command that works when I try with Curl for GET:
curl http://localhost:8080/ui/webapp/conf/1
Command that doesn't work when I try with Curl for DELETE:
curl -x delete http://localhost:8080/ui/webapp/conf/1
Second command gives that error after a time later at curl:
curl: (7) couldn't connect to host
There is no error at Tomcat side and at debug it doesn't enter the delete method body.
Any ideas?
Try
curl -X DELETE http://localhost:8080/ui/webapp/conf/1
with uppercase DELETE and uppercase X
I have been stuck at this for a while. I have a smartgwt widget listgrid tied to a restdatasource. I have mapped its URLs to my spring services. However I cannot figure out how to retrieve the JSON dsrequest on spring server side. Even my dispatcher servlet does not contain the params.
My restdatasource is as follows:
RestDataSource myDS = new RestDataSource() {
#Override
protected Object transformRequest(DSRequest dsRequest) {
dsRequest.setContentType("application/json");
JavaScriptObject jso = dsRequest.getData();
String s1 = JSON.encode(jso);
return s1;
// return super.transformRequest(dsRequest);
}
#Override
protected void transformResponse(DSResponse response, DSRequest request, Object data) {
super.transformResponse(response, request, data);
}
};
Then on this datasource set the operations as follows:
// set the operation on the datasource
OperationBinding fetch = new OperationBinding();
fetch.setOperationType(DSOperationType.FETCH);
fetch.setDataProtocol(DSProtocol.POSTMESSAGE);
OperationBinding add = new OperationBinding();
add.setOperationType(DSOperationType.ADD);
add.setDataProtocol(DSProtocol.POSTMESSAGE);
OperationBinding update = new OperationBinding();
update.setOperationType(DSOperationType.UPDATE);
update.setDataProtocol(DSProtocol.POSTPARAMS);
OperationBinding remove = new OperationBinding();
remove.setOperationType(DSOperationType.REMOVE);
remove.setDataProtocol(DSProtocol.POSTMESSAGE);
myDS.setOperationBindings(fetch, add, update, remove);
myDS.setDataFormat(DSDataFormat.JSON);
// myDS.setDataProtocol(DSProtocol.POSTMESSAGE);
Set some fields in the datasource:
// set the values for the datasource
DataSourceTextField Id = new DataSourceTextField("Id", "Id");
Id.setPrimaryKey(true);
Id.setCanEdit(false);
DataSourceTextField name= new DataSourceTextField("name", "Name");
name.setCanEdit(false);
DataSourceTextField employeeType= new DataSourceTextField("employeeType", "employeeType");
employeeType.setCanEdit(true);
employeeType.setValueMap("Manager", "Associate", "Contractor");
Set these fields to the datasource:
myDS.setFields(Id, name,employeeType);
myDS.setFetchDataURL("/rest/myservice/fetch");
myDS.setAddDataURL("/rest/myservice/add");
myDS.setUpdateDataURL("/rest/myservice/update");
myDS.setRemoveDataURL("/rest/myservice/remove");
So in the case that the user changes the employeeType (because it's a dropdown), an update request is sent. I send the JSON string to the server configured as below:
#Controller
#RequestMapping("/myservice")
public class MyService {
...fetch
...update
#RequestMapping(value="/update", method=RequestMethod.POST)
#ResponseBody
public String update()
{
}
I am failing to understand how to retrieve the (JSON) dsrequest because even my DispatcherServlet does not have the parameters (even if I use POSTPARAMS). The developer console shows the request made correctly but I don't receive anything on the server side.
My spring servlet is configured as below in web.xml:
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup> </servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
I think I am missing something obvious but I can't locate it. Do I use #PathVariable or RequestParams?
Found my own answer. Hope this helps someone.
update.setDataProtocol(DSProtocol.POSTPARAMS);
to
update.setDataProtocol(DSProtocol.POSTMESSAGE);
#Override
protected Object transformRequest(DSRequest dsRequest) {
JavaScriptObject jso = dsRequest.getData();
String s1 = JSON.encode(jso);
return s1;
}
#RequestMapping(value="/update", method=RequestMethod.POST)
#ResponseBody public String update(#RequestBody String json) { }