Spring Security: oauth2Login redirect only on certain paths - spring-boot

I have Spring Security configured to authenticate my website, such that all paths are automatically redirected to the OAuth2 authorization URL (using .oauth2Login()). However, I want unauthenticated requests to the API (i.e. /api/**) to return 401 Unauthorized instead of being redirected. I can't figure out how to do this. Any help would be much appreciated.
Here is my current configuration:
http
.authorizeRequests()
.antMatchers("/api/auth/oauth2/callback").permitAll()
.anyRequest().authenticated()
.oauth2Login()
.authorizationEndpoint()
.baseUri(this.oauth2AuthorizationRedirectBaseUri);
http.logout()
.logoutUrl("/auth/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");

You can define a custom authentication entry point for /API/** and add t to your configuration:
#Component
public class CustomAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(
HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("developers");
super.afterPropertiesSet();
}
}
in your Http security configs add:
http.
...
.exceptionHandling()
.defaultAuthenticationEntryPointFor(
new CustomAuthenticationEntryPoint(),
new AntPathRequestMatcher("/api/**"))

Related

AntMatcher seems not to match requested path (Spring Security)

We are trying to use Spring Security to secure our webservice. We are using a customer filter (a class that extends GenericFilterBean) to read a JSON Web Token from the HTTP header. If a token exists, it is stored as PreAuthenticatedAuthenticationToken in the Security Context. Protected resources should use our customer Authentication Provider to verify if the token is valid and to load user info (which includes roles).
The problem is that I don't get it to configure an AntMatcher for a specific resource.
If I use antMatchers("/**").hasRole("USER") the resource is protected, but we don't want all resources to match, so I tried an AntMachter for one resource like this:
.antMatchers(HttpMethod.GET,"/rest/security/v1/currentuser").hasRole("USER")
But this matcher seems not to match the requested resource and so the Authentication Provider is not called. But I don't know why. I tried several combinations of ant pattern but nothing worked yet.
I set a breakpoint in the custom filter to check the current path and when I call servletRequest.getPathInfo() I get excatly what I thought it should be our ant pattern: /rest/security/v1/currentuser
Spring Security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.csrf().disable()
.authorizeRequests()
//.antMatchers("/**").hasRole("USER")
.antMatchers(HttpMethod.GET, "/rest/security/v1/currentuser").hasRole("USER")
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.addFilterBefore(new JwtAuthenticationFilter(), BasicAuthenticationFilter.class);
}
Custom filter:
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
Optional<String> token = Optional.ofNullable(httpRequest.getHeader(HTTP_AUTHENTICATION_HEADER));
if (token.isPresent()) {
PreAuthenticatedAuthenticationToken requestAuthentication = new PreAuthenticatedAuthenticationToken(token, null);
SecurityContextHolder.getContext().setAuthentication(requestAuthentication);
}
filterChain.doFilter(servletRequest, servletResponse);
}
Authentication provider:
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken authenticationToken = null;
try {
// Token Validation, Loading User Info
} catch (Exception e) {
LOG.error("Failed to authenticate", e);
}
return authenticationToken;
}

Spring oauth2 SimpleUrlAuthenticationFailureHandler two redirects on failure

For some reason spring with oauth2 and custom SimpleUrlAuthenticationFailureHandler redirects twice on login failure.
It calls http://localhost:8081/login?error as expected but with a location header http://localhost:8081/login therefore I see an extra redirect.
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/" + URLConstants.LOGIN)
.loginProcessingUrl("/" + URLConstants.LOGIN)
//.failureUrl("/login?error")
.failureHandler(authFailureHandler)
.permitAll();
And SimpleUrlAuthenticationFailureHandler
#Component
public class AuthFailureHandler extends
SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// some code
saveException(request, exception);
getRedirectStrategy().sendRedirect(request, response, "/login?error");
Above code onAuthenticationFailure pretty much calls same methods as original code in onAuthenticationFailure. I've also tried
super.onAuthenticationFailure(request, response, exception);
But I do get same results (an extra redirect)
If I remove .failureHandler(authFailureHandler) then the code works as expected. Any ideas?
See images attached
Extra redirect - with SimpleUrlAuthenticationFailureHandler
As expected - without SimpleUrlAuthenticationFailureHandler

How to capture Spring Security authentication token and save it in a database

We are using a two-pronged approach to user authentication where we are using an in-memory authentication token store in clustered environment and saving the token in a shared database as well. Now I am able to get token using the in-memory store and authenticate users. Everything is working as expected.
I am looking for the following things :
Capture the token and save it in the database? How to achieve this with Spring Security (this should happen after successful authentication)?
If server restarts then I should still be able to validate the token from database.(if user name password is correct.)
#Component
public class CustomAuthSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
System.out.println(request);
System.out.println(response);
}
}
Following are my HTTP settings:
#Override
public void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.logout()
.logoutUrl("/oauth/logout")
.logoutSuccessHandler(customLogoutSuccessHandler)
.and()
.csrf()
.requireCsrfProtectionMatcher(
new AntPathRequestMatcher("/oauth/authorize"))
.disable().headers().frameOptions().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests().antMatchers("/hello/")
.permitAll().antMatchers("/secure/**").authenticated();
}
PS : It looks like adding an interceptor can help, however I am not sure how do I to get token from response. Any suggestions?

Send a http 401 error code instead of default login page, spring security

I am using a basic authorization with the Spring Security. I configure the latter via Java config.
I would like to send to a client the HTTP 401 error code with the message "Invalid login and password" if they are invalid. However, currently Spring Security simply displays me a default pop-up window.
Here is my Spring security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated().and()
.httpBasic()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/pages/index.html");
http.exceptionHandling().authenticationEntryPoint(new AjaxAuthorizationPoint());
}
As far as I understood, I have to add the custom authentificationEntryPoint to handle the case of the invalid credentials i.e. I have to send there a 401 error code with the error message
Here is the code for it. For the sake of simplicity, the body of the method is rather simple.
public class AjaxAuthorizationPoint extends BasicAuthenticationEntryPoint{
#Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException) throws IOException {
System.out.println("blah");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
However, the method public void commence() doesn't fire up, when I enter an invalid login and password and Spring simply sends me the default login pop-up window.
How can I redefine the default strategy? How can I configure the Spring security to send a HTTP 401 error code instead of displaying a default login page?
I think, I have found a solution for my problem. Here the code that does exactly what I need.
Spring security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().authenticationEntryPoint(new AjaxAuthorizationPoint("/ajax_login"));
http.authorizeRequests().anyRequest().authenticated().and().httpBasic()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/pages/index.html");
}
And custom ajax authorization point:
public class AjaxAuthorizationPoint extends LoginUrlAuthenticationEntryPoint {
public AjaxAuthorizationPoint(String loginFormUrl) {
super(loginFormUrl);
}
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.setStatus(403);
response.getWriter().print("Invalid login/password");
response.getWriter().flush();
}
}
Would appreciate any code review.

Spring security redirect to login and restore form data previously entered

Overview
I have Spring Web-Application secured with Spring Security
On the site there is a form to input some data, this form is public, but the data will only be processed for authenticated users
If the user press the submit button and is not yet logged in, he will be delegated to the login page. Was the login successfull the user will be redirected to a site where the result of the data processing is visible
Problem
In standard configuration all the data which has been setup by the user are lost after the login process. As I understand it its because a new HttpRequest is created for the redirect after the login.
Solution
I have to write a custom LoginUrlAuthenticationEntryPoint which stores the form data in the session
I have to write a custom SavedRequestAwareAuthenticationSuccessHandler which reads the date from the session an add them as parameters to the url
WebApp Configuration
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private SecurityProperties security;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("admin")
.roles("ADMIN", "USER")
.and()
.withUser("user")
.password("user")
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/inputForm")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.successHandler(new SavedRequestAwareAuthenticationSuccessHandlerCustom())
.and()
.csrf()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.and()
.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPointCustom("/login"));
}
}
Custom SuccessHandler
public class SavedRequestAwareAuthenticationSuccessHandlerCustom extends SavedRequestAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
String text = (String) request.getSession().getAttribute("text");
if (text != null) {
request.getSession().removeAttribute("text");
setDefaultTargetUrl("/user/dashboard/?text=" + text);
}
super.onAuthenticationSuccess(request, response, authentication);
}
}
Custom EntryPoint
public class LoginUrlAuthenticationEntryPointCustom extends LoginUrlAuthenticationEntryPoint {
public LoginUrlAuthenticationEntryPointCustom(String loginFormUrl) {
super(loginFormUrl);
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException,
ServletException {
String text = request.getParameter("text");
request.getSession().setAttribute("text", text);
super.commence(request, response, authException);
}
}
What would you say, is this a valid way to restore the form data, are the better/other solutions, maybe a standard way in spring?
Update
It seem's that something is still wrong with my configuration, cause as seen in the debug message, the request ist not saved by the "HttpSessionRequestCache". If I get this working I don't have to work around with custom implementations.
o.s.s.w.util.matcher.AndRequestMatcher : Trying to match using Ant [pattern='/**', GET]
o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'POST /user/dashboard' doesn't match 'GET /**
o.s.s.w.util.matcher.AndRequestMatcher : Did not match
o.s.s.w.s.HttpSessionRequestCache : Request not saved as configured RequestMatcher did not match
kindly make sure that the form method is post
like this
<form th:action="#{/login}" method="post">
<!-- form input -- >
</form>

Resources