Spring Security, programmatic login for json restful web service - spring-security

I am using spring 4.2.1 with spring security 4.0.2
On login, I need to return a json object tree to the client, containing the cached data it requires for the session.
So I've added a the following method:
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody ServerResponse<?> login(#RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
Authentication result = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(result);
Object data = null; // Do stuff here
return new ServerResponse<>(data);
}
My spring security config:
<ss:http auto-config="false" use-expressions="true" entry-point-ref="authenticationEntryPoint">
<ss:anonymous enabled="false" />
<!-- this is enabled by default in spring 4 -->
<ss:csrf disabled="true" />
<ss:custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />
<ss:session-management session-authentication-strategy-ref="sas" />
<ss:port-mappings>
<ss:port-mapping http="8080" https="8443" />
</ss:port-mappings>
<ss:intercept-url pattern="/app/logi**" access="permitAll()" />
<ss:intercept-url pattern="/app/logou**" access="permitAll()" />
<ss:intercept-url pattern="/app/**" access="hasAuthority('user')" />
<ss:intercept-url pattern="/www/**" access="hasAuthority('user')" />
</ss:http>
All the pages I find regarding a programmatic login confirm that what I am doing is fine.
However, when I try and call another web service method later, I get 403 as the client is not logged in.
I read some vague references to having to use a spring filter, but I am not sure how I would get the filter to return the json tree to the client after successful login.
Any suggestions or links to an example on how to do this would be much appreciated.
Thanks

Sooo it turns out the problem was that I was doing Cross Origin Resource Sharing and the browser was not sending the cookie across with the next request.
Basically I was calling the server from html on the file system (with origin file://)
I was handling options calls, but I was not sending back
Access-Control-Allow-Credentials true
headers in the responses and I had to configure angular to send the cookie by passing the flag
withCredentials: true
in the config object to $http.post

Related

Spring Security: Getting error "The server understood the request but refuses to authorize it"

While running the application using Spring Security, I am getting below error on all browsers:
"The server understood the request but refuses to authorize it"
I tried by changing Roles from "ROLE_ADMIN" to "ROLE_USER" in "spring-security.xml" file.
Below is "spring-security.xml"
<http auto-config="true">
<intercept-url pattern ="/admin" access = "hasRole('ROLE-USER')"/>
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name = "abc" password = "xyz" authorities="hasRole('ROLE-USER')" />
</user-service>
</authentication-provider>
</authentication-manager>
Below is SpringController Class:
#Controller
public class SpringController {
#RequestMapping(value = "/")
public String homePage() {
return "HomePage";
}
#RequestMapping(value="/admin", method=RequestMethod.GET)
public String loginPage() {
return "login";
}
HomePage.jsp and login.jsp pages are loaded property but after passing credentials on login.jsp getting error:
HTTP Status 403 – Forbidden
Type: Status Report
Message: Access is denied
Description: The server understood the request but refuses to
authorize it.
Apache Tomcat/7.0.90
403 is a very generic error code. I was facing the same issue but after making some changes I am able to make it work. Still not sure if the problem was with password encryption or configuration of form-login tag.
<security:http auto-config="true" >
<security:intercept-url pattern="/login*" access="isAnonymous()" />
<security:intercept-url pattern="/**" access="isAuthenticated()"/>
<security:form-login login-page="/login" login-processing-url="/login-user" authentication-failure-url="/login?error=true" />
<security:csrf disabled="true" />
<security:logout logout-success-url="/" />
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="admin" password="{noop}admin" authorities="ROLE_USER" />
</security:user-service>
</security:authentication-provider >
</security:authentication-manager>
Ignore the security: prefix in the tags.
{noop} in front of the password ensures that I am not using any encryption for the password.
Controller to show login JSP
#Controller
#RequestMapping("/login")
public class LoginController {
#RequestMapping(value = { "/", "" }, method = { RequestMethod.GET})
public String login(HttpServletRequest request) {
System.out.println("LoginController.login() "+request.getRequestURI());
return "login";
}
}
Form action
<form name='loginForm' action="login-user" method='POST'>
I got this issue but not while using spring. We were hosting two instances of our own in house application on the same server but using two different ports. Login to one instance was okay, however login to the other one from the same browser (another tab) caused the error above to be thrown from tomcat, preceded with this message "CSRF nonce validation failed".
The workaround I did is to login to the other instance from different browser. I know that this is not a fix but it may help if you have a situation similar to mine
This happens because you use normal <form> tags with spring security, while it checks for CSRF attacks.
The solution is:
Add CSRF hidden field with each <form> tag
<form action="..." method="POST">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
Or use spring MVC <form:form> tags which include this hidden field automtically (Recommended)
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<form:form action="..." method="POST">
</form:form>
This also happened with me when I accidentally launched two instances of my application on different tomcat servers. I resolved it by removing the working directories of the application in my webapps and work folders, and restarting the server.
use <form:form> tags when you're dealing with spring security, instead of manually adding the CSRF token for each forms as below
<form:form action="", method="">
</form:form>

Oauth 1.0a consumer code equesting an access token twice

I've setup a consumer app, and most of the oauth workflow looks correct, but for some reason after the callback url is invoked by the provider, it tries to get an access token TWICE. The first time works
http://localhost:8080/app/ws/oauth/token
[OAuth oauth_consumer_key="itd79n64zlwv5hhv", oauth_nonce="cac26978-c36c-4a8b-8f3e-3e779ff927ab", oauth_signature="5c8BM9qQoijXC2f5IXpQGtSQsys%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1458938403", oauth_token="5451cf20-7eed-4797-819c-ee2316981654", oauth_verifier="c56de555-79df-455e-ab87-f5f11b953fef", oauth_version="1.0"]
response is a 200, payload includes oauth_token=a95d6305-4261-4c1d-a9b0-43411a0c2f2c&oauth_token_secret=573702d2-70ca-412c-84e5-868e9ee07169
but then, it calls the URL again.
http://localhost:8080/app/ws/oauth/token
[OAuth oauth_consumer_key="itd79n64zlwv5hhv", oauth_nonce="6c013ef9-2f3c-49dd-84fb-97db73b5fb39", oauth_signature="5RTQE5XtcqUwEFVvYQjExhH1eio%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1458938403", oauth_token="5451cf20-7eed-4797-819c-ee2316981654", oauth_verifier="c56de555-79df-455e-ab87-f5f11b953fef", oauth_version="1.0"
which causes an exception on the server since the request token has been removed and the access token has already been issued.
When stepping through the code, I can see that the OAuthConsumerContextFilter stores the access token fine after the first call.
Somehow the filter chain ends up bring it back to readResource in CoreOAuthConsumerSupport with the request token.
I built the consumer app using spring-boot.
from: applicationContext.xml
<bean id="oscarService" class="com.mdumontier.oscar.labline.service.OscarService">
<property name="oscarRestTemplate">
<bean class="org.springframework.security.oauth.consumer.client.OAuthRestTemplate">
<constructor-arg ref="oscar" />
</bean>
</property>
</bean>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="marissa" password="wombat" authorities="ROLE_USER" />
<security:user name="sam" password="kangaroo" authorities="ROLE_USER" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
<security:http auto-config='true' >
</security:http>
<oauth:consumer resource-details-service-ref="resourceDetails" oauth-failure-page="/oauth_error.jsp">
<oauth:url pattern="/oscar/**" resources="oscar"/>
</oauth:consumer>
<oauth:resource-details-service id="resourceDetails">
<oauth:resource id="oscar"
key="itd79n64zlwv5hhv"
secret="d3psvmrn8k1xws9x"
request-token-url="http://localhost:8080/app/ws/oauth/initiate"
user-authorization-url="http://localhost:8080/app/ws/oauth/authorize"
access-token-url="http://localhost:8080/app/ws/oauth/token"/>
</oauth:resource-details-service>
Spring Boot automatically registers any Beans which implement Filter in the main application filter chain. See: https://stackoverflow.com/a/28428154 for a bit more detail.
The oauth:consumer helper registers both OAuth filters as beans, and seems to not have been updated in a while. I couldn't even get the XML config to work properly under the latest Spring Boot. Anyway, this means that both will be run twice, and in the case of the OAuthConsumerContextFilter this is destructive since it will run outside the security sub-chain and fail every time.
To fix this you have two options.
One, hint to Spring Boot to avoid this behavior by providing a FilterRegistrationBean for each filter it's automatically picking up, like so:
#Bean
public FilterRegistrationBean registration(OAuthConsumerContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
Two, bypass the XML config entirely and use Java config. I've posted a complete working code sample of getting on OAuth 1 consumer in Spring Boot using Java config in this answer: https://stackoverflow.com/a/42143001/2848158
Within the Java config, you would have to either repeat the FilterRegistrationBean trick, or just not register those filters as beans in the first place but rather create and register instances directly with the Security filter chain.

update spring security to match url suffix

my spring mvc application uses the following url
http://xyz.com/<war name>/springmvc/login
I have updated my web.xml
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/springmvc/*</url-pattern>
</filter-mapping>
I updated spring security xml file as follows
<http access-decision-manager-ref="accessDecisionManager" auto-config="true">
<intercept-url pattern="/springmvc/welcome*" access="ADMIN" />
<form-login login-page="/springmvc/login" default-target-url="/springmvc/welcome"
authentication-failure-url="/springmvc/loginfailed" />
<logout logout-success-url="/springmvc/logout" />
<remember-me data-source-ref="dataSource"/>
</http>
If i type springmvc/login or springmvc/welcome it goes to login page. but when I enter the username and password, I get a 404. The url changes to http://xyz.com/UserInterface/springmvc/j_spring_security_check. I expect to see hello.jsp as per the controller below
my login controller is as follows
#RequestMapping(value="/welcome", method = RequestMethod.GET)
public String printWelcome(ModelMap model) {
User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String name = user.getUsername();
model.addAttribute("username", name);
model.addAttribute("message", "Spring Security login + database example");
//logical view name
return "hello";
}
The default login-processing-url property value for form-login is /j_spring_security_check.
i.e. the login form will be posted to this url.
If you context root is UserInterface the default url would be http://xyz.com/UserInterface/j_spring_security_check, but you are creating the url as http://xyz.com/UserInterface/springmvc/j_spring_security_check which spring-security is not able to understand.
You have two options
Change you springSecurityFilterChain url pattern to /* from
/springmvc/*, and make sure that your custom-login form has action
as //j_spring_security_check
Add custom login-processing-url path in form-login

Configuring spring security to redirect user to previous location after session timeout

I have a slight issue with my configuration of spring security and the InvalidSessionStrategy implementation I use.
Say a user is connected to the app and is viewing page: /userArea/thePage and their session times out, the user will first be redirected to the /signin page. Then, upon successful signin, they will be redirected to the home page of they personal area (/userArea) whereas I want them to come back to where they were located when the session timed out i.e. /userArea/thePage.
Is this possible?
If so how do I need to alter my config/app?
Here is my current config:
<beans:bean id="sessionManagementFilter" class="org.springframework.security.web.session.SessionManagementFilter">
<beans:constructor-arg name="securityContextRepository" ref="httpSessionSecurityContextRepository" />
<beans:property name="invalidSessionStrategy" ref="simpleRedirectInvalidSessionStrategy" />
</beans:bean>
<beans:bean id="simpleRedirectInvalidSessionStrategy" class="org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy">
<beans:constructor-arg name="invalidSessionUrl" value="/signin" />
<beans:property name="createNewSession" value="true" />
</beans:bean>
<http auto-config="true" use-expressions="true">
<custom-filter ref="sessionManagementFilter" before="SESSION_MANAGEMENT_FILTER" />
<form-login login-processing-url="/resources/j_spring_security_check" login-page="/signin" authentication-failure-url="/signin?login_error=t" default-target-url="/userArea" />
<logout logout-url="/resources/j_spring_security_logout" logout-success-url="/signin" />
...
edit 1: Let me better specify my requirements:
When a user session times out, I want the user to be redirected to the saved request (the url they requested before being redirected to the signin page).
However, when they initially signin with the app, I want them to be redirected to the home page of the personal area.
Are my requirements possible to implement using solution suggested by Carsten (see below)?
You could set the always-use-default-target="true" in the form-login tag. This redirects the user to the url they where trying to access before being intercepted to login.
But this will be the standard behaviour and not only in the case of a session timeout. Depending on the application this might not be what you want.
Edit:
To do what you want you need to find a way to save the information on which page the user was when the session timedout. I don't know of any out of the box solution for this problem, since there is no state that indicates whether or not the user timed out or logged out manually.
What needs to be done ist to:
set a flag or save the page-url on session timeout
check in a custom AuthenticationSuccesHandler and redirect accordingling
If I would implement somehing like that I would most likely store the page-url. Also there are a few tricky things with this from an UX perspective. What happens if the saved page relies on a state achieved earlier? (I assume thats the reason you want the User to go to the default-url on normal login?) What happens if the user just does not log out shuts down sleeps for the night and logs in navigating to the login page (does the flag/page-url time out?)? etc.
In general I think it would be better use the always-use-default-target="true" since this adds the comfort of bookmarking any page and not having to navigate there at each login.
Looks like it's common issue for any Spring project.
Spring developers thought that this is undocumented behavior https://github.com/spring-projects/spring-security/issues/1981, my business users are thinking that it's a bug.
So, as a result we need to do some custom implementation)))
Personally for me it's a bug and after making a custom implementation I don't understand why it's not fixed at Spring Framework.
As in a lot of other cases we have no choice and just copy-paste SimpleRedirectInvalidSessionStrategy and add our custom code.
You can even more simplify this code(I just make a customization which can be used OOTB in Spring):
public class CustomInvalidSessionStrategy implements InvalidSessionStrategy {
private final Log logger = LogFactory.getLog(this.getClass());
private String destinationUrl = null;
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private boolean createNewSession = true;
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException {
this.logger.debug("Starting new session (if required) and redirecting to '" + this.destinationUrl + "'");
if (this.createNewSession) {
request.getSession();
}
if (destinationUrl == null) {
this.redirectStrategy.sendRedirect(request, response, request.getRequestURI());
} else {
this.redirectStrategy.sendRedirect(request, response, this.destinationUrl);
}
}
public void setCreateNewSession(final boolean createNewSession) {
this.createNewSession = createNewSession;
}
public void setInvalidSessionUrl(final String invalidSessionUrl) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(invalidSessionUrl), "url must start with '/' or with 'http(s)'");
this.destinationUrl = invalidSessionUrl;
}
}
And some extra configuration for Spring security:
<security:http ...>
...
<security:session-management invalid-session-strategy-ref="customInvalidSessionStrategy" />
...
</security:http>
<bean id="customInvalidSessionStrategy" class="com.custom.web.security.CustomInvalidSessionStrategy"/>

Recording logins with Spring Security

I want to log every login in my web application. I was able to access the logins which take place through UsernamePasswordAuthenticationFilter but I don't know how to log users who log in using remember-me functionality. I tried overriding the
createSuccessfulAuthentication(HttpServletRequest request, UserDetails user)
of TokenBasedRememberMeServices, but then logouts are recorded too, because the remember-me service re-authenticates the user.
The best way of logging authentication success and failures is to use a Spring ApplicationListener.
Spring Security publishes various events for authentication success and failure which you can listen for. Events are also published when access is denied to a resource.
You can look at LoggerListener as an example. Start by adding one of those to your application context and it will automatically log authentication events at warn level.
Regarding remember-me logins, if you logout and then access the site immediately afterwards, and are re-authenticated using a remember-me cookie, then technically that is the same as any other remember-me authentication, so there's not much you can do about it.
However, if your logout success URL is passing through the remember-me filter, and that is how the new session is being created (without any additional action from the user), then simply omit it that page from the security filter chain.
For logging each sucessful login i think best way is to create LoginSucessHandler and specify authentication-success-handler for normal login as well as remember-me. i have done this with below code and configuration.
#Service
public class LoginSucessHandler extends
SavedRequestAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
User user = (User) authentication.getPrincipal();
// record login success of user
super.onAuthenticationSuccess(request, response, authentication);
}
}
<http auto-config="true" use-expressions="true">
<form-login login-page="/login"
authentication-failure-url="/login.hst?error=true"
**authentication-success-handler-ref="loginSucessHandler"** />
<logout invalidate-session="true" logout-success-url="/home"
logout-url="/logout" />
<remember-me key="jbcp" **authentication-success-handler-ref="loginSucessHandler"**/>
<session-management>
<concurrency-control max-sessions="1" />
</session-management>
</http>
I think in your case will help solution when you will use your custom filter, which will intercept every request to your application. In this filter you can log username for every request.
Here I described how to add your custom filter. You just need to change functionality to what you want. And don't forhet to put your filter after security filter chain in web.xml.

Resources