Spring Security with overlapping configurations checks authorization in multiple <http> blocks - spring

I have an application using Spring Security 4.2.18 with the security configured in xml. Right now I am introducing Spring Boot 2.5.4 in the project. Since upgrading, I have a problem with the configuration of security for some requests.
I have a block matching specific requests and one matching the rest of all requests.
<http pattern="/api/**" use-expressions="true" authentication-manager-ref="apiAuthenticationManager" >
<http-basic/>
<intercept-url pattern="/api/**" access="hasRole('ROLE_API')"/>
<csrf disabled="true"/>
</http>
<http pattern="/**" auto-config="true" use-expressions="true" create-session="always" disable-url-rewriting="true"
authentication-manager-ref="authenticationManager">
<form-login login-processing-url="/resources/j_spring_security_check" login-page="/login"
username-parameter="j_username"
password-parameter="j_password"
authentication-failure-url="/login?login_error=t" default-target-url="/redirectlogin"/>
<logout logout-url="/resources/j_spring_security_logout"/>
...
<intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>
...
</http>
<authentication-manager id="apiAuthenticationManager">
<authentication-provider user-service-ref="apiUserDetailsService">
</authentication-provider>
</authentication-manager>
The ApiUserDetailsService follows the specification:
#Service
#Transactional
public class ApiUserDetailsService implements UserDetailsService {
...
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
boolean foundAccount = apiConfig.getUsername().equals(username);
if (foundAccount) {
return new User(username, apiConfig.getPassword(), singletonList(new SimpleGrantedAuthority("ROLE_API")));
}
throw new UsernameNotFoundException("Could not finder user with name " + username);
}
}
If I make a request to something under /api and use incorrect Basic auth credentials, I previously got a 401 Unauthorized response. Since upgrading I get redirected to /login and end up in a redirect loop since the credentials are used there as well.
If I remove the <intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/> in the second <http> block, I get the expected behaviour.
How can I configure my application so that my request doesn't try to authorize using the rules in the second <http> block? I have tried using an EntryPoint but it isn't called before the user is erroneously authorized using the method of the generic <http> block.

Have you tried putting <intercept-url pattern="/login*" access="permitAll" /> right above your <intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/> like:
In the second http block:
...
<intercept-url pattern="/login*" access="permitAll" />
<intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>
...

The problem wasn't that the configurations were overlapping but rather that the failed authentication was redirected to /login which in turn was handled by the second <http> configuration.
I solved it by creating an implementation of AuthenticationEntryPoint that sets the 401 Unathorized status on the response:
#Component
public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
}
and in turn referencing to this in my basic authentication configuration:
<http pattern="/api/**" use-expressions="true" authentication-manager-ref="apiAuthenticationManager" >
<http-basic entry-point-ref="basicAuthenticationEntryPoint"/>
<intercept-url pattern="/api/**" access="hasRole('ROLE_API')"/>
<csrf disabled="true"/>
</http>

Related

How to configure one login page with Multiple landing pages which intercept with different url patterns in spring security 4

My requirement is, I have two different landing pages one for user and one for admin. This landing pages has to be appear based on the intercept url pattern which i configure in the spring security xml file. Both the landing page is having a hyperlink to login, when the user click on the login hyperlink of adminLayout.jsp it will load the same login page and when the user click on the login hyperlink of userLayout.jsp it will load the same login page by interacting with the controller for two different url patterns.The url patterns will be /admin and /user.
I stuck here.
How can i configure two different landing pages(adminLayout) and userLayout) in spring security. this tow landing pages is having the same login form, which i want to configure in the spring security form-login for both the url patterns and tow layouts.Before login only the landing page has to appear and then when the user click on the login hyperlink from two different pages it has to make user of spring security provided login-form functionality.Please help me out on this.
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/admin**" access="hasRole('ROLE_ADMIN')" requires-channel="http" />
<intercept-url pattern="/user**" access="hasRole('ROLE_USER')" requires-channel="http" />
<csrf disabled="true"/>
<access-denied-handler ref="accessDeniedHandler"/>
<!-- Here i want to configure the landing pages based on the intercept url pattern if the pattern is /admin i want to dispaly the adminLayout.
If the intercept url pattern is /user i want to display the userLayout. Both this Layout pages is having common login page which user will click from this layout pages.
if want to make use of spring secuiryt for form login..
-->
<form-login login-processing-url="/j_spring_security_check"
login-page="/sslogin" authentication-success-handler-ref="authenticationHandler"
authentication-failure-url="/fail2login?error=true"/>
<logout logout-success-url="/logout" invalidate-session="true" logout-url="/j_spring_security_logout" delete-cookies="JSESSIONID" />
<session-management>
<concurrency-control error-if-maximum-exceeded="true" max-sessions="1" expired-url="/fail2login" />
</session-management>
</http>
<beans:bean id="accessDeniedHandler" class="com.fss.portal.handlers.PortalAccessDeniedHandler">
<beans:property name="accessDeniedURL" value="403"></beans:property>
</beans:bean>
<beans:bean id="authenticationHandler" class="com.fss.portal.handlers.AuthenticationHandler">
</beans:bean>
<beans:bean id="customAuthenticationProvider" class="com.fss.portal.utility.CustomAuthenticationProvider">
<beans:property name="passwordEncoder" ref="bcryptEncoder"></beans:property>
</beans:bean>
<beans:bean id="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<authentication-manager>
<authentication-provider ref="customAuthenticationProvider">
</authentication-provider>
</authentication-manager>
I think you should try creating a AuthenticationEntryPoint implementation with multiple landing page support.
It could be something like this:
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
public class MultipleLandingPageEntryPoint extends LoginUrlAuthenticationEntryPoint
implements AuthenticationEntryPoint {
private Map<String, String> landingPages;
public MultipleLandingPageEntryPoint(String defaultLoginFormUrl, Map<String, String> landingPages) {
super(defaultLoginFormUrl);
this.landingPages = landingPages;
}
public MultipleLandingPageEntryPoint(String defaultLoginFormUrl) {
super(defaultLoginFormUrl);
}
public Map<String, String> getLandingPages() {
return landingPages;
}
public void setLandingPages(Map<String, String> landingPages) {
this.landingPages = landingPages;
}
#Override
protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) {
for(String key : this.landingPages.keySet()){
RequestMatcher rm = new RegexRequestMatcher(key, null);
if(rm.matches(request)){
return this.landingPages.get(key);
}
}
// If not found in the map, return the default landing page through superclass
return super.determineUrlToUseForThisRequest(request, response, exception);
}
}
Then, in your security config, you must configure it:
<beans:bean id="authenticationMultiEntryPoint" class="com.xxx.yyy.MultipleLandingPageEntryPoint">
<beans:constructor-arg value="/user/landing.htm" />
<beans:property name="landingPages">
<beans:map>
<beans:entry key="/user**" value="/user/landing.htm" />
<beans:entry key="/admin**" value="/admin/landing.htm" />
</beans:map>
</beans:property>
</beans:bean>
And use it in your <security:http> element:
<security:http pattern="/admin/landing.htm" security="none" />
<security:http pattern="/user/landing.htm" security="none" />
<security:http auto-config="true" use-expressions="true"
entry-point-ref="authenticationMultiEntryPoint">
If you implement the AuthenticationEntryPoint extending LoginUrlAuthenticationEntryPoint (which I think it's a good idea) check additional parameters on it.
EDIT: I've just updated the class implementation, did not include the latest version
Create A Custom AuthenticationSuccessHandler like below
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response,
Authentication authentication)
throws IOException,
javax.servlet.ServletException {
if(authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN")) {
request.getRequestDispatcher("/admin").forward(request, response);
} else if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_USER")) {
request.getRequestDispatcher("/user").forward(request, response);
}
}
}
And configure it with form-login tag as following
<bean id="customAuthenticationSuccessHandler" class="CustomAuthenticationSuccessHandler" />
<form-login authentication-success-handler-ref="customAuthenticationSuccessHandler" ...>
UPDATE
Create a Controller mappings /landing point to it by <form-login login-page="/landing" .../>. This landing should have links to admin and user landing pages. Which can have links or forms to login.
You can remove protection from these landing pages.
<http pattern="/landing**" security="none"/>
<http pattern="/landing/admin**" security="none"/>
<http pattern="/landing/user**" security="none"/>
And you can write a Custom AuthenticationFailureHandler to redirect to correct login page.

Spring Security custom LogoutSuccessHandler gets strange Authentication object

I am developing an application using the Spring Security (3.1) and I encoutered the following problem. When user logs out, I want to redirect to some custom URL depending if he logs out from a secure page or not. I wrote a custom LogoutHandler, that looks as follow:
#Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String refererUrl = request.getHeader("Referer");
if (requiredAuthentication(refererUrl, authentication)) {
response.sendRedirect(request.getContextPath());
} else {
response.sendRedirect(refererUrl);
}
}
private boolean requiredAuthentication(String url, Authentication authentication){
return !getPrivilegeEvaluator().isAllowed(url, authentication);
}
So, when the user is logging out from the non-secure page he is logged out and redirected to the same URL, and if he is logging ouf from secure page, he goes to index page.
The problem is, that Authentication object that comes to the method is always authenticated (even though, the method is called AFTER loggin out the user, acording to the specification).
My security context:
<http use-expressions="true" disable-url-rewriting="true" request-matcher-ref="requestMatcher" >
<intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" requires-channel="https" />
<intercept-url pattern="/dashboard/**" access="hasRole('ROLE_OWNER')" requires-channel="https" />
<intercept-url pattern="/**" access="permitAll"/>
<form-login login-page="/login"
authentication-success-handler-ref="successHandler"
authentication-failure-url="/login"
login-processing-url="/validate" />
<logout logout-url="/logout" invalidate-session="true" success-handler-ref="logoutSuccessHandler" />
<remember-me services-ref="rememberMeServices" key="KEY" use-secure-cookie="false" />
<session-management session-fixation-protection="migrateSession">
<concurrency-control max-sessions="1" />
</session-management>
</http>
Do you have any idea, why received Authentication is still valid, when gettig to the logoutSuccessHandler? I can't edit this object, because it's fields are final (except the isAuthenticated, but it's not checked by isAllowed() method..)
Looking at Spring Security source code, the LogoutFilter gets the Authentication object from the SecurityContextHolder, keeps it on a local variable, and removes it from the holder, via SecurityContextLogoutHandler. After all LogoutHandlers are called, it calls your LogoutSuccessHandler, and passes the Authentication object.
Even that it says it is valid, it is not anymore in the SecurityContextHolder, so for Spring, the user is logged out.

Spring Security 3.0 : Basic Auth Prompt disappear

I am using spring basic authentication with the following settings in my security xml:
<http use-expressions="true" create-session="never" >
<intercept-url pattern="/**" method="GET" access="isAuthenticated()" />
<intercept-url pattern="/**" method="POST" access="isAuthenticated()" />
<intercept-url pattern="/**" method="PUT" access="isAuthenticated()" />
<intercept-url pattern="/**" method="DELETE" access="isAuthenticated()" />
<http-basic />
</http>
If authentication fails, the browser pop ups a prompt window to renter the user name and password.
Is there any way to make that prompt not pop up at all ?
Most probable the page that is used for authentication failure is also protected. You can try manually to set the failure page to one that is not protected like
<access-denied-handler error-page="/login.jsp"/>
together with
<intercept-url pattern="/*login*" access="hasRole('ROLE_ANONYMOUS')"/>
or
<intercept-url pattern='/*login*' filters='none'/>
or you can use the auto-config='true' attribute of the http element that will fix that for you.See more here
I have also had the same problem for the REST API throwing login dialog in the browser. As you have told , when the browser sees the response header as
WWW-Authenticate: Basic realm="Spring Security Application
It will prompt with a basic authentication dialog.For REST API based login , this is not ideal. Here is how I did it.Define a custom authentication entry point and in the commence
set the header as "FormBased"
response.setHeader("WWW-Authenticate", "FormBased");
application-context.xml configuration below
<security:http create-session="never" entry-point-ref="authenticationEntryPoint" authentication-manager-ref="authenticationManager">
<security:custom-filter ref="customRestFilter" position="BASIC_AUTH_FILTER" />
<security:intercept-url pattern="/api/**" access="ROLE_USER" />
</security:http>
<bean id="authenticationEntryPoint" class="com.tito.demo.workflow.security.RestAuthenticationEntryPoint">
</bean>
Custom entry point class below.
#Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static Logger logger = Logger.getLogger(RestAuthenticationEntryPoint.class);
public void commence( HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException ) throws IOException {
logger.debug("<--- Inside authentication entry point --->");
// this is very important for a REST based API login.
// WWW-Authenticate header should be set as FormBased , else browser will show login dialog with realm
response.setHeader("WWW-Authenticate", "FormBased");
response.setStatus( HttpServletResponse.SC_UNAUTHORIZED );
}
}
Note: I have used spring 3.2.5.Release
Now when the rest API is hit from a restclient like POSTMAN , the server will return 401 Unauthorized.
I have faced the same issue and what I did is create a custom RequestMatcher in my resource server. This prevents Outh2 from sending WWW-Authenticate header.
Example:
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new OAuthRequestedMatcher())
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
}
private static class OAuthRequestedMatcher implements RequestMatcher {
public boolean matches(HttpServletRequest request) {
String auth = request.getHeader("Authorization");
boolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer");
boolean haveAccessToken = request.getParameter("access_token")!=null;
return haveOauth2Token || haveAccessToken;
}
}

Spring Security - need 403 error, not redirect

I am using Spring Security 3.0.4. I have a bunch of web service which are protected by Spring Security. When I access them as an unauthenticated user, Spring Security redirects to login page. Instead of that, I want to return HTTP 403 error. How can I achieve that?
Here is my security config:
<http auto-config="false" use-expressions="true" >
<intercept-url pattern="/authorization.jsp" access="permitAll"/>
<intercept-url pattern="/registration.jsp" access="permitAll"/>
<intercept-url pattern="/api/authorization/auth" access="permitAll"/>
<intercept-url pattern="/api/authorization/new" access="permitAll"/>
<intercept-url pattern="/api/accounts/new" access="permitAll"/>
<intercept-url pattern="/app/**" access="permitAll"/>
<intercept-url pattern="/extjs/**" access="permitAll"/>
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<form-login login-page="/authorization.jsp"
default-target-url="/index.jsp"
authentication-failure-url="/registration.jsp?login_error=1"
always-use-default-target="true"
/>
<logout logout-success-url="/authorization.jsp"
logout-url="/j_spring_security_logout"
invalidate-session="true"/>
</http>
For java configuration you need to do
http.exceptionHandling().authenticationEntryPoint(alwaysSendUnauthorized401AuthenticationEntryPoint);
Where alwaysSendUnauthorized401AuthenticationEntryPoint is innstance of class
public class AlwaysSendUnauthorized401AuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public final void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
LOGGER.debug("Pre-authenticated entry point called. Rejecting access");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
This disables default behavior of Spring (redirecting unauthenticated requests to login form).
Side note:
for such case HTTP code SC_UNAUTHORIZED(401) is better choice than SC_FORBIDDEN(403).
There's an article on the spring forums here that outlines how to get your app determining between the two methods. So far I'm using the following code to secure my data controllers:
<bean id="ep403" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
<sec:http pattern="/data/**" entry-point-ref="ep403" use-expressions="true">
<sec:intercept-url pattern="/**" access="isAuthenticated()"/>
</sec:http>
<bean id="epauth" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<constructor-arg value="/login.html"/>
</bean>
<sec:http pattern="/**" entry-point-ref="epauth" use-expressions="true">
<sec:intercept-url pattern="/**" access="isAuthenticated()"/>
</sec:http>
So the whole DelegatingAuthenticationEntryPoint solution in the article I linked is a bit more heavyweight, but I imagine it does the job just fine as well.
you need to
Create a RequestMatcher to determine which requests should get a 403 (AntPathRequestMatcher may suffice in your case).
Configure the HttpSessionRequestCache to check the matcher and not store those pages for post-login redirect.
Use a DelegatingAuthenticationEntryPoint to either 403 the request outright or redirect to login according to the matcher.
See the example here:
http://distigme.wordpress.com/2012/11/01/ajax-and-spring-security-form-based-login/
This solution worked for me (reference)
http
...
.oauth2Login().permitAll()
.and().exceptionHandling()
.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"))
...
It should return a 403 error unless you configure it to go to another url with this tag:
<sec:access-denied-handler error-page="/urlToGoIfForbidden" />

How to perform custom logic with Spring Security when user is remembered?

I'm using Spring Security 3 and I want to perform some logic (saving some data in the session) when the user is visiting the site and he's remembered. I extended the GenericFilterBean class, perform the logic in the doFilter method, then complete the filter chain by calling the chain.doFilter method. I inserted that filter after the "remember me" filter in the security.xml file.
But the problem is that the filter is executed on each page regardless of whether the user is remembered or not. Is there something wrong with the filter implementation or the position of the filter?
Is the filter chain by default executed on each page?
When making a custom filter should i add it to the web.xml too?
The filter class:
package projects.internal;
import java.io.IOException;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import projects.ProjectManager;
public class rememberMeFilter extends GenericFilterBean {
private ProjectManager projectManager;
#Autowired
public rememberMeFilter(ProjectManager projectManager) {
this.projectManager = projectManager;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
System.out.println("In The Filter");
Authentication auth = (Authentication) SecurityContextHolder
.getContext().getAuthentication();
HttpServletResponse response = ((HttpServletResponse) res);
HttpServletRequest request = ((HttpServletRequest) req);
// if the user is not remembered,do nothing
if (auth == null) {
chain.doFilter(request, response);
}
else {
// the user is remembered save some data in the session
System.out.println("User Is Remembered");
chain.doFilter(request, response);
}
}
}
The security.xml file:
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<global-method-security pre-post-annotations="enabled">
</global-method-security>
<http use-expressions="true" >
<remember-me data-source-ref="dataSource"/>
<intercept-url pattern="/" access="permitAll" />
<intercept-url pattern="/images/**" filters="none" />
<intercept-url pattern="/scripts/**" filters="none" />
<intercept-url pattern="/styles/**" filters="none" />
<intercept-url pattern="/p/login" filters="none" />
<intercept-url pattern="/p/register" filters="none" />
<intercept-url pattern="/p/forgot_password" filters="none" />
<intercept-url pattern="/p/**" access="isAuthenticated()" />
<custom-filter after="REMEMBER_ME_FILTER" ref="rememberMeFilter" />
<form-login login-processing-url="/j_spring_security_check"
login-page="/p/login" authentication-failure-url="/p/login?login_error=1"
default-target-url="/p/dashboard" authentication-success-handler-ref="myAuthenticationHandler"
always-use-default-target="false" />
<logout/>
</http>
<beans:bean id="myAuthenticationHandler" class="projects.internal.myAuthenticationHandler" />
<beans:bean id="rememberMeFilter" class="projects.internal.rememberMeFilter" >
</beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<password-encoder hash="md5" />
<jdbc-user-service data-source-ref="dataSource" />
</authentication-provider>
</authentication-manager>
</beans:beans>
Any help?
I think your confusion comes from the fact that the Spring Security Filter Chain is a single ServletFilter, which contains it's own, internal, chain of SpringSecurityFilters. If you want to add a filter to the that chain you need to subclass SpringSecurityFIlter and implement it's doFilterHttp() method. In your case I might try to write a Pre-Authentication filter that does the logic you want. If no user object is found (hence there was none in the session) then it does some logic and then Spring Security goes on to authenticate the user. Check this out for a more detailed example Pre-Authentication Mechanism.

Resources