Spring MVC view controller redirection - java

Is it possible not to redirect to a view without query parameters? I have a login view and I want to redirect to login?error url in browser when login fails. I have my own AuthenticationFailureHandler:
public class SomeCustomHandler implements AuthenticationFailureHandler {
private final SimpleUrlAuthenticationFailureHandler authenticationFailureHandler = new SimpleUrlAuthenticationFailureHandler("/login?error");
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
authenticationFailureHandler.onAuthenticationFailure(request, response, exception);
}
}
configured in WebSecurityConfigurerAdapter:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.failureHandler(new SomeCustomHandler())
.permitAll();
}
But when login fails, returned login?error is redirected in user to login ignoring ?error parameter.
Here is my MvcWebConfigurer:
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login/**").setViewName("login");
}

I think you can try to do something like that:
public class SomeCustomHandler implements AuthenticationFailureHandler {
private final SimpleUrlAuthenticationFailureHandler authenticationFailureHandler = new SimpleUrlAuthenticationFailureHandler("/login?error");
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
if(request.getPathInfo().startsWith("/login") && !request.getParameterMap().isEmpty()) {
authenticationFailureHandler.setUseForward(true);
}
authenticationFailureHandler.onAuthenticationFailure(request, response, exception);
}
}

Related

Spring Boot - Spring Security - InvalidBearerTokenException Error Handling

Similar to this question but it got no answers: Spring Security: Handle InvalidBearerTokenException in #ExceptionHandler
I have similar code and I'm trying to catch org.springframework.security.oauth2.server.resource.InvalidBearerTokenException when a user has supplied invalid/expired/bad JWT format.
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
resolver.resolveException(request, response, null, e);
}
}
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private CustomAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
#Override
protected void configure(HttpSecurity http) throws Exception
{
// other config here
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer().jwt();
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
}
I've also implemented the #ExceptionHandler of AuthenticationException for custom response.
#ExceptionHandler({AuthenticationException.class})
protected ResponseEntity<Object> handleAuthException(AuthenticationException ex, WebRequest req)
{
CustomResponse response = ...
return new ResponseEntity<>(response, ...);
}
InvalidBearerTokenException is a subclass of AuthenticationException.
Any idea why this AuthenticationEntryPoint code is not catching it? I've also tried adding logging inside the commence method but it's not being called when InvalidBearerTokenException is thrown, but other AuthenticationException does.
You have to specify this AuthenticationEntryPoint inside the OAuth2ResourceServerConfigurer, like so:
#Override
protected void configure(HttpSecurity http) throws Exception
{
// other config here
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer().jwt().and()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
When you set it, the Configurer will change the AuthenticationEntryPoint that is used inside the BearerTokenAuthenticationFilter, see here.

Spring security: Failed authentication with more details

I'm trying to get the exact motive for a failed autentication (i.e wrong password, user doesn't exist, and so on) problem is, no matter how I try do simulate this actions I always get "Full authentication is required to access this resource", how can I get a more detailed exception ? TKS in advance !
Here's my code:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService;
#Autowired
UnauthorizedCustomResponse unauthorizedCustomResponse;
#Autowired
AccessDeniedCustomResponse accessDeniedCustomResponse;
#Autowired
CryptographyService cryptographyService;
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userService)
.passwordEncoder(cryptographyService.getCryptography());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/signin", "/api/public/**", "/api/private/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedCustomResponse)
.accessDeniedHandler(accessDeniedCustomResponse)
.and()
.httpBasic();
}
}
#Component
public class AccessDeniedCustomResponse implements AccessDeniedHandler {
private final Logger logger = LogManager.getLogger(this.getClass());
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception)
throws IOException, ServletException {
logger.error("Usuário não autenticado: {}", exception.getMessage());
CustomException customException = new CustomException(CustomExceptionMessage.CUSTOM_EXCEPTION_NOT_MAPPED, exception);
customException.flush(response);
}
}
#Component
public class UnauthorizedCustomResponse implements AuthenticationEntryPoint {
private final Logger logger = LogManager.getLogger(this.getClass());
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
logger.error("Acesso não autorizado: {}", exception.getMessage());
CustomException customException = new CustomException(CustomExceptionMessage.ACCESS_DENIED, exception);
customException.flush(response);
}
}
You can analyze the specific exception that was thrown from the AuthenticationException superclass and act accordingly:
#Component
public class UnauthorizedCustomResponse implements AuthenticationEntryPoint {
private final Logger logger = LogManager.getLogger(this.getClass());
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
if(exception.getClass().isAssignableFrom(UsernameNotFound.class)) {
//Username not found
}
else if (exception.getClass().isAssignableFrom(BadCredentials.class)) {
//Bad credentials error
}
else if (exception.getClass().isAssignableFrom(AccountStatusException.class)) {
//Accound status exception
}
logger.error("Acesso não autorizado: {}", exception.getMessage());
CustomException customException = new CustomException(CustomExceptionMessage.ACCESS_DENIED, exception);
customException.flush(response);
}
}
You can find a better list of AuthenticationException known subclasses here: https://docs.spring.io/spring-security/site/docs/4.2.19.RELEASE/apidocs/org/springframework/security/core/AuthenticationException.html

Redirect loop when x509 authentication fails

I have spring boot application with x509 authentication. My problem is that when authentication fails i get redirect loop instead of error screen.
When authentication fails i throw UsernameNotFoundException loadUserDetails from method in ArhivX509UserDetailsService.java
My code is as follows:
SecurityConfiguration.java
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("x509UserDetailsService")
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> iX509UserDetailsService;
#Override
protected void configure(HttpSecurity pHttp) throws Exception {
//#formatter:off
pHttp
.authorizeRequests()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/error").permitAll()
.antMatchers("/error401").permitAll()
.anyRequest().authenticated()
.and()
.x509()
.subjectPrincipalRegex("(.*)")
.authenticationUserDetailsService(iX509UserDetailsService)
.and()
.addFilterAfter(new X509ErrorCheckerFilter(), X509AuthenticationFilter.class)
.addFilterBefore(new LoggerMDCFilter(), X509ErrorCheckerFilter.class)
.exceptionHandling()
.authenticationEntryPoint(unauthorizedEntryPoint())
.accessDeniedPage(AppUrls.ERROR_401)
.and()
.requiresChannel()
.anyRequest()
.requiresSecure()
.and()
.sessionManagement()
.maximumSessions(1)
.and()
.and()
.logout()
.invalidateHttpSession(true)
.deleteCookies("SESSION", "JSESSIONID")
.logoutSuccessUrl("http://www.google.com")
.permitAll();
//#formatter:on
}
#Bean
public AuthenticationEntryPoint unauthorizedEntryPoint() {
return new AuthenticationEntryPoint() {
#Override
public void commence(HttpServletRequest pRequest, HttpServletResponse pResponse,
AuthenticationException pAuthException) throws IOException, ServletException {
pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
};
}
}
ArhivX509UserDetailsService.java
#Service("x509UserDetailsService")
public class ArhivX509UserDetailsService
implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
#Autowired
private IUserProfileService iUserProfileService;
#Autowired
private ICheckCertificateService iCheckCertService;
#Override
public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken pToken) throws UsernameNotFoundException {
X509Certificate tCertificate = (X509Certificate) pToken.getCredentials();
String tSubjectDN = tCertificate.getSubjectDN().toString().toUpperCase();
ProfilKorisnika tProfilKorisnika = iUserProfileService.getUserProfile(tSubjectDN);
if (tProfilKorisnika == null) {
throw new UsernameNotFoundException("Pogreška kod prijave korisnika.");
}
return tProfilKorisnika;
}
}
X509ErrorCheckerFilter.java
public class X509ErrorCheckerFilter extends GenericFilterBean {
private static final String DEFAULT_REDIRECT_URL = "/error401";
private static final String[] UNAUTHENTICATED_URLS = { "/webjars/**", "/error", "/error401", "/login",
"/logout" };
#Override
public void doFilter(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain)
throws IOException, ServletException {
HttpServletRequest tHttpRequest = (HttpServletRequest) pRequest;
HttpServletResponse tHttpResponse = (HttpServletResponse) pResponse;
String tContextRoot = tHttpRequest.getSession().getServletContext().getContextPath();
String tUri = tHttpRequest.getRequestURI().replaceFirst(tContextRoot, "");
if (isUriSecured(tUri)) {
Authentication tAuthentication = SecurityContextHolder.getContext().getAuthentication();
AuthenticationException tException = (AuthenticationException) tHttpRequest
.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
if (tException != null || tAuthentication == null) {
RequestDispatcher tRd = tHttpRequest.getRequestDispatcher(DEFAULT_REDIRECT_URL);
tRd.forward(tHttpRequest, tHttpResponse);
return;
}
}
pChain.doFilter(pRequest, pResponse);
}
private boolean isUriSecured(String pRequestURI) {
boolean tResult = true;
for (String tUrl : UNAUTHENTICATED_URLS) {
if (pRequestURI.startsWith(tUrl)) {
tResult = false;
break;
}
}
return tResult;
}
}
If you need more details please ask.
Applying permitAll() to /error401 means that when the security filterchain is finished executing, the request will be processed normally, regardless of whether or not there is an Authentication in the current SecurityContext.
X509ErrorCheckerFilter is forwarding all unauthenticated requests to /error401 from within the filterchain, so permitAll() is never applied. Instead, the forwarded request goes through the filterchain again and fails authentication, causing the circular redirect.
To resolve this, you have several options. Here are a couple:
1. Disable Security for error endpoints
you can disable security for endpoints using web.ignoring() in your SecurityConfiguration class. This is the simplest option.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/error*");
}
2. Implement the ErrorController interface
The security filterchain will not be called for request mappings included in a controller that implements ErrorController.
See Spring's own BasicErrorController for reference(source).
This second option is preferable as it removes any requirement for a redirect in your filter. Instead, it lets Spring Security do the heavy lifting for routing requests through the auth process. As long as there's no authenticated Authentication in the session's SecurityContext when the security filterchain has finished processing, spring security will return a 401 and return the error page specified by your ErrorController.

Spring boot security - two distinct filters

I am trying to configure spring security with two distinct filters.
What i want is to have some URLs which will be processed by one filter, and some URLs which will be processed by other filter.
This is the setup i came up with :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled=true)
public class SecurityConfigurationContext {
#Order(1)
#Configuration
public static class ConfigurerAdapter1 extends WebSecurityConfigurerAdapter{
...
#Override
protected void configure(final HttpSecurity http) throws Exception {
// Handlers and entry points
http.exceptionHandling()
.authenticationEntryPoint(MyCustomAuthenticationEntryPoint);
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/filter1-urls/*").hasRole("USER");
http.addFilterBefore(myCustomFilter1, ChannelProcessingFilter.class);
http.csrf().disable();
http.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myCustomAuthenticationService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
#Order(2)
#Configuration
public static class ConfigurerAdapter2 extends WebSecurityConfigurerAdapter{
...
#Override
protected void configure(final HttpSecurity http) throws Exception {
// Handlers and entry points
http.exceptionHandling()
.authenticationEntryPoint(MyCustomAuthenticationEntryPoint);
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/filter2-urls/*").hasRole("SUPER_USER");
http.addFilterBefore(myCustomFilter2, ChannelProcessingFilter.class);
http.csrf().disable();
http.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myCustomAuthenticationService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
}
And filters look like this :
Filter1 :
#Component
#Order(1)
public final class MyCustomFilter1 implements Filter{
public MyCustomFilter1() {
super();
}
#Override
public final void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
// logic for processing filter1-urls
}
}
Filter2 :
#Component
#Order(2)
public final class MyCustomFilter2 implements Filter{
public MyCustomFilter2() {
super();
}
#Override
public final void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
// logic for processing filter2-urls
}
}
The problem is that both of these filters are invoked in a chain for every request. Any request i make, it first passes through one filter and then the other, rather then just through one.
How do i fix this ?
Thanks in advance.
You should have the only one ConfigurerAdapter. You can combine it into one configuration:
http.exceptionHandling()
.authenticationEntryPoint(MyCustomAuthenticationEntryPoint);
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/filter1-urls/*").hasRole("USER")
.antMatchers(HttpMethod.GET, "/filter2-urls/*").hasRole("SUPER_USER");
http.addFilterBefore(myCustomFilter, ChannelProcessingFilter.class);
Both of filters will be invoked because you set such behavior. You should left the only one filter to achieve the result you want.
#Component
public final class MyCustomFilter implements Filter {
public MyCustomFilter() {
super();
}
#Override
public final void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
// logic for processing filter2-urls and filter1-urls
}
}
Anyway your filters will be invoked in a chain. If you want to split your filters you should add if statement in your filters to filter only needed url (you can get request url from ServletRequest
#Component
public final class MyCustomFilter2 implements Filter {
public MyCustomFilter2() {
super();
}
#Override
public final void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
if (/*is filter2 urls*/) {
// logic for processing filter2-urls
}
}
}

Spring security - Custom ExceptionTranslationFilter

This question is actually related to this issue problem.
Based on the suggestion from #harsh-poddar, I added the filter accordingly.
However, after adding that it seems like I can't login even with valid credential.
Following is the related code:
SecurityConfig
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// #Bean
// public CustomAuthenticationEntryPoint customAuthenticationEntryPoint() {
// return new CustomAuthenticationEntryPoint();
// }
#Bean
public CustomExceptionTranslationFilter customExceptionTranslationFilter() {
return new CustomExceptionTranslationFilter(new CustomAuthenticationEntryPoint());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//Note : Able to login without this filter, but after adding this, valid credential also fails
.addFilterAfter(customExceptionTranslationFilter(), ExceptionTranslationFilter.class)
// .exceptionHandling()
// .authenticationEntryPoint(new customAuthenticationEntryPoint())
// .and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic()
.and()
.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new CustomAuthenticationProvider());
}
}
CustomAuthenticationProvider
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
public CustomAuthenticationProvider() {
super();
}
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
final String name = authentication.getName();
final String password = authentication.getCredentials().toString();
if (name.equals("admin") && password.equals("password")) {
final List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
final UserDetails principal = new User(name, password, grantedAuths);
final Authentication auth = new UsernamePasswordAuthenticationToken(principal, password, grantedAuths);
return auth;
} else {
throw new BadCredentialsException("NOT_AUTHORIZED");
}
}
#Override
public boolean supports(final Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
CustomExceptionTranslationFilter
#Component
public class CustomExceptionTranslationFilter extends ExceptionTranslationFilter {
public CustomExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
super(authenticationEntryPoint);
}
}
CustomAuthenticationEntryPoint
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized.");
}
}
p/s : sorry for the basic question, I'm really new in spring & spring security.
The intended design for AuthenticationEntryPoint is to start/initiate an authentication. However, your implementation CustomAuthenticationEntryPoint does not do this. Instead, it simply sends back an unauthorized response. Please see javadoc for AuthenticationEntryPoint for more details on implementation specifics.
Based on your configuration you are using HTTP Basic for authentication:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
This specific configuration will automatically configure BasicAuthenticationEntryPoint which is an implementation of AuthenticationEntryPoint. The BasicAuthenticationEntryPoint will challenge the user with a http response header of WWW-Authenticate: Basic realm="User Realm" to authenticate, as per server protocol.
However, the fact that you are configuring your own CustomAuthenticationEntryPoint it will ultimately override the BasicAuthenticationEntryPoint which is not what you want to do.
The other post recommended this configuration which again is not what you want to do.
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint());
}
If your main goal is to provide a custom response to the user when authentication fails than I would propose a form login configuration with a configured AuthenticationFailureHandler. Here is the configuration:
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().failureHandler(new DefaultAuthenticationFailureHandler())
.and()
.csrf().disable(); // NOTE: I would recommend enabling CSRF
Your implementation of DefaultAuthenticationFailureHandler would be:
public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// Set status only OR do whatever you want to the response
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
The AuthenticationFailureHandler is specifically designed to handle a failed authentication attempt.

Categories

Resources