While developing an application it's quite useful to be able to quickly login as different users, with different roles, to see how the application presents itself.
Typing usernames and entering password is no fun, and a waste of time. What I'd like to do is:
add a page/panel with a list of available usernames;
clicking on a username will generate an event for Spring security which allows it to recognize the user as authenticated, without entering passwords;
after clicking the link I am authenticated as the specified user.
N.B.: Passwords are hashed and submitted in plain-text using forms, so encoding the passwords in the links is not an option.
Obviously this feature will only be present at development time.
How can I achieve this?
Use InMemoryDaoImpl for development mode. It is very easy to create users and passwords stored in memory:
<bean id="userDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
admin=admin,ROLE_ADMIN,ROLE_USER
user1=user1,ROLE_USER
user2=user2,ROLE_USER
</value>
</property>
</bean>
In development mode inject this to your authentication provider. In production replace it with the proper DB or LDAP implementation.
I have done it this way for an web application:
I have a configuration parameter in context.xml of the server (of course only in the development server). This parameter contains a coma seperated list of usernames and passwords.
The login page (jsp(x)) simply add a extra form and submit button for each username, password item form the context parameter. So if a user clicks on that button the normal login process with the predefined login data is trigged.
Server context.xml
...
<Context>
...
<Parameter name="quickLogin"
value="admin:passwd,user:otherPasswd"
override="false" />
</Context>
login.jspx
...
<!-- Login for debugging purposes -->
<c:forTokens items="${initParam.quickLogin}" delims="," var="loginPassword">
<c:set var="login" value="${fn:split(loginPassword, ':')[0]}" />
<c:set var="password" value="${fn:split(loginPassword, ':')[1]}" />
<form name="debugLogin" action="${form_url}" method="POST" >
<crsf:hiddenCrsfNonce/>
<input type="hidden" name='j_username' value="${fn:escapeXml(login)}" />
<input type="hidden" name='j_password' value="${fn:escapeXml(password)}" />
<input type="submit" value="${fn:escapeXml(login)} login" />
</form>
</c:forTokens>
...
As I understand you would like to have the SpringSecurity authenticate you automatically if some specific URL is requested (and you would have a link to this URL in your panel/page).
How about writing a custom filter:
public class YourSpecialDevelopmentTimeFilter
extends AuthenticationProcessingFilter
implements SyncSecurityFilter
....
that would override:
protected boolean requiresAuthentication(
HttpServletRequest request, HttpServletResponse response)
and return true depending on some parameters in the request?
Of course another concern is not to have this functionality in the production environments. That is always a risky thing to have different code-base for dev and prod.
Related
I'm using Spring Security 5.0.13 and I'd like to activate the csrf protection for the login page. I'm using the xml configuration, which I changed from
<http>
...
<csrf disabled="true"/>
</http>
to
<bean id="csrfMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
<constructor-arg name="pattern" value="/j_spring_security_check"/>
<constructor-arg name="httpMethod" value="POST"/>
</bean>
<csrf request-matcher-ref="csrfMatcher" />
However the j_spring_security_logout endpoint now expects a POST request, whereas it used to accept a GET request. I know it would be better to have a POST request for the logout button, but I can't break this functionality as it's used elsewhere outside of my control.
How can I activate the csrf protection for the login page without affecting the logout url verb ?
CSRF protection requires you to send a hidden input containing the CRSF attribute value for all requests that cause changes. That is what protects you - this CSRF attribute that is generated by your server and hence can't be faked by sending a request from some other place outside your website.
You can only send hidden inputs inside of forms with post requests therefore if you want csrf you will need to use post. Good news is - it is very easy, most template engines set everything for you automatically.
With Thymeleaf you don't even need to change anything, it generates this attribute in your post requests automatically.
With Mustache you would need to add following property:
spring.mustache.expose-request-attributes=true
And afterwards use following form:
<form id="logoutForm" method="POST" action="/logout">
<input type="hidden" name="_csrf" value="{{_csrf.token}}"/>
<button type=submit>Logout</button>
</form>
As you can see it really is relatively easy, but you have to add the hidden crsf.token value to each post request, implementation depending on your template engine, and like I said with thymeleaf you wouldn't need to worry about it.
According to the Spring Security reference docs, you can modify how Spring Security matches the /logout endpoint. By default, it looks for POST /logout, but you can configure it to look for GET /logout instead:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) {
http
// ... other configs
.logout(logout -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
);
}
}
There is no direct XML equivalent through the <logout> element, though you could declare your own <bean> of type LogoutFilter and register it. Or logging a ticket might be an option to get it added to <logout>.
Background
A user must submit report parameters to the server. The server redirects the user to a URL. That URL runs Oracle Reports to produce a PDF (or web page).
The system uses a relatively slow authentication mechanism. But I'm not looking to restart the web flow, so the authentication should not otherwise interfere with the redirect.
JSP
The "Run Report" button is coded as follows:
<button type="submit" id="run" name="_eventId_run">
<fmt:message key="form.button.report.run" />
</button>
The rest of the page binds the parameters to a map in the DAO, such as:
<form:radiobutton path="parameters['service']" class="service" value="..." />
Submitting the form shows that the bind variables are set in the DAO map correctly. Further, the report is able to generate the URL used for redirection.
The form itself resembles:
<form:form modelAttribute="report" action="${flowExecutionUrl}"
method="post">
<fieldset>
<%-- secret tokens --%>
<tiles:insertAttribute name="hidden" />
<%-- includes the requested report form parameters --%>
<jsp:include page="${reportKey}.jsp" />
<%-- includes the aforementioned submit button %-->
<tiles:insertAttribute name="reportButtons" />
</fieldset>
</form:form>
Flow
The flow has three view states: list reports, enter parameters, and run report. The last two are pertinent:
<view-state id="parameters" model="report" view="flow/reports/parameters">
<transition on="run" to="redirect">
<evaluate expression="reportService.run(report)" result="flowScope.url" />
</transition>
</view-state>
<view-state id="redirect" view="externalRedirect:#{flowScope.url}"/>
The reportService.run(report) method is being called. The report parameters are being bound. The return result is, indeed, the correct URL.
Java
The report service itself is reasonably trivial:
public String run(Report report) {
Map<String, String> parameters = report.getParameters();
String url = getConfigurationValue("ReportUrl");
for (String key : parameters.keySet()) {
info("Report Parameter: {} = {}", key, parameters.get(key));
}
return url;
}
Again, the correct url is being returned. There is no controller class. The Report DAO could hardly be simpler:
public class Report extends DAOBase implements Serializable {
/** Values to pass into the report. */
private Map<String, String> parameters;
public Report() {
setParameters(createParameters());
}
// standard accessors and serial version not shown
}
Problem
It appears that the application is performing a POST-Redirect-GET (PRG). The GET does not direct the browser to the URL set by flowScope.url. (I have not verified that flowScope.url contains valid data.) When the POST operation completes, the application is redirected to the parameter flow, rather than the run button redirecting to the redirect flow.
From the schema definition, everything seems correct.
Questions
Using Spring 4.1.2, what needs to change so that the externalRedirect sends the browser to the URL returned by reportService.run(...)?
Does the form need to supply a different value for flowExecutionUrl?
Attempts
Here are some of the various changes that have been made to no avail:
Use $ in the EL, instead of # (i.e., externalRedirect:${flowScope.url})
Use <end-state id="redirect" view="externalRedirect:#{flowScope.url}"/>
Use <view-state id="redirect" view="externalRedirect:http://google.com"/>
Use form:button instead of button
Changing the redirect view-state and removing the expression evaluation causes the redirect to fire. For example:
<transition on="run" to="redirect">
<!-- <evaluate expression="reportService.run(report)" result="flowScope.url" /> -->
</transition>
...
<view-state id="redirect" view="externalRedirect:http://google.com"/>
This, of course, means that the report parameters are never used, which won't work.
Remove the evaluation from the transition:
<transition on="run" to="redirect"/>
Call the run method within an EL statement to generate the URL:
<view-state id="redirect" view="externalRedirect:#{reportService.run(report)}"/>
I have two classes one is Action class MyAction and other is POJO class User. Inside MyAction, i have made getter-setter for User class.
Then I have index.jsp page for user input. In this, I am using struts 2 push tag for the properties stored in User class. It looks something like this:
<s:form action="go" method="post">
<s:push value="user">
<s:textfield name="id" label="usr-id"/> <!-- user.id -->
<s:textfield name="fname" label="first-name"/> <!-- user.fname -->
</s:push>
<s:submit/>
<s:token/>
</s:form>
But I am getting an error. If I remove the push tag and prefix the User properties with user, then it runs fine. Can any one guide me, where I am wrong and how to use push tag for input fields in form.
No matter if you use <s:push>, <s:set/>, <s:iterator> with its var or IteratorStatus attributes... :
to show a value, any way is a good way;
to send a value, the only way is specifying the full "path" in the name attribute.
For example, you can use the pushed object in the value attribute, but to make it work when submitting, you still need to put the user in name attribute:
<s:push value="user">
<s:textfield name="user.id" value="id" /> <!-- user.id -->
<s:textfield name="user.fname" value="fname"/> <!-- user.fname -->
</s:push>
This makes the use of <s:push> in your case totally useless.
But in an use-case where source and destination differs, eg. you read user.fname from ActionSource, and send its value to ActionDestination in a selectedFname String, the jsp would be
<s:push value="user">
<s:textfield name="selectedId" value="id" /> <!-- user.id -->
<s:textfield name="selectedFname" value="fname"/> <!-- user.fname -->
</s:push>
So it would have done "something usefull".
But basing on my experience, you won't pretty much never use push. If you need it, your data structure is probably too complex.
Your code looks like ok, but to send values from textfields you need to push the user object to the stack again. Better do it with some interceptor before the params interceptor populates the action. The same thing is doing modelDriven interceptor.
The Model-Driven interceptor watches for ModelDriven actions and adds the action's model on top of the value stack.
Note: The ModelDrivenInterceptor must come before the both StaticParametersInterceptor and ParametersInterceptor if you want the parameters to be applied to the model.
Note: The ModelDrivenInterceptor will only push the model into the stack when the model is not null, else it will be ignored.
You can use model driven approach it's pushing a model for the view and for the controller. The last is missing from your code.
The example of using ModelDriven approach.
Push is not a ui tag. Push is used for put objects into top of value stack. If your object is not in top of stack you get your values by using object.attributname. If your object is in the value stack, you can access it directly attributename.
I'm new to Stripes and appreciate every hint that brings me nearer to a functioning web-app!
technological setup: java, dynamic web project, stripes, jsp
scenario:
users can login (index.jsp). After correct email-adress and password (LoginFormActionBean.java), the user is forwarded to a welcoming page (loggedin.jsp).
The content on this welcoming page is something like "welcome < username >, you've been successfully logged in!".
implementation:
i have a form in the index.jsp where i take the user input and pass it to a method in the LoginFormActionBean.java --> works!
in the corresponding method i check whether the user is correct and if so, i insert the user in the ActionBeanContext:
getContext.setUser(loggedinUser);
after that i forward to the loggedin.jsp:
return new ForwardResolution("/loggedin.jsp");
the loggedin.jsp contains following important lines:
<jsp:useBean id="loggedinBean" class="mywebapp.controller.LoggedinBean" scope="session" />
...
${loggedinBean.context.user} //show the whole user object
...
<s:form beanclass="mywebapp.controller.LoggedinBean" name="ButtonForm">
<s:submit name="foo" value="PrintUser" />
</s:form>
<s:form beanclass="mywebapp.controller.LoggedinBean" name="TextForm">
<s:text name="user" />
</s:form>
...
the LoggedinBean.java contains a MyActionBeanContext attribute (like the LoginFormActionBean.java).
to get the userobject out of the context i use:
public String getUser(){
return getContext().getUser().toString();
}
furthermore the LoggedinBean.java contains a method, which is annotated with #DefaultHandler and forwards to loggedin.jsp (the same page)
result:
now, what happens is: after logging in correctly, i'm forwarded to the loggedin.jsp,
the line "${loggedinBean.context.user}" is empty and so is the < s:text >-field.
BUT after clicking the "PrintUser" Button, the < s:text >-field in the "TextForm"-form is filled with the user object of the logged in user!
conclusion:
what i think happens, is that the "setContext()" method of the LoggedinBean.java is not called before i manually execute a method in the bean. Because the "setContext()" method in the bean is not called before i press the button!
the online documentation says to use a context attribute in a JSP just write "${actionBean.context.user}". But the context is null!
even the book "pragmatic stripes"(2008) gives no more information about using the ActionBeanContext.
question:
what happens there?
how can i get the "${loggedinBean.context.user}" line to display the logged in user at all?
and how can i get the < s:text >-field to display the user object after loading the JSP, but without pressing the button?
i hope my problem is clear and my remarks are satisfying
I would like to recommend the usage of the MVC pattern. This pattern will lead to an implementation were the Action Beans will act as controllers that handle all http requests and the JSP pages will become passive views with little logic, only accessible via the Action Bean controllers (no direct access to JSP pages any more!).
If you use this pattern, you always have an "actionBean" available in your JPS and thus you can refer to ${actionBean.context} (see: getContext).
I'm doing a project in seam that requires restful URLs. I have a view that is mapped to /group/{group}/{locale}. On that page I have a list of so called messages. Each message has a button to save changes to the message. This is linked to an action bean like this:
<h:commandButton type="submit" value="Save Changes" action="#{groupAction.update}" />
Each message has an anchor, so /group/{group}/{locale}#{id} can be used to make the browser go to that anchor. This is why I need a redirect after the POST:
<page view-id="/group.xhtml">
<rewrite pattern="/group/{group}/{locale}"/>
<param name="group" value="#{groupAction.group}"/>
<param name="locale" value="#{groupAction.locale}"/>
<navigation from-action="#{groupAction.update}">
<redirect view-id="/group.xhtml?group=#{group}&locale=#{locale}##{id}"/>
</navigation>
</page>
Also I have the following redirect rule (UrlRewriteFilter) to get to the proper RESTful URL:
<outbound-rule>
<from>^/group.xhtml\?group=([\.\w]+)&locale=([\.\w]+)\#([\.\w]+)\?cid=(\d*)$</from>
<to type="temporary-redirect" last="true">/group/$1/$2#$3</to>
</outbound-rule>
I strip the conversationId here. This has been tested an works. However seam still appends a '?conversationId={cid}'. So what's the problem? Well, imagine a URL like '/group/{group}/{locale}#{id}?conversationId={cid}'. Oviously the browser doesn't like this and will not automatically go to that anchor.
I did some research and discovered my problem in the seam documentation:
29.1.4.2. Conversation propagation with redirects
This filter allows Seam to propagate the conversation context across browser redirects. It intercepts any browser redirects and adds a request parameter that specifies the Seam conversation identifier. The redirect filter will process all requests by default, but this behavior can also be adjusted in components.xml:
<web:redirect-filter url-pattern="*.seam"/>
I don't need the redirect-filter and I tried putting something invalid in the url-pattern to "disable" the filter. However that didn't work. So my question is now:
How do I disable the redirect-filter in seam?
I can't find the answer. The seam documentation talks about disabling it in web.xml, but my attempts have not been succesful yet.
I've worked out where the unwanted conversationId query string parameter is coming from.
The <redirect/> results in a call to org.jboss.seam.faces.FacesManager.redirect(String viewId, Map<String, Object> parameters,
boolean includeConversationId)
This is called from the following code in org.jboss.seam.faces.Navigator which sets includeConversationId to true:
FacesManager.instance().redirect(viewId, parameters, true);
I cannot see any way to avoid this, so the fix/workaround is to do the redirect programmatically in the action method with:
FacesManager.instance().redirect(viewId, parameters, false);
OK, I managed to kill the redirect-filter by adding this to my compononents.xml:
<web:redirect-filter disabled="true" installed="false" />
However my main problem still exists, so that was apparently not the problem. It still adds the conversationId as an extra query string on the URL. Apperently I'm doing something wrong since it has been done before.
#Adeel Ansari:
components.xml:
<web:rewrite-filter view-mapping="/seam/*" />
pages.xml see my initial question.