Is this a bug in Spring Security?
org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter
line:134
...
Object principal = getPreAuthenticatedPrincipal(request);
if (checkForPrincipalChanges &&
!currentUser.getName().equals(principal)) {
logger.debug("Pre-authenticated principal has changed to " + principal + " and will be reauthenticated");
...
Shouldn't it consider a null preAuthenticatedPrincipal to be a non change?
I shouldn't have to send the preAuthenticatedPrincipal with every request should I?
Shouldn't there be a check to see if this value is null?
Shouldn't this be
Object principal = getPreAuthenticatedPrincipal(request);
if (checkForPrincipalChanges &&
principal!=null &&
!currentUser.getName().equals(principal)) {
logger.debug("Pre-authenticated principal has changed to " + principal + " and will be reauthenticated");
Notice addition of principal!=null &&
This was found in spring-security-web-3.0.2.RELEASE.jar
If this is indeed a bug then I think I am working around it by adding the following override to my implementation:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (getPreAuthenticatedPrincipal((HttpServletRequest) request) != null) {
super.doFilter(request, response, chain);
} else {
//if the request did not include a preauthenticated principal then we should just continue are merry way down the filter chain
chain.doFilter(request, response);
}
}
Let me know if I am wrong about this being a bug and whether I missed anything in my workaround.
This is not a bug for the following reasons.
doAuthenticate() method returns without an error if preauthenticated principal is null.
In requiresAuthentication() method, you can turn on or off checkForPrincipalChanges
The following check happens only if currentUser is not null.
if (checkForPrincipalChanges && !currentUser.getName().equals(principal)) {
This check should happen as it is, since there is indeed a change in principal - from a non-null currentUser to a null principal now.
Related
I'm building app on spring webflux, and i'm stuck because spring security webflux (v.M5) did not behave like Spring 4 in term of exception handling.
I saw following post about how to customise spring security webflux:
Spring webflux custom authentication for API
If we throw exception let say in ServerSecurityContextRepository.load, Spring will update http header to 500 and nothing i can do to manipulate this exception.
However, any error thrown in controller can be handled using regular #ControllerAdvice, it just spring webflux security.
Is there anyway to handle exception in spring webflux security?
The solution I found is creating a component implementing ErrorWebExceptionHandler. The instances of ErrorWebExceptionHandler bean run before Spring Security filters. Here's a sample that I use:
#Slf4j
#Component
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
#Autowired
private DataBufferWriter bufferWriter;
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
AppError appError = ErrorCode.GENERIC.toAppError();
if (ex instanceof AppException) {
AppException ae = (AppException) ex;
status = ae.getStatusCode();
appError = new AppError(ae.getCode(), ae.getText());
log.debug(appError.toString());
} else {
log.error(ex.getMessage(), ex);
}
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
exchange.getResponse().setStatusCode(status);
return bufferWriter.write(exchange.getResponse(), appError);
}
}
If you're injecting the HttpHandler instead, then it's a bit different but the idea is the same.
UPDATE: For completeness, here's my DataBufferWriter object, which is a #Component:
#Component
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
#Slf4j
public class DataBufferWriter {
private final ObjectMapper objectMapper;
public <T> Mono<Void> write(ServerHttpResponse httpResponse, T object) {
return httpResponse
.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = httpResponse.bufferFactory();
try {
return bufferFactory.wrap(objectMapper.writeValueAsBytes(object));
} catch (Exception ex) {
log.warn("Error writing response", ex);
return bufferFactory.wrap(new byte[0]);
}
}));
}
}
There is no need to register any bean and change default Spring behavior. Try more elegant solution instead:
We have:
The custom implementation of the ServerSecurityContextRepository
The method .load return Mono
public class HttpRequestHeaderSecurityContextRepository implements ServerSecurityContextRepository {
....
#Override
public Mono<SecurityContext> load(ServerWebExchange exchange) {
List<String> tokens = exchange.getRequest().getHeaders().get("X-Auth-Token");
String token = (tokens != null && !tokens.isEmpty()) ? tokens.get(0) : null;
Mono<Authentication> authMono = reactiveAuthenticationManager
.authenticate( new HttpRequestHeaderToken(token) );
return authMono
.map( auth -> (SecurityContext)new SecurityContextImpl(auth))
}
}
The problem is: if the authMono will contains an error instead of Authentication - spring will return the http response with 500 status (which means "an unknown internal error") instead of 401. Even the error is AuthenticationException or it's subclass - it makes no sense - Spring will return 500.
But it is clear for us: an AuthenticationException should produce the 401 error...
To solve the problem we have to help Spring how to convert an Exception into the HTTP response status code.
To make it we have can just use the appropriate Exception class: ResponseStatusException or just map an original exception to this one (for instance, by adding the onErrorMap() to the authMono object). See the final code:
public class HttpRequestHeaderSecurityContextRepository implements ServerSecurityContextRepository {
....
#Override
public Mono<SecurityContext> load(ServerWebExchange exchange) {
List<String> tokens = exchange.getRequest().getHeaders().get("X-Auth-Token");
String token = (tokens != null && !tokens.isEmpty()) ? tokens.get(0) : null;
Mono<Authentication> authMono = reactiveAuthenticationManager
.authenticate( new HttpRequestHeaderToken(token) );
return authMono
.map( auth -> (SecurityContext)new SecurityContextImpl(auth))
.onErrorMap(
er -> er instanceof AuthenticationException,
autEx -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, autEx.getMessage(), autEx)
)
;
)
}
}
I just went trough lots of documentation, having a similar problem.
My solution was using ResponseStatusException. AccessException of Spring-security seems to be understood.
.doOnError(
t -> AccessDeniedException.class.isAssignableFrom(t.getClass()),
t -> AUDIT.error("Error {} {}, tried to access {}", t.getMessage(), principal, exchange.getRequest().getURI())) // if an error happens in the stream, show its message
.onErrorMap(
SomeOtherException.class,
t -> { return new ResponseStatusException(HttpStatus.NOT_FOUND, "Collection not found");})
;
If this goes in the right direction for you, I can provide a bit better sample.
I am trying to create standard error page for zuul server so that I can redirect exception to this page?
Currently, I have created a zuul filter to catch zuul exception as below:
code snippets
#Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
Object e = ctx.get("error.exception");
if (e != null && e instanceof ZuulException) {
ZuulException zuulException = (ZuulException)e;
LOG.error("Zuul failure detected: " + zuulException.getMessage(), zuulException);
// Remove error code to prevent further error handling in follow up filters
ctx.remove("error.status_code");
// Populate context with new response values
ctx.setResponseBody("Internal Server Error : Please contact Phoenix Admin");
ctx.getResponse().setContentType("application/json");
ctx.setResponseStatusCode(500); //Can set any error code as excepted
}
}
catch (Exception ex) {
LOG.error("Exception filtering in custom error filter", ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
appreciate for any advice?
I will have to spend some time to see how you can read from the error page I am traveling right now. But setting the response body by reading content might not be a bad option definitely might not be perfect.
Checkout the below code if it helps.
Also add some code that you might be trying and is not working its easier that way to answer and help.
You need to make sure the filter runs after the predecoration filter.
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String url = UriComponentsBuilder.fromHttpUrl("http://localhost:8082").path("/outage").build()
.toUriString();
ctx.set("requestURI", url);
return null;
}
Check this out for more information:- https://github.com/spring-cloud/spring-cloud-netflix/issues/1754
Hope this helps you.
I am using Spring security java config and I wanted to know a way to implemented log-out for multiple urls. i.e.
logout().logoutRequestMatcher(new
AntPathRequestMatcher("/invalidate")).logoutUrl("/logout");
In this code the normal logout url "/logout" works fine and its a post request but i also want the user to logout for the url "/invalidate" which doesn't seem to work.
According to Spring Security tutorial, it seems that the next is more elegant approach:
In the security form-login tag just add something like this:
<security:logout logout-url="/logout" success-handler-ref="logoutHandler"/>
Every time that you'll hit /logout URL the logoutHandler will be invoked, and on it, you can decide how to behave after a successful logout.
From Spring docs:
All you need to do is to create a new class that implements the interface marked in the image and implement its single method.
On that method decide how to act after a successful logout. for example:
#Component("logoutHandler")
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
#Override
public void onLogoutSuccess(HttpServletRequest request,HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if(request.getParameter("msgShow") != null && request.getParameter("msgShow").equals("false")){
redirectResponse(request, response, "http://" + request.getServerName() + ":" + request.getServerPort() + "/my_web_app/home?logout=false");
}
else{
redirectResponse(request, response,"http://" + request.getServerName() + ":" + request.getServerPort() + "/my_web_app/home?logout=true");
}
}
private void redirectResponse(HttpServletRequest request, HttpServletResponse response, String destination) {
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
response.setHeader("Location", destination);
}
}
Now don't forget to add a #Component annotation to the above logout handler + on security configuration file add the next 2 statements:
<context:annotation-config />
<context:component-scan base-package="package.to.logout.handler" />
This might not be the most elegant way, but you can just specify a #Controller that is mapped to all the URLs you want for logout, e. g.
#Controller
public class LogoutController {
final String logoutRedirectUrl = "redirect:http://yourredirect.xy";
#RequestMapping("/logout")
public String logout1(HttpServletRequest request) throws ServletException {
request.logout();
return logoutRedirectUrl;
}
#RequestMapping("/second/logout/")
public String logout2(HttpServletRequest request) throws ServletException {
request.logout();
return logoutRedirectUrl;
}
}
Just a quick tip, the actual matching should be improved.
List logoutUrls = Arrays.asList(
"/rest/logout1",
"/rest/logout2"
);
RequestMatcher rm = new RequestMatcher() {
#Override
public boolean matches(HttpServletRequest request) {
String uriStr = request.getRequestURI().toString();
return logoutUrls.stream()
.filter(lu -> uriStr.contains(lu))
.findFirst()
.isPresent();
}
};
and then register the request matcher:
http.logout(logout -> logout.logoutRequestMatcher(rm));
I have inherited a Grails app, which uses the Acegi 0.5.3 plugin.
The application can be accessed via two completely different URLs e.g., app.domainone.com and app.domaintwo.com. The domain names map to two different user communities. Now I have been tasked with restricting user access from only the domain that they are related to. At the moment the users can visit any of the domains and login to the application.
I have some clue about how Acegi works but, can't say I understand all of it yet. So wanted to ask how I would be able to achieve this.
In an ideal scenario, when the user tries to login, I would like to redirect (if required) to their 'relevant' domain and automagically sign them in with their given credentials. However, as an interim solution even a plain redirect to the relevant login page would suffice.
Here goes my CustomAuthenticationProcessingFilter. There is probably a better solution out there but this helped me with the little knowledge that I have of Grails and Spring Security.
class CustomAuthenticationProcessingFilter extends GrailsAuthenticationProcessingFilter implements
InitializingBean {
//def authenticationManager
#Override
public int getOrder() {
return FilterChainOrder.AUTHENTICATION_PROCESSING_FILTER
}
#Override
void doFilterHttp(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) throws IOException,
ServletException {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
def loginUrl = "${request.getRequestURL().toString() - request.getRequestURI().toString()}${request.contextPath}"
def username = request.getParameter("j_username")
def password = request.getParameter("j_password")
if ( loginUrl && username && password) {
def user = User.findByEmailOrCompanyEmail(username,username)
if(user) {
def query = """select c from Community c, UserCommunity uc
where c.id = uc.comm.id
and uc.user.id = :userId"""
def comm = Community.executeQuery(query,[userId:user.id])
comm = comm?(comm?.get(0)):null
if(loginUrl!=comm?.url) {
println "Trying to login using the wrong URL"
response.sendRedirect(comm.url+'/login/auth')
return
}
}
}
}
//Resume the normal flow
super.doFilterHttp(request, response, chain)
}
#Override
public void afterPropertiesSet() throws Exception {
// TODO Auto-generated method stub
}
}
Grails 1.3.7
Spring-Security-Core 1.1.2
I've implemented a custom class that extends AjaxAwareAuthenticationSuccessHandler so that specific roles can be taken to specific URL's after logging in which works great. However, if the session expires I need to be able to take the user to the requested URL when the session expired, overriding the Role based URL.
Here is a simplified version of my code
class MyAuthSuccessHandler extends AjaxAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response,
final Authentication authentication) throws ServletException, IOException {
def goAdmin = false
authentication.authorities.each { ga ->
if (ga.authority.equals('ROLE_ADMIN')) {
goAdmin = true
}
}
if (goAdmin) {
response.sendRedirect(request.contextPath + '/admin/index')
}else{
super.onAuthenticationSuccess(request, response, authentication)
}
}
}
I tried adding a call to determineTargetUrl(request, response) but it always returns '/' even though I've requested a resource like /admin/foo which is protected.
Thanks.
Requesting
super.determineTargetUrl(request, response);
should work, if you use SavedRequestAwareAuthenticationSuccessHandler as super class. I am not sure if it is possible for you to switch to this class in your scenario. Maybe this can help, but I guess you are fully aware of it: http://omarello.com/2011/09/grails-custom-target-urls-after-login/