I have a pretty complex problem about struts2 chaining actions, thanks in advance for your patience reading my problem. I will try my best to describe it clearly.
Below is my struts.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.enable.SlashesInActionNames" value="true" />
<constant name="struts.devMode" value="false" />
<package name="default" extends="struts-default" namespace="/">
<action name="test" class="com.bv.test.TestAction1" >
<result name="success" type="chain">y</result>
</action>
<action name="x">
<result name="success">/index.jsp</result>
</action>
<action name="y" class="com.bv.test.TestAction2">
<result name="success">/index.jsp</result>
</action>
</package>
</struts>
My logic is like this:
When accessing to /myapp/test, TestAction1 will handle the request;
In TestAction1, I "include" action x (my 2nd action in my config) like this:
ResponseImpl myResponse = new ResponseImpl(response);
RequestDispatcher rd = request.getRequestDispatcher("/x.action");
rd.include(request, myResponse);
And the important thing is I am using a customized ResponseIml when including "x.action".
After including, I return "success", so the result chains to action y (3rd action in my config);
And at last, TestAction2 continue to handle the request, it will go to success result, and the jsp should be rendered, but what I see is a blank page.
The jsp file is very simple:
index.jsp
<h1>Test!</h1>
My question/puzzle is:
In TestAction1, if I get the response from ServletActionContext, I
am getting different ones before and after including; before
including is the default response, but after including I got an
instance of my customized ResponseImpl; I expect to get the same
one: i.e.: the default response;
In TestAction2, I get response from ServletActionContext, what I got
is the instance of my customized ResponseIml. This is my most important thing, I think I should get a default response instance here, i.e.: org.apache.catalina.connector.Response, I am running on JBoss;
I am getting a different ActionContext in TestAction2 (compared with
the ActionContext I get in TestAction1).
This problem really drive me on the nuts, I have spent days on it.
Any advice will be appreciated!
Thanks a million!!
My Code:
TestAction1:
public class TestAction1 {
public String execute() {
ActionContext ac = ActionContext.getContext();
System.out.println("Before including: the action context is : " + ac);
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
System.out.println("Before including: the response is : " + response);
ResponseImpl myResponse = new ResponseImpl(response);
RequestDispatcher rd = request.getRequestDispatcher("/x.action");
try {
rd.include(request, myResponse);
String s = myResponse.getOutput();
System.out.println("get from response: " + s);
}
catch (Exception e) {
e.printStackTrace();
}
ac = ActionContext.getContext();
System.out.println("After including : the action context is : " + ac);
response = ServletActionContext.getResponse();
System.out.println("After including : the response is : " + response);
return "success";
}
}
ResponseImpl:
import java.util.Locale;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.Cookie;
import javax.servlet.jsp.JspWriter;
/**
*
*
*/
public class ResponseImpl extends HttpServletResponseWrapper {
//=========================================================
// Private fields.
//=========================================================
private ServletOutputStream outputStream = null;
private ByteArrayOutputStream byteArrayOutputStream = null;
private StringWriter stringWriter = null;
private PrintWriter printWriter = null;
private HttpServletResponse _response = null;
private String contentType= "text/html";
private String encoding = "UTF-8";
/**
*
*/
class ServletOutputStream extends javax.servlet.ServletOutputStream {
private OutputStream outputStream = null;
/**
*
*/
ServletOutputStream(ByteArrayOutputStream outputStream) {
super();
this.outputStream = outputStream;
}
/**
*
*/
public void write(int b) throws IOException {
this.outputStream.write(b);
}
}
//=========================================================
// Public constructors and methods.
//=========================================================
/**
*
*/
public ResponseImpl(HttpServletResponse response) {
super(response);
this._response = response;
}
/**
*
*/
public String getOutput() {
if (this.stringWriter != null) {
return this.stringWriter.toString();
}
if (this.byteArrayOutputStream != null) {
try {
return this.byteArrayOutputStream.toString(this.encoding);
}
catch (UnsupportedEncodingException e) {
}
return this.byteArrayOutputStream.toString();
}
return null;
}
//=========================================================
// Implements HttpServletResponse interface.
//=========================================================
public void addCookie(Cookie cookie) {
}
public void addDateHeader(String name, long date) {
}
public void addHeader(String name, String value) {
}
public void addIntHeader(String name, int value) {
}
public boolean containsHeader(String name) {
return false;
}
public String encodeRedirectURL(String url) {
if (null != this._response) {
url = this._response.encodeRedirectURL(url);
}
return url;
}
public String encodeURL(String url) {
if (null != this._response) {
url = this._response.encodeURL(url);
}
return url;
}
public void sendError(int sc) {
}
public void sendError(int sc, String msg) {
}
public void sendRedirect(String location) {
}
public void setDateHeader(String name, long date) {
}
public void setHeader(String name, String value) {
}
public void setIntHeader(String name, int value) {
}
public void setStatus(int sc) {
}
public void resetBuffer() {
}
//=========================================================
// Implements deprecated HttpServletResponse methods.
//=========================================================
public void setStatus(int sc, String sm) {
}
//=========================================================
// Implements deprecated HttpServletResponse methods.
//=========================================================
public String encodeRedirectUrl(String url) {
return encodeRedirectURL(url);
}
public String encodeUrl(String url) {
return encodeURL(url);
}
//=========================================================
// Implements ServletResponse interface.
//=========================================================
public void flushBuffer() {
}
public int getBufferSize() {
return 0;
}
public String getCharacterEncoding() {
return this.encoding;
}
public String getContentType() {
return this.contentType;
}
public Locale getLocale() {
return null;
}
public javax.servlet.ServletOutputStream getOutputStream() {
if (this.outputStream == null) {
this.byteArrayOutputStream = new ByteArrayOutputStream();
this.outputStream =
new ServletOutputStream(this.byteArrayOutputStream);
}
return this.outputStream;
}
public PrintWriter getWriter() {
if (this.printWriter == null) {
this.stringWriter = new StringWriter();
this.printWriter = new PrintWriter(this.stringWriter);
}
return this.printWriter;
}
public boolean isCommitted() {
return true;
}
public void reset() {
}
public void setBufferSize(int size) {
}
public void setCharacterEncoding(String charset) {
}
public void setContentLength(int len) {
}
public void setContentType(String type) {
int needle = type.indexOf(";");
if (-1 == needle) {
this.contentType = type;
}
else {
this.contentType = type.substring(0, needle);
String pattern = "charset=";
int index = type.indexOf(pattern, needle);
if (-1 != index) {
this.encoding = type.substring(index + pattern.length());
}
}
}
public void setLocale(Locale locale) {
}
}
TestAction2:
public class TestAction2 {
public String execute() {
ActionContext ac = ActionContext.getContext();
System.out.println("In TestAction 2 : the action context is : " + ac);
HttpServletResponse response = ServletActionContext.getResponse();
System.out.println("In TestAction 2 : the response is : " + response);
return "success";
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Struts2 Application</display-name>
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
</web-app>
This is my debug info.
Before including: the action context is
:com.opensymphony.xwork2.ActionContext#c639ce
Before including: the response is :
org.apache.catalina.connector.ResponseFacade#8b677f
get from response: <h1>Test!</h1>
After including : the action context is :
com.opensymphony.xwork2.ActionContext#2445d7
After including : the response is : com.bv.test.ResponseImpl#165547d
In TestAction 2 : the action context is
:com.opensymphony.xwork2.ActionContext#19478c7
In TestAction 2 : the response is : com.bv.test.ResponseImpl#165547d
So, I have different ActionContext instances before and after the including!!
When you do rd.include another request is fired internally inside the web server. So from struts point of view it sees a completely new request and a new action context is created as a result. (that's why you need to include 'INCLUDE' thing on the struts2 filter.. so that it's seeing included requests as well). Probably since thread local variables are used to track action context and all that when you do ActionContext.getContext() the context related to the new request (related to the include) gets retrieved.
Did you try resetting the response to the initial one in a finally block like below
try {
rd.include(request, myResponse);
String s = myResponse.getOutput();
System.out.println("get from response: " + s);
}
catch (Exception e) {
e.printStackTrace();
} finally {
ServletActionContext.setResponse(response);
}
If this resolves the response issue.. you could probably store the string 's' as a variable in the action context and retrieve it inside your Action2
You could also try the following as well. Instead of using chaining.. inside your TestAction1 include the TestAction2 with the original response. return 'none' from the action as the return value.
public class TestAction1 {
public String execute() {
ActionContext ac = ActionContext.getContext();
System.out.println("Before including: the action context is : " + ac);
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
System.out.println("Before including: the response is : " + response);
ResponseImpl myResponse = new ResponseImpl(response);
RequestDispatcher rd = request.getRequestDispatcher("/x.action");
try {
rd.include(request, myResponse);
String s = myResponse.getOutput();
System.out.println("get from response: " + s);
}
catch (Exception e) {
e.printStackTrace();
}
RequestDispatcher rd = request.getRequestDispatcher("/y.action");
try {
rd.include(request, response);
}
catch (Exception e) {
e.printStackTrace();
} finally {
return "none";
}
}
}
Related
I am working on an application where I would like to include dynamic XHTML content from a stream. To handle this I wrote a taghandler extension which dumps the dynamic XHTML content to output component as
UIOutput htmlChild = (UIOutput) ctx.getFacesContext().getApplication().createComponent(UIOutput.COMPONENT_TYPE);
htmlChild.setValue(new String(outputStream.toByteArray(), "utf-8"));
This works fine for XHTML content which has no JSF tags. If I have JSF tags in my dynamic XHTML content like <h:inputText value="#{bean.item}"/>, then they're printed as plain text. I want them to render as input fields. How can I achieve this?
Essentially, you should be using an <ui:include> in combination with a custom ResourceHandler which is able to return the resource in flavor of an URL. So when having an OutputStream, you should really be writing it to a (temp) file so that you can get an URL out of it.
E.g.
<ui:include src="/dynamic.xhtml" />
with
public class DynamicResourceHandler extends ResourceHandlerWrapper {
private ResourceHandler wrapped;
public DynamicResourceHandler(ResourceHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public ViewResource createViewResource(FacesContext context, String resourceName) {
if (resourceName.equals("/dynamic.xhtml")) {
try {
File file = File.createTempFile("dynamic-", ".xhtml");
try (Writer writer = new FileWriter(file)) {
writer
.append("<ui:composition")
.append(" xmlns:ui='http://java.sun.com/jsf/facelets'")
.append(" xmlns:h='http://java.sun.com/jsf/html'")
.append(">")
.append("<p>Hello from a dynamic include!</p>")
.append("<p>The below should render as a real input field:</p>")
.append("<p><h:inputText /></p>")
.append("</ui:composition>");
}
final URL url = file.toURI().toURL();
return new ViewResource(){
#Override
public URL getURL() {
return url;
}
};
}
catch (IOException e) {
throw new FacesException(e);
}
}
return super.createViewResource(context, resourceName);
}
#Override
public ResourceHandler getWrapped() {
return wrapped;
}
}
(warning: basic kickoff example! this creates a new temp file on every request, a reuse/cache system should be invented on your own)
which is registered in faces-config.xml as follows
<application>
<resource-handler>com.example.DynamicResourceHandler</resource-handler>
</application>
Note: all of above is JSF 2.2 targeted. For JSF 2.0/2.1 users stumbling upon this answer, you should use ResourceResolver instead for which an example is available in this answer: Obtaining Facelets templates/files from an external filesystem or database. Important note: ResourceResolver is deprecated in JSF 2.2 in favor of ResourceHandler#createViewResource().
My solution for JSF 2.2 and custom URLStream Handler
public class DatabaseResourceHandlerWrapper extends ResourceHandlerWrapper {
private ResourceHandler wrapped;
#Inject
UserSessionBean userBeean;
public DatabaseResourceHandlerWrapper(ResourceHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public Resource createResource(String resourceName, String libraryName) {
return super.createResource(resourceName, libraryName); //To change body of generated methods, choose Tools | Templates.
}
#Override
public ViewResource createViewResource(FacesContext context, String resourceName) {
if (resourceName.startsWith("/dynamic.xhtml?")) {
try {
String query = resourceName.substring("/dynamic.xhtml?".length());
Map<String, String> params = splitQuery(query);
//do some query to get content
String content = "<ui:composition"
+ " xmlns='http://www.w3.org/1999/xhtml' xmlns:ui='http://java.sun.com/jsf/facelets'"
+ " xmlns:h='http://java.sun.com/jsf/html'> MY CONTENT"
+ "</ui:composition>";
final URL url = new URL(null, "string://helloworld", new MyCustomHandler(content));
return new ViewResource() {
#Override
public URL getURL() {
return url;
}
};
} catch (IOException e) {
throw new FacesException(e);
}
}
return super.createViewResource(context, resourceName);
}
public static Map<String, String> splitQuery(String query) throws UnsupportedEncodingException {
Map<String, String> params = new LinkedHashMap<>();
String[] pairs = query.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
params.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
}
return params;
}
#Override
public ResourceHandler getWrapped() {
return wrapped;
}
static class MyCustomHandler extends URLStreamHandler {
private String content;
public MyCustomHandler(String content) {
this.content = content;
}
#Override
protected URLConnection openConnection(URL u) throws IOException {
return new UserURLConnection(u, content);
}
private static class UserURLConnection extends URLConnection {
private String content;
public UserURLConnection(URL url, String content) {
super(url);
this.content = content;
}
#Override
public void connect() throws IOException {
}
#Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(content.getBytes("UTF-8"));
}
}
}
}
I am using JSF2 with Prime faces. I will want to show all the previous links or Urls clicked by user in every page.How can i do this?
Here's a good link for you: How can I get the request URL from a Java Filter?
What I would do is use something like that example and do a set of three URLs.
//Using linked example
public class MyFilter implements Filter {
public void init(FilterConfig config) throws ServletException { }
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
chain.doFilter(request, response);
String url = ((HttpServletRequest) request).getPathTranslated();
UrlBean urlBean = (UrlBean) FacesContext.getCurrentInstance()getApplication().getValueBinding("#{urlBean}");
urlBean.storeUrl(url);
}
public void destroy() { }
}
This is totally untested, but the idea should work. You would just need to implement some logic (probably a stack) for your component so it stores what you need. (Obviously you might end up at the same URL multiple times). I should note the UrlBean is just an abstract idea, you would have to implement it
Hi I have created this Singleton class to put the Url Browsed by a User.
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class UrlHistory {
#SuppressWarnings("rawtypes")
private static Map<String, List> store = new HashMap<String, List>();
private static UrlHistoryBean instance = null;
public static UrlHistoryBean getInstance() {
if (instance == null) {
instance = new UrlHistoryBean();
}
return instance;
}
LinkedList<UrlData> urlList = new LinkedList<UrlHistoryBean.UrlData>();
public void addUrl(String sessionId, String urlString, int urlId) {
UrlData data = new UrlData();
data.setUrlName(urlString);
data.setUrlId(companyId);
if (urlList.isEmpty()) {
urlList.add(data);
} else {
boolean isEqual = false;
for (UrlData urlDataObj : urlList) {
if (urlDataObj.equals(data))
isEqual = true;
}
if(!isEqual)
urlList.addFirst(data);
}
store.put(sessionId, urlList);
}
#SuppressWarnings("rawtypes")
public static Map<String, List> getStore() {
return store;
}
#SuppressWarnings("rawtypes")
public static void setStore(Map<String, List> store) {
UrlHistoryBean.store = store;
}
public class UrlData {
String urlName;
int urlId;
public String getUrlName() {
return UrlName;
}
public void setUrlName(String UrlName) {
this.UrlName = UrlName;
}
public int getUrlId() {
return urlId;
}
public void setUrlId(int urlId) {
this.urlId = urlId;
}
public boolean equals(UrlData rData) {
boolean bEqual = false;
if (this.getUrlId() > 0 && rData.getUrlId() > 0 && this.getUrlId() == rData.getUrlId()) {
bEqual = true;
}
return bEqual;
}
}
}
Can they work together?
Some project sample would be great.
I have a web-app on Spring3. And i need to implement NTLM. Spring stopped NTLM support in 3rd version. Is there any possibilities to implement it?
Looking for a sample project.
They can be used together. Essentially what you want to do is hook into the SPNEGO protocol and detect when you receive an NTLM packet from the client. A good description of the protocol can be found here:
http://www.innovation.ch/personal/ronald/ntlm.html
http://blogs.technet.com/b/tristank/archive/2006/08/02/negotiate-this.aspx
Another great resource for NTLM is this:
http://davenport.sourceforge.net/ntlm.html
But you asked for a sample so here goes. To detect an NTLM packet you need to base64
decode the packet and inspect for a starting string:
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String header = request.getHeader("Authorization");
if ((header != null) && header.startsWith("Negotiate ")) {
if (logger.isDebugEnabled()) {
logger.debug("Received Negotiate Header for request " + request.getRequestURL() + ": " + header);
}
byte[] base64Token = header.substring(10).getBytes("UTF-8");
byte[] decodedToken = Base64.decode(base64Token);
if (isNTLMMessage(decodedToken)) {
authenticationRequest = new NTLMServiceRequestToken(decodedToken);
}
...
}
public static boolean isNTLMMessage(byte[] token) {
for (int i = 0; i < 8; i++) {
if (token[i] != NTLMSSP_SIGNATURE[i]) {
return false;
}
}
return true;
}
public static final byte[] NTLMSSP_SIGNATURE = new byte[]{
(byte) 'N', (byte) 'T', (byte) 'L', (byte) 'M',
(byte) 'S', (byte) 'S', (byte) 'P', (byte) 0
};
You'll need to make an authentication provider that can handle that type of authenticationRequest:
import jcifs.Config;
import jcifs.UniAddress;
import jcifs.ntlmssp.NtlmMessage;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.smb.SmbSession;
import jcifs.util.Base64;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import javax.annotation.PostConstruct;
import java.io.IOException;
/**
* User: gcermak
* Date: 3/15/11
* <p/>
*/
public class ActiveDirectoryNTLMAuthenticationProvider implements AuthenticationProvider, InitializingBean {
protected String defaultDomain;
protected String domainController;
protected UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
public ActiveDirectoryNTLMAuthenticationProvider(){
Config.setProperty( "jcifs.smb.client.soTimeout", "1800000" );
Config.setProperty( "jcifs.netbios.cachePolicy", "1200" );
Config.setProperty( "jcifs.smb.lmCompatibility", "0" );
Config.setProperty( "jcifs.smb.client.useExtendedSecurity", "false" );
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
NTLMServiceRequestToken auth = (NTLMServiceRequestToken) authentication;
byte[] token = auth.getToken();
String name = null;
String password = null;
NtlmMessage message = constructNTLMMessage(token);
if (message instanceof Type1Message) {
Type2Message type2msg = null;
try {
type2msg = new Type2Message(new Type1Message(token), getChallenge(), null);
throw new NtlmType2MessageException(Base64.encode(type2msg.toByteArray()));
} catch (IOException e) {
throw new NtlmAuthenticationFailure(e.getMessage());
}
}
if (message instanceof Type3Message) {
final Type3Message type3msg;
try {
type3msg = new Type3Message(token);
} catch (IOException e) {
throw new NtlmAuthenticationFailure(e.getMessage());
}
final byte[] lmResponse = (type3msg.getLMResponse() != null) ? type3msg.getLMResponse() : new byte[0];
final byte[] ntResponse = (type3msg.getNTResponse() != null) ? type3msg.getNTResponse() : new byte[0];
NtlmPasswordAuthentication ntlmPasswordAuthentication = new NtlmPasswordAuthentication(type3msg.getDomain(), type3msg.getUser(), getChallenge(), lmResponse, ntResponse);
String username = ntlmPasswordAuthentication.getUsername();
String domain = ntlmPasswordAuthentication.getDomain();
String workstation = type3msg.getWorkstation();
name = ntlmPasswordAuthentication.getName();
password = ntlmPasswordAuthentication.getPassword();
}
// do custom logic here to find the user ...
userDetailsChecker.check(user);
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
// The Client will only ever send a Type1 or Type3 message ... try 'em both
protected static NtlmMessage constructNTLMMessage(byte[] token) {
NtlmMessage message = null;
try {
message = new Type1Message(token);
return message;
} catch (IOException e) {
if ("Not an NTLMSSP message.".equals(e.getMessage())) {
return null;
}
}
try {
message = new Type3Message(token);
return message;
} catch (IOException e) {
if ("Not an NTLMSSP message.".equals(e.getMessage())) {
return null;
}
}
return message;
}
protected byte[] getChallenge() {
UniAddress dcAddress = null;
try {
dcAddress = UniAddress.getByName(domainController, true);
return SmbSession.getChallenge(dcAddress);
} catch (IOException e) {
throw new NtlmAuthenticationFailure(e.getMessage());
}
}
#Override
public boolean supports(Class<? extends Object> auth) {
return NTLMServiceRequestToken.class.isAssignableFrom(auth);
}
#Override
public void afterPropertiesSet() throws Exception {
// do nothing
}
public void setSmbClientUsername(String smbClientUsername) {
Config.setProperty("jcifs.smb.client.username", smbClientUsername);
}
public void setSmbClientPassword(String smbClientPassword) {
Config.setProperty("jcifs.smb.client.password", smbClientPassword);
}
public void setDefaultDomain(String defaultDomain) {
this.defaultDomain = defaultDomain;
Config.setProperty("jcifs.smb.client.domain", defaultDomain);
}
/**
* 0: Nothing
* 1: Critical [default]
* 2: Basic info. (Can be logged under load)
* 3: Detailed info. (Highest recommended level for production use)
* 4: Individual smb messages
* 6: Hex dumps
* #param logLevel the desired logging level
*/
public void setDebugLevel(int logLevel) throws Exception {
switch(logLevel) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 6:
Config.setProperty("jcifs.util.loglevel", Integer.toString(logLevel));
break;
default:
throw new Exception("Invalid Log Level specified");
}
}
/**
*
* #param winsList a comma separates list of wins addresses (ex. 10.169.10.77,10.169.10.66)
*/
public void setNetBiosWins(String winsList) {
Config.setProperty("jcifs.netbios.wins", winsList);
}
public void setDomainController(String domainController) {
this.domainController = domainController;
}
}
And finally you need to tie it all together in your spring_security.xml file:
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<http auto-config="true" use-expressions="true" disable-url-rewriting="true">
<form-login login-page="/auth/login"
login-processing-url="/auth/j_security_check"/>
<remember-me services-ref="rememberMeServices"/>
<logout invalidate-session="true" logout-success-url="/auth/logoutMessage" logout-url="/auth/logout"/>
<access-denied-handler error-page="/error/accessDenied"/>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="myUsernamePasswordUserDetailsService">
<password-encoder ref="passwordEncoder">
<salt-source ref="saltSource"/>
</password-encoder>
</authentication-provider>
<authentication-provider ref="NTLMAuthenticationProvider"/>
</authentication-manager>
</beans:beans>
Lastly you need to know how to tie it all together. The protocol as described in the first set of links shows that there are a couple round trips that you need to make between the client and server. Thus in your filter you need a bit more logic:
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.codec.Base64;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.extensions.kerberos.KerberosServiceRequestToken;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* User: gcermak
* Date: 12/5/11
*/
public class SpnegoAuthenticationProcessingFilter extends GenericFilterBean {
private AuthenticationManager authenticationManager;
private AuthenticationSuccessHandler successHandler;
private AuthenticationFailureHandler failureHandler;
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String header = request.getHeader("Authorization");
if ((header != null) && header.startsWith("Negotiate ")) {
if (logger.isDebugEnabled()) {
logger.debug("Received Negotiate Header for request " + request.getRequestURL() + ": " + header);
}
byte[] base64Token = header.substring(10).getBytes("UTF-8");
byte[] decodedToken = Base64.decode(base64Token);
// older versions of ie will sometimes do this
// logic cribbed from jcifs filter implementation jcifs.http.NtlmHttpFilter
if (request.getMethod().equalsIgnoreCase("POST")) {
if (decodedToken[8] == 1) {
logger.debug("NTLM Authorization header contains type-1 message. Sending fake response just to pass this stage...");
Type1Message type1 = new Type1Message(decodedToken);
// respond with a type 2 message, where the challenge is null since we don't
// care about the server response (type-3 message) since we're already authenticated
// (This is just a by-pass - see method javadoc)
Type2Message type2 = new Type2Message(type1, new byte[8], null);
String msg = jcifs.util.Base64.encode(type2.toByteArray());
response.setHeader("WWW-Authenticate", "Negotiate " + msg);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentLength(0);
response.flushBuffer();
return;
}
}
Authentication authenticationRequest = null;
if (isNTLMMessage(decodedToken)) {
authenticationRequest = new NTLMServiceRequestToken(decodedToken);
}
Authentication authentication;
try {
authentication = authenticationManager.authenticate(authenticationRequest);
} catch (NtlmBaseException e) {
// this happens during the normal course of action of an NTLM authentication
// a type 2 message is the proper response to a type 1 message from the client
// see: http://www.innovation.ch/personal/ronald/ntlm.html
response.setHeader("WWW-Authenticate", e.getMessage());
response.setHeader("Connection", "Keep-Alive");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentLength(0);
response.flushBuffer();
return;
} catch (AuthenticationException e) {
// That shouldn't happen, as it is most likely a wrong configuration on the server side
logger.warn("Negotiate Header was invalid: " + header, e);
SecurityContextHolder.clearContext();
if (failureHandler != null) {
failureHandler.onAuthenticationFailure(request, response, e);
} else {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.flushBuffer();
}
return;
}
if (successHandler != null) {
successHandler.onAuthenticationSuccess(request, response, authentication);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {
this.successHandler = successHandler;
}
public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
this.failureHandler = failureHandler;
}
#Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
Assert.notNull(this.authenticationManager, "authenticationManager must be specified");
}
}
You'll see that in the exception we use "Negotiate" rather than NTLM:
/**
* User: gcermak
* Date: 12/5/11
*/
public class NtlmType2MessageException extends NtlmBaseException {
private static final long serialVersionUID = 1L;
public NtlmType2MessageException(final String type2Msg) {
super("Negotiate " + type2Msg);
}
}
The spring filter (above) was largely patterned on jcifs.http.NtlmHttpFilter which you can find in the source for jcifs here:
http://jcifs.samba.org/
This isn't a whole, downloadable project as you requested but if there is interest from the community I could add this NTLM code to my github project:
http://git.springsource.org/~grantcermak/spring-security/activedirectory-se-security
Hope this helps!
Grant
I have configured result as follows : Its my custom result type.
<result-types>
<result-type name="myBytesResult" class="blahblah.MyBytesResult" />
</result-types>
<action name="myAction" class="blahblah.MyAction">
<result name="success" type="myBytesResult">
<param name="pptId">${pptId}</param>
</result>
</action>
And my result has setter/getter for pptId and MyAction also has setter/getter for pptId. But when i check in my result, Its not setting pptId (I am getting ${pptId} as string in result). It seems its not getting getter from Action.
What could be reason for the same ?
The code MyAction
public String doDefault() {
System.out.println("Default Called");
setPptId("MyPpt");
return "success";
}
public byte[] getMyImageInBytes() throws Exception {
try {
//.....
} catch (Exception e) {
}
return null;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public String getPptId() {
return this.pptId;
}
public void setPptId(String pptId) {
this.pptId = pptId;
}
MyBytesResult
private String contentType;
private String pptId;
public void execute(ActionInvocation invocation) throws Exception {
HttpServletResponse response = ServletActionContext.getResponse();
//...Some more code for settign response
System.out.println("pt Id[" + this.pptId + "]");
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public String getPptId() {
return pptId;
}
public void setPptId(String pptId) {
this.pptId = pptId;
}
Updated answer
So, after some digging, it appears that this is actually working properly. It is the responsibility of the Result to evaluate the incoming data as an OGNL expression if that is necessary. This is how the redirect and http header results work. You can parse the value against the stack in your custom result as follows:
String resolvedPptId = TextParseUtil.translateVariables(pptId, stack)
From the Javadoc for translateVariables:
Converts all instances of ${...}, and %{...} in expression to the value returned by a call to {#link ValueStack#findValue(java.lang.String)}. If an item cannot
be found on the stack (null is returned), then the entire variable ${...} is not
displayed, just as if the item was on the stack but returned an empty string.
I want to send String as a response to the AJAX xhrPOST method. I am using Struts2 to implement the server side processing. But, I am not getting how to send the result "type" as string and the mapping which should be done to send the string from the struts2 action class to the AJAX response.
You can have your action method return not a String result, but a result of type StreamResult.
In other words:
class MyAction {
public StreamResult method() {
return new StreamResult(new ByteArrayInputStream("mystring".getBytes()));
}
}
You don't necessarily have to return a String from a Struts2 action method. You can always return an implementation of the Result interface from xwork.
copy this in action class
private InputStream inputStream;
public InputStream getInputStream() {
return inputStream;
}
public String execute(){
inputStream = new StringBufferInputStream("some data to send for ajax response");
return SUCCESS;
}
Struts.xml
<action name=....>
<result type="stream">
<param name="contentType">text/html</param>
<param name="inputName">inputStream</param>
</result>
This works when we want to send a single data in response
You could create a simple StringResult pretty easily by extending StrutsResultSupport, but nothing exists built-in to the framework as far as I know.
Here's an implementation that I've used in the past of a simple StringResult:
public class StringResult extends StrutsResultSupport {
private static final Log log = LogFactory.getLog(StringResult.class);
private String charset = "utf-8";
private String property;
private String value;
private String contentType = "text/plain";
#Override
protected void doExecute(String finalLocation, ActionInvocation invocation)
throws Exception {
if (value == null) {
value = (String)invocation.getStack().findValue(conditionalParse(property, invocation));
}
if (value == null) {
throw new IllegalArgumentException("No string available in value stack named '" + property + "'");
}
if (log.isTraceEnabled()) {
log.trace("string property '" + property + "'=" + value);
}
byte[] b = value.getBytes(charset);
HttpServletResponse res = (HttpServletResponse) invocation.getInvocationContext().get(HTTP_RESPONSE);
res.setContentType(contentType + "; charset=" + charset);
res.setContentLength(b.length);
OutputStream out = res.getOutputStream();
try {
out.write(b);
out.flush();
} finally {
out.close();
}
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
}
I've used the json plugin to do similar things. If you use that, you can use the following to expose a single String property in your action:
<result name="success" type="json">
<param name="root">propertyToExpose</param>
</result>