I'm trying to make a basic authentication service, for some business logic i need to acceept all basic auth credentials and make them hit another service (and there it will be fail if the credentials are wrong).
So I'm trying to throw an exception when the basic auth is not present, or are empty credentials.
This is my SecurityConfigurer:
#Configuration
#EnableWebSecurity
public class SecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
STGAuthenticationProvider authProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().httpBasic();
}
}
And this is my CustomAuthProvider:
#Component
public class STGAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
if(!StringUtils.isBlank(username) && !StringUtils.isBlank(password)) {
return new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
} else {
throw new STGNoCredentialsException(Constants.Error.NO_CREDENTIALS);
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Actually my app gives me "401 Unauthorized" if i send a request with no auth (I would really like to get my custom Exception you can see at my CustomAuthProvider).
And when i send just 1 credential (username or password), or no one, my service answer me with empty body at POSTMAN. Can you guys help me?
From what I understand, your issue is similar to one I had a few days ago: I needed to return a 401 instead of a 403 whenever an endpoint was called with no authorisation or with auth token expired.
With respect to your code, I would add .exceptionHandling().authenticationEntryPoint(...) to your WebSecurityConfigurerAdapter as follows
#Configuration
#EnableWebSecurity
public class SecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
/* other stuff */
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().httpBasic()
.exceptionHandling().authenticationEntryPoint(/*custom exception*/);
}
}
and then, instead of /*custom exception*/ add something as new MyAuthException(), where MyAuthException looks like the following:
#Component
public class MyAuthException implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) /*throws ...*/ {
response.setStatus(/* your status */);
response.getWriter().write(/*the body of your answer*/);
/* whatever else you want to add to your response */
/* or you could throw an exception, I guess*/
}
}
(I don't remember and right now I can't check whether this class needs to be marked as #Component, I think not).
Related
I started building my project based on a custom error response in order to send the json body with only fields that i need. For this reason i have a
#RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler
that catches all exceptions and returns ResponseEntity having ther custom error body.
I have a postgres database where i save users. Currently i have /signin, /signup and /profile endpoints. I wanted to use jwt authentication. I used this github repo and i can get the token when i send user's credentials on the /signin endpoint.
Here's the problem. Read this part of JwtTokenFilter.java
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = jwtTokenProvider.resolveToken(httpServletRequest);
try {
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (CustomException ex) {
//this is very important, since it guarantees the user is not authenticated at all
SecurityContextHolder.clearContext();
httpServletResponse.sendError(ex.getHttpStatus().value(), ex.getMessage());
return;
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
Suppose i want to signup a new user. Then my request's header won't have a token (token is null) and the program will execute filterChain.doFilter(httpServletRequest, httpServletResponse);. That work's fine, the user gets signed up and i get the 201 that my controller returns upon successful registration.
However, suppose i make a GET request at /profile endpoint again having no token. This too will execute filterChain.doFilter. However this time spring will respond with a NON-custom 403 error response.
I can't find a way to catch the exception on my RestControllerHandler because spring handles it for me.
Also, when i throw an exception inside doFilterInternal, the exception again won't be handled by my GlobalHandler, spring handles it.
Will have to add custom AuthenticationFailureHandler
public class CustomAuthenticationFailureHandler
implements AuthenticationFailureHandler {
private ObjectMapper objectMapper = new ObjectMapper();
#Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
Map<String, Object> data = new HashMap<>();
data.put(
"timestamp",
Calendar.getInstance().getTime());
data.put(
"exception",
exception.getMessage());
response.getOutputStream()
.println(objectMapper.writeValueAsString(data));
}
}
and then configure this here
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication()
.withUser("user1").password(passwordEncoder.encode("user1Pass")).roles("USER");
}
#Override
protected void configure(HttpSecurity http)
throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.failureHandler(authenticationFailureHandler());
}
#Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new CustomAuthenticationFailureHandler();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
I have a working Spring Boot with Spring Security. Everything mostly works. What I'm not understanding is why the RestController never fires following the filter authorizing the request.
In other words, I have a rest controller set up to accept POST requests from /foo/bar and I have an AuthenticationFilter set up to first verify the users credentials before performing what the user requested.
Given that my RestController never fires, I've had to implement my code in the Success Handler, but my code belongs in the Controller instead.
I've attempted to debug this by stepping through Spring Security code, but nothing appears to suggest it would skip my RestController.
#RestController
#RequestMapping("foo")
public class FooController {
#PostMapping("bar") // this never executes
public ResponseEntity<FooResponse> foobar(#RequestBody Credentials creds) {
return new ResponseEntity<>(new FooResponse("baz"), HttpStatus.OK);
}
}
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AuthenticationFilter authenticationTokenFilter() throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandlerImpl());
return filter;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(accountService)
.passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/foo/bar").permitAll()
.and()
.addFilter(new AuthorizationFilter(properties, authenticationManager(), tokenService)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public AuthenticationFilter() {
super("/foo/bar");
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
Credentials creds = new ObjectMapper().readValue(request.getInputStream(), Credentials.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>())
);
}
}
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// do nothing and prevent redirect to /login, /logout, etc
}
}
You are using http.cors(). Have you configured it elsewhere? If not, it probably considers you are not authenticated. Check this link out: https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/cors.html
I would suggest you to remove it if you are not going to use it
Also you have here an example of an easy security configuration: https://www.baeldung.com/spring-security-login
From my point of view, removing cors() should fix your problem (or configuring it the right way :)
I've made a spring page using Spring Security. When I try to access any url inside this page, if the session is not set, it will redirect you to the login page: /login. This is fine, but now I've made a simple http rest api inside this web. What I want if I try to access any url inside /api/** just drop the 401, instead of sending a HTTP redirect to login.
I have made a Filter with preHandle:
public class BusinessKeyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()
&&
// when Anonymous Authentication is enabled
!(auth instanceof AnonymousAuthenticationToken)) {
// other stuf ....
}
} else {
if (request.getRequestURI().startsWith("/api")) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return false;
}
}
return true;
}
}
But in this case request URI is already /login
My configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and().sessionManagement()
.invalidSessionUrl("/login?invalid")
.and().csrf().disable().formLogin()
.loginPage("/login")
.failureHandler(customAuthenticationFailureHandler)
.defaultSuccessUrl("/loggedIn")
.usernameParameter("email")
.passwordParameter("password")
.successHandler(authenticationSuccessHandler)
.and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/").and().exceptionHandling()
.accessDeniedPage("/access-denied")
;
}
I would recommend not mixing your previous security configuration with your new REST api config. You could do the following:
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Configuration
#Order(1)
public static class WebConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatchers("/web")
...
/* Your previous config would go here */
}
}
#Configuration
#Order(2)
public static class ApiConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatchers("/web")
... /* Your /api configuration goes here */
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
}
#Bean
AuthenticationEntryPoint customAuthenticationEntryPoint() {
return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}
}
}
This way, you could configure your rest api separately. Now you can have different a authentication entry point for your rest api. The fact is that you're most likely going to want to also provide a custom failure handler and success handler, which you can now do easily, and which will remain separate from the rest of your web application.
I need to implement authorization with a specific header (say "sessionId") and secure all uri's except one.
I extended OncePerRequestFilter and implemented custom AuthenticationProvider to check if sessionId is valid (as well as custom Token class etc).
How it works now: for any uri it immediately jumps to AuthSessionAuthenticationProvider's authenticate method right after AuthSessionFilter is applied and returns 403 if header sessionId isn't specified. But I want some uri's to allow access without that header.
It all together:
config:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(permittedUris).permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().accessDeniedHandler(new AuthSessionAccessDeniedHandler())
.and().addFilterBefore(new AuthSessionFilter(), BasicAuthenticationFilter.class);
}
Filter:
public class AuthSessionFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Authentication auth = new AuthSessionToken(request.getHeader("sessionId"));
SecurityContextHolder.getContext().setAuthentication(auth);
filterChain.doFilter(request, response);
}
}
Provider:
public class AuthSessionAuthenticationProvider implements AuthenticationProvider {
//...
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
AuthSessionToken token = (AuthSessionToken) authentication;
if (token.getSessionId() == null) {
throw new AccessDeniedException("Missing header sessionId");
}
AuthSessionAuthorities user = authSessionService.getUserAuthoritiesToken(token.getSessionId());
if (user == null) {
throw new AccessDeniedException("Session ID invalid: " + token.getSessionId());
}
token.setAuthenticatedUser(user);
return token;
}
//...
}
I found more elegant solution that was developed exactly for that purpose.
It's a RequestHeaderAuthenticationFilter. And then antMatchers works as expected. The initial configuration looks like this:
#Bean
#SneakyThrows
public RequestHeaderAuthenticationFilter preAuthenticationFilter() {
RequestHeaderAuthenticationFilter preAuthenticationFilter = new RequestHeaderAuthenticationFilter();
preAuthenticationFilter.setPrincipalRequestHeader(SESSION_ID);
preAuthenticationFilter.setCredentialsRequestHeader(SESSION_ID);
preAuthenticationFilter.setExceptionIfHeaderMissing(false);
preAuthenticationFilter.setContinueFilterChainOnUnsuccessfulAuthentication(true);
preAuthenticationFilter.setAuthenticationManager(authenticationManager());
return preAuthenticationFilter;
}
I am trying to toggle/bypass/disable Spring Security (Authentication and Authorization) for all the requests having particular Request Header.
For example, if a request url is hit with that Request Header, Spring Security should be bypassed, if not it should not be bypassed.
For this, I am using following requestMatchers Spring Security config:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.GET)
.antMatchers(HttpMethod.OPTIONS)
.requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE"));
}
My remaining Security Config is :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity (prePostEnabled = true)
#ConditionalOnProperty (name = "security.enabled", havingValue = "true", matchIfMissing = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private SecurityProps securityProps;
#Autowired
private MyUserDetailsService myUserDetailsService;
#Autowired
private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
#Autowired
private MyCORSFilter myCORSFilter;
public SecurityConfig() {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.addFilterBefore(myCORSFilter, SessionManagementFilter.class)
.addFilterBefore(requestHeaderFilter(), RequestHeaderAuthenticationFilter.class)
.authenticationProvider(preauthAuthProvider())
.authorizeRequests()
.antMatchers(HttpMethod.GET, securityProps.getNoAuthGetPattern()).permitAll()
.antMatchers(HttpMethod.OPTIONS, securityProps.getNoAuthOptionsPattern()).permitAll()
.requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE")).permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(myAuthenticationEntryPoint);
}
#Autowired
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preauthAuthProvider());
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.GET)
.antMatchers(HttpMethod.OPTIONS)
.requestMatchers(new RequestHeaderRequestMatcher("TEST-HEADER","TEST-VALUE"));
}
public RequestHeaderAuthenticationFilter requestHeaderFilter() throws Exception {
RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();
requestHeaderAuthenticationFilter.setPrincipalRequestHeader(MySecurityConstants.LOGIN_HEADER);
requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager());
requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(false);
requestHeaderAuthenticationFilter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (exception instanceof MySecurityException) {
myAuthenticationEntryPoint.commenceMySecurityException(request, response, (MySecurityException) exception);
} else if (exception instanceof UsernameNotFoundException) {
myAuthenticationEntryPoint.commenceUsernameNotFoundException(request, response,
(UsernameNotFoundException) exception);
} else if (exception instanceof PreAuthenticatedCredentialsNotFoundException) {
myAuthenticationEntryPoint.commence(request, response, exception);
}
}
});
return requestHeaderAuthenticationFilter;
}
#Bean
public PreAuthenticatedAuthenticationProvider preauthAuthProvider() throws Exception {
PreAuthenticatedAuthenticationProvider authProvider = new PreAuthenticatedAuthenticationProvider();
authProvider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
return authProvider;
}
#Bean
public UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper()
throws Exception {
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper =
new UserDetailsByNameServiceWrapper<>();
wrapper.setUserDetailsService(ivyUserDetailsService);
return wrapper;
}
}
With the above settings, I am unable to disable/bypass Spring Security and I am getting the AuthenticationCredentialsNotFoundException exception:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
Can anyone help me by identifying what am I doing wrong? Is my approach correct or I need to do something else to achieve this?
EDIT :
I am getting this exception in org.springframework.security.access.intercept.AbstractSecurityInterceptor class in beforeInvocation() method where it tries to get the authentication object from SecurityContextHolder. AbstractSecurityInterceptor is invoked by its subclass MethodSecurityInterceptor which is invoked from my Spring Controller which is annotated with #PreAuthorize.
I think your bypass is working fine. Its skipping the check.
The security's authorization check part gets the authenticated object from SecurityContext, which will be set when a request gets through the spring security filter.
So when you skip security filter SecurityContext is not set yet thus the error
You can do something like this to set it manually for your Custom Header Case
try {
SecurityContext ctx = SecurityContextHolder.createEmptyContext();
SecurityContextHolder.setContext(ctx);
ctx.setAuthentication(event.getAuthentication());
} finally {
SecurityContextHolder.clearContext();
}
Edit 1:
Answering all the queries.
But if thats the case, then I guess all GET call should also have
failed, but my GET calls are working fine.
Since you have added this line All your GET calls are skipped from security check.
.antMatchers(HttpMethod.GET, securityProps.getNoAuthGetPattern()).permitAll()
where can I add the code you have mentioned? Any particular filter or
somewhere else ?
I have done something like this in a Filter.
Refer Here
Look at TokenAuthenticationFilter Class in Answer. Where am manually setting.
Note: Its JWT implementation but good to refer
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (tokenHelper.validateToken(authToken, userDetails)) {
// create authentication
TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
authentication.setToken(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
What is event in your answer?
I just got that case from Some Answer, cant find its link now. But you can setAuthentication like this or like above
Authentication authentication = new PreAuthenticatedAuthenticationToken("system", null);
authentication.setAuthenticated(true);
context.setAuthentication(authentication);