spring security permit all considering basic auth passed and validating it - spring

I am doing POC on spring security recently and saw some strange behavior. API endpoint configured as permit all is authenticating the request if client calls this endpoint using basic auth.
Sample code:
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/health").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic().authenticationEntryPoint(entryPoint)
.and()
.csrf().disable()
.cors().and()
.formLogin().disable();
return http.build();
Calling using curl as below is giving error:
curl -X GET -u "user:password" -H Content-Type:application/json http://localhost:20000/health
Error:
{"responseCode":401,"responseStatus":"Unauthorized","errorMessage":["Not authorized to access"],"responseMsg":null}
And if called without and user and password in above curl command then it works and return the desired response.
Can anyone please suggest if this is correct behavior and how to override it.
Auth entrypoint implementation:
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint{
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
ResponseData responseData = new ResponseData();
responseData.setResponseStatus(HttpStatus.UNAUTHORIZED.getReasonPhrase());
responseData.setErrorMessage(new ArrayList<>(Arrays.asList("Not authorized to access")));
responseData.setResponseCode(HttpServletResponse.SC_UNAUTHORIZED);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
String jsonResponse = mapper.writeValueAsString(responseData);
PrintWriter printWriter = response.getWriter();
printWriter.append(jsonResponse);
printWriter.flush();
printWriter.close();
}
}

permitAll does not mean that there is no authentication, it just states that there will be no authorization checks for that endpoint. Therefore, if you have httpBasic enabled and send the credentials in the request, the credentials will be checked by the BasicAuthenticationFilter against your UserDetailsService.
I assume that your credentials are wrong and that's why you are receiving a 401.

Related

Spring Security OAuth - how to disable login page?

I want to secure my application with Spring Security, using OAuth 2. However, I don't want the server to redirect incoming unauthorized requests, but instead to respond with HTTP 401. Is it possible?
Example: this code redirects requests to a default login page.
application.properties
spring.security.oauth2.client.registration.google.client-id=...
spring.security.oauth2.client.registration.google.client-secret=...
AuthConfig.java
#Configuration
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secured/**").authenticated()
.anyRequest().permitAll()
.and()
.oauth2Login();
// https://stackoverflow.com/questions/31714585/spring-security-disable-login-page-redirect
// deos not work
// .and()
// .formLogin().successHandler((request, response, authentication) -> {});
}
}
You need to create new authentication entry point and set it in configuration.
#Configuration
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new AuthenticationEntryPoint())
.and()
.authorizeRequests()
.antMatchers("/secured/**").authenticated()
.anyRequest().permitAll()
.and()
.oauth2Login();
}
}
public class AuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
public AuthenticationEntryPoint() {
super("");
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(401, "Unauthorized");
}
}
You need to set oauth2Login.loginPage in your HttpSecurity config and create a controller mapping to return whatever you want. Here's a simple example.
So in your security config
http
.authorizeRequests()
.antMatchers("/noauth").permitAll()
.oauth2Login()
.loginPage("/noauth")
In a controller
#GetMapping("/noauth")
public ResponseEntity<?> noAuth() {
Map<String, String> body = new HashMap<>();
body.put("message", "unauthorized");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(body);
}
You can pass a map or pojo to the body method.
I would like to expand on Petr's answer by explaining that apparently for the time being first of all, the default login page is shown when there are more than one OAuth2 configured providers. I would expect that Spring Boot would have a smart trick to bypass this page easily and choose the right provider automatically, basing e.g. on the existence of the provider's client ID in the original request. I found out the hard way that this is not the case. So the way to do this is.. this not very apparent trick of providing a custom handler for failures - that will REDIRECT the user to the correct OAuth2 endpoint for each provider, based on the original HTTP request URL. I tried this and it works and I spent a whole day trying all manners of other solutions - my original scenario was to pass additional parameters to OAuth2 scheme in order to be able to get them back on successful authentication - they used to do this appending Base64 encoded information to the "state" URL request parameter, but Spring Security does not allow this at the moment. So the only alternative was to call a Spring Security-protected URL with those parameters already there, so when the successful authentication happens, this URL is accessed again automatically with those parameters intact.
Related: Multiple Login endpoints Spring Security OAuth2

Use AbstractAuthenticationProcessingFilter for multiple URLs

I have the below endpoint patterns in my application
/token -- accessible to all
/rest/securedone/** -- requires authentication
/rest/securedtwo/** -- requires authentication
/rest/unsecured/** -- does not require authentication
As of now, I am able to access the /token endpoint.
But /rest/securedone/** and /rest/unsecured/** return 401 when a token(JWT) is not sent. It is my intention to secure /rest/securedone/** and that is fine /rest/unsecured/** should be accessible.
My httpSecurity config is as below:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/token").permitAll()
.antMatchers("/rest/secured/**").authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.headers().cacheControl();
}
and my AbstractAuthenticationProcessingFilter extended class is as below:
public class MyAuthenticationTokenFilter extends AbstractAuthenticationProcessingFilter {
private static Logger log = LoggerFactory.getLogger(MyAuthenticationTokenFilter.class);
public MyAuthenticationTokenFilter() { super("/rest/**"); }
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, ServletException {
//authentication handling code
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
}
Can someone please help my figure out the below:
When is the MyAuthenticationTokenFilter used? For which URL will it be invoked? How come, /rest/unsecured/** is also expecting authentication? It happens even if i explicitly say .antMatchers("/rest/secured/**").permitAll().
Can I specify multiple url patterns in my super(defaultFilterProcessingUrl) call inside MyAuthenticationTokenFilter constructor? For example, if I have another url such as /api/secured/**, how can I get my MyAuthenticationTokenFilter to be invoked for /api/secured/** requests? I do not need different authentication handling so I want to re-use this filter.
When is the MyAuthenticationTokenFilter used ?
This filter is using for processing the request with client credential,it will filter the url when the
RequestMatcher match the request url, for example, in your configuration, it will handle the url that matches /rest/**, and try to convert the client credential to Authentication(e.g userInfo, role ...), it maybe throws an exception when the request with incorrect client credential.
It is different to authorizeRequests(xxx.authenticated() or xxx.permit()), authorizeRequests just check the whether the authentication has some special attributes (e.g role, scope).
By way of analogy, AbstractAuthenticationProcessingFilter just puts some cards(Authentication) into a box(SecurityContext) by different clients, authorizeRequests just check the box has the card that it needed, or it will deny the request. AbstractAuthenticationProcessingFilter
don't care who/how to use the cards, and authorizeRequests don't care where the cards come from.
Can I specify multiple url patterns in my super(defaultFilterProcessingUrl) call inside MyAuthenticationTokenFilter constructor ?
Yes, you can set the requiresAuthenticationRequestMatcher by setRequiresAuthenticationRequestMatcher, it will override the old requiresAuthenticationRequestMatcher, for example,
authenticationTokenFilter
.setRequiresAuthenticationRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/rest/secured/**")
, new AntPathRequestMatcher("/api/secured/**")
));

Spring Security unexpected behavior for REST endpoints authentication?

The scenario we are looking for is as follows:
client connects with REST to a REST login url
Spring microservice (using Spring Security) should return 200 OK and a login token
the client keeps the token
the client calls other REST endpoints using the same token.
However, I see that the client is getting 302 and a Location header, together with the token. So it does authenticate, but with un-desired HTTP response status code and header.
The Spring Security configuration looks like this:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable() // Refactor login form
// See https://jira.springsource.org/browse/SPR-11496
.headers()
.addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN))
.and()
.formLogin()
.loginPage("/signin")
.permitAll()
.and()
.logout()
.logoutUrl("/signout")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated();
...
}
I tried adding interceptors and filters but can't see where 302 and Location being set and added in Spring side.
However, the Location header does show in the response headers received at the client side (together with the rest of the Spring Security headers LINK):
Server=Apache-Coyote/1.1
X-Content-Type-Options=nosniff
X-XSS-Protection=1; mode=block
Cache-Control=no-cache, no-store, max-age=0, must-revalidate
Pragma=no-cache
Expires=0
X-Frame-Options=DENY, SAMEORIGIN
Set-Cookie=JSESSIONID=D1C1F1CE1FF4E1B3DDF6FA302D48A905; Path=/; HttpOnly
Location=http://ec2-35-166-130-246.us-west-2.compute.amazonaws.com:8108/ <---- ouch
Content-Length=0
Date=Thu, 22 Dec 2016 20:15:20 GMT
Any suggestion how to make it work as expected ("200 OK", no Location header and the token)?
NOTE: using Spring Boot, Spring Security, no UI, just client code calling REST endpoints.
If you need a rest api, you must not use http.formLogin(). It generates form based login as described here.
Instead you can have this configuration
httpSecurity
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.disable()
.addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class);
Create a class, AuthTokenFilter which extends Spring UsernamePasswordAuthenticationFilter and override doFilter method, which checks for an authentication token in every request and sets SecurityContextHolder accordingly.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, " + tokenHeader);
HttpServletRequest httpRequest = (HttpServletRequest) request;
String authToken = httpRequest.getHeader(tokenHeader);
String username = this.tokenUtils.getUsernameFromToken(authToken); // Create some token utility class to manage tokens
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(-------------);
// Create an authnetication as above and set SecurityContextHolder
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
Then create an AuthenticationController, mapped with /login url, which checks credentials, and returns token.
/*
* Perform the authentication. This will call Spring UserDetailsService's loadUserByUsername implicitly
* BadCredentialsException is thrown if username and password mismatch
*/
Authentication authentication = this.authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetailsImp userDetails = (UserDetailsImp) authentication.getPrincipal();
// Generate token using some Token Utils class methods, using this principal
To understand loadUserByUsername , UserDetailsService and UserDetails, please refer Spring security docs
}
For better understanding, please thoroughly read above link and subsequent chapters.
It's a 302 response telling the browser to redirect to your login page. What do you expect to happen? 302 response must have a Location header.
http.formLogin()
is designed for form-based login. So the 302 status and Location header in the response is expected if you attempt to access a protected resource without being authenticated.
Based on your requirement/scenario,
client connects with REST to a REST login url
have you considered using HTTP Basic for authentication?
http.httpBasic()
Using HTTP Basic, you can populate the Authorization header with the username/password and the BasicAuthenticationFilter will take care of authenticating the credentials and populating the SecurityContext accordingly.
I have a working example of this using Angular on the client-side and Spring Boot-Spring Security on back-end.
If you look at security-service.js, you will see a factory named securityService which provides a login() function. This function calls the /principal endpoint with the Authorization header populated with the username/password as per HTTP Basic format, for example:
Authorization : Basic base64Encoded(username:passsword)
The BasicAuthenticationFilter will process this request by extracting the credentials and ultimately authenticating the user and populating the SecurityContext with the authenticated principal. After authentication is successful, the request will proceed to the destined endpoint /principal which is mapped to SecurityController.currentPrincipal which simply returns a json representation of the authenticated principal.
For your remaining requirements:
Spring microservice (using Spring Security) should return 200 OK and a login token
the client keeps the token
the client calls other REST endpoints using the same token.
You can generate a security/login token and return that instead of the user info. However, I would highly recommend looking at Spring Security OAuth if you have a number of REST endpoints deployed across different Microservices that need to be protected via a security token. Building out your own STS (Security Token Service) can become very involved and complicated so not recommended.
You can implement your custom AuthenticationSuccessHandler and override method "onAuthenticationSuccess" to change the response status as per your need.
Example:
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
ObjectMapper mapper = new ObjectMapper();
Map<String, String> tokenMap = new HashMap<String, String>();
tokenMap.put("token", accessToken.getToken());
tokenMap.put("refreshToken", refreshToken.getToken());
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(response.getWriter(), tokenMap);
}
You need to override the default logout success handler to avoid redirect. In spring boot2 you can do as below:
....logout().logoutSuccessHandler((httpServletRequest,httpServletResponse,authentication)->{
//do nothing not to redirect
})
For more details: Please check this.
You can use headers().defaultsDisabled() and then chain that method to add the specific headers you want.

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?

Spring Security - Token based API auth & user/password authentication

I am trying to create a webapp that will primarily provide a REST API using Spring, and am trying to configure the security side.
I am trying to implement this kind of pattern: https://developers.google.com/accounts/docs/MobileApps (Google have totally changed that page, so no longer makes sense - see the page I was referring to here: http://web.archive.org/web/20130822184827/https://developers.google.com/accounts/docs/MobileApps)
Here is what I need to accompish:
Web app has simple sign-in/sign-up forms that work with normal spring user/password authentication (have done this type of thing before with dao/authenticationmanager/userdetailsservice etc)
REST api endpoints that are stateless sessions and every request authenticated based ona token provided with the request
(e.g. user logins/signs up using normal forms, webapp provides secure cookie with token that can then be used in following API requests)
I had a normal authentication setup as below:
#Override protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/mobile/app/sign-up").permitAll()
.antMatchers("/v1/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/")
.loginProcessingUrl("/loginprocess")
.failureUrl("/?loginFailure=true")
.permitAll();
}
I was thinking of adding a pre-auth filter, that checks for the token in the request and then sets the security context (would that mean that the normal following authentication would be skipped?), however, beyond the normal user/password I have not done too much with token based security, but based on some other examples I came up with the following:
Security Config:
#Override protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.addFilter(restAuthenticationFilter())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint()).and()
.antMatcher("/v1/**")
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/mobile/app/sign-up").permitAll()
.antMatchers("/v1/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/")
.loginProcessingUrl("/loginprocess")
.failureUrl("/?loginFailure=true")
.permitAll();
}
My custom rest filter:
public class RestAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public RestAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
private final String HEADER_SECURITY_TOKEN = "X-Token";
private String token = "";
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
this.token = request.getHeader(HEADER_SECURITY_TOKEN);
//If we have already applied this filter - not sure how that would happen? - then just continue chain
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
//Now mark request as completing this filter
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
//Attempt to authenticate
Authentication authResult;
authResult = attemptAuthentication(request, response);
if (authResult == null) {
unsuccessfulAuthentication(request, response, new LockedException("Forbidden"));
} else {
successfulAuthentication(request, response, chain, authResult);
}
}
/**
* Attempt to authenticate request - basically just pass over to another method to authenticate request headers
*/
#Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
AbstractAuthenticationToken userAuthenticationToken = authUserByToken();
if(userAuthenticationToken == null) throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
return userAuthenticationToken;
}
/**
* authenticate the user based on token, mobile app secret & user agent
* #return
*/
private AbstractAuthenticationToken authUserByToken() {
AbstractAuthenticationToken authToken = null;
try {
// TODO - just return null - always fail auth just to test spring setup ok
return null;
} catch (Exception e) {
logger.error("Authenticate user by token error: ", e);
}
return authToken;
}
The above actually results in an error on app startup saying: authenticationManager must be specified
Can anyone tell me how best to do this - is a pre_auth filter the best way to do this?
EDIT
I wrote up what I found and how I did it with Spring-security (including the code) implementing a standard token implementation (not OAuth)
Overview of the problem and approach/solution
Implementing the solution with Spring-security
Hope it helps some others..
I believe the error that you mention is just because the AbstractAuthenticationProcessingFilter base class that you are using requires an AuthenticationManager. If you aren't going to use it you can set it to a no-op, or just implement Filter directly. If your Filter can authenticate the request and sets up the SecurityContext then usually the downstream processing will be skipped (it depends on the implementation of the downstream filters, but I don't see anything weird in your app, so they probably all behave that way).
If I were you I might consider putting the API endpoints in a completely separate filter chain (another WebSecurityConfigurerAdapter bean). But that only makes things easier to read, not necessarily crucial.
You might find (as suggested in comments) that you end up reinventing the wheel, but no harm in trying, and you will probably learn more about Spring and Security in the process.
ADDITION: the github approach is quite interesting: users just use the token as a password in basic auth, and the server doesn't need a custom filter (BasicAuthenticationFilter is fine).

Resources