Authorization Error handling when Authorization service down in Spring boot security doesn't work - spring

In my spring boot web app, I hit a third party service for Authorization and my application is just a content provider. Parent application uses site minder for authentication. My application gets user Id in header and makes call to third party api to set UserDetails with authorities.
My requirement is to handle scenario when third party service for authorization is down. Currently in this case I set UserDetails with no roles and since every endpoint is bound by authorization so I get 403 if third party service for authorization is down.
But I want to display different message if user lacks authorization and if authorization service is down.
If I handle authorization service down by throwing custom exception from UserDetailsServiceImpl -> loadUserByUserName() then RequestHeaderAuthenticationFilter encounters this exception and request gets filtered out. Any idea how to get this done ?
SecurityConfiguration
public class WebSecurityCustomConfig extends WebSecurityConfigAdapter {
private UserDetailsService userDetails;
protected void configure(HttpSecurity http) {
http.csrf().disable().authorizeRequests().antMatchers("/*).permitAll()
.anyRequests()
.hasAnyAuthority("MODULEX","MODULEY");
http.addFilterBefore(requestHeaderAuthFilter(),
BasicAuthenticationFilter.class);
http.exceptionHandling().authenticationEntryPoint(customEntryPoint());
}
protect void configure(AuthenticationManagerBuilder builder) {
PreAuthenticaticatedAuthenticationProvider auth = new
PreAuthenticaticatedAuthenticationProvider ();
auth.setPreAuthenticatedUserDetailsService(new
UserDetailsByNameServiceWrapper<>(userDetails));
}
}
Custom UserDetailsService
public class CustomUserDetailsService implements UserDetailsService {
private final AuthorizationService authService;
#Inject
public CustoUserDetailsService(AuthorizationService authService) {
this.authService = authService;
}
public UserDetails loadUserByUsername(String username) {
return new User(username, "",
authService.getAuthorities(username));
// authService is a third party jar and if their upstream service
//is down , it throws a runtime exception
}
}
If I handle their error as follows then I end up with 403 but I want 503 in case service is down and 403 if user doesnt have right authority for endpoint he is accessing.
current handling auth service exception
public UserDetails loadUserByUsername(String username) {
try{
return new User(username, "",
authService.getAuthorities(username));
}
catch(AuthServiceException e) {
return new User(username, "",
Collections.emptyList());
}
}

Implement AuthenticationEntryPoint and override commence() method as follows.
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
And after that create a method in your WebSecurityCustomConfig class that initialize the RestAuthenticationEntryPoint class as follows.
public AuthenticationEntryPoint authenticationEntryPoint() {
RestAuthenticationEntryPoint ep = new RestAuthenticationEntryPoint();
return ep;
}
Then change your following line to http.exceptionHandling().authenticationEntryPoint(customEntryPoint());
to
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint());

Related

Getting a user's groups from LDAP in Spring Boot

Our legacy application is deployed on Glassfish, and uses javax.security to manage authorization.
The following code retrieves from LDAP the Active Director groups the user is a member of:
try{
subject = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");
Principal principal;
if (subject != null) {
Iterator<Principal> principalsIt = subject.getPrincipals().iterator();
while (principalsIt.hasNext()) {
principal = principalsIt.next();
ldapGroups.add(principal.toString());
}
}
}catch (PolicyContextException e) {
...
}
In our new Spring Boot application, after login, we can use the Spring SecurityContextHolder to get user details:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
This is how the user is being authenticated and authorized:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userSearchFilter("(...)")
.userSearchBase("...")
.groupSearchBase("...").groupSearchFilter("member={0}").contextSource()
.url("...").managerDn("...").managerPassword("...");
}
#Override
protected void configure(HttpSecurity security) throws Exception {
security.authorizeRequests().antMatchers("/*/**").permitAll().anyRequest().fullyAuthenticated().and()
.formLogin().loginPage("/login").successHandler(new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
redirectStrategy.sendRedirect(request, response, "/campaigns/myCampaigns");
}
});
}
}
Is there a way to modify the code which logs the user in, so that at the same time that they are authenticated and authorized, it also retrieves their groups at the same time. So far, the only examples I have found involve the use of LdapTemplate and making a separate call.
Thanks

How to create a principal based on an arbitrary http header?

I'm using spring security to secure my application, but I can't understand how the principal is actually created.
On the web I can only find references about how to provide a custom AuthenticationProvider but problem is that the principal will always be empty, this is because I need to construct it based on an http header, namely "MY_AUTH_HEADER".
This header contains data that I have to use in conjuction with a db.
So, starting by:
public class MedMadAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
authentication.getName(); //how can I create an "Authentication" object based on headers?
return null;
}
#Override
public boolean supports(Class<?> authentication) {
// TODO Auto-generated method stub
return false; //How can I specify my own auth class?
}
}
What do I have to define to be before this filter and create the Authentication object starting from headers?
The Authentication object gets extracted in an AbstractAuthenticationProcessingFilter.
You can create a custom AbstractAuthenticationProcessingFilter that creates an Authentication object based on information in your custom header.
In the example below, the CustomAuthenticationFilter will be invoked on requests to "/login".
It will create a custom Authentication object, namely CustomAuthenticationToken, based on the value in the header MY_AUTH_HEADER.
public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public CustomAuthenticationFilter(AuthenticationManager manager) {
super("/login");
setAuthenticationManager(manager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String authToken = request.getHeader("MY_AUTH_HEADER");
CustomAuthenticationToken authentication = new CustomAuthenticationToken(authToken);
return this.getAuthenticationManager().authenticate(authentication);
}
}
You can then add the custom filter to the security chain.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
)
.addFilterAt(new CustomAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
Depending on your authentication mechanism, you may be able to reuse an existing Authentication, such as BearerTokenAuthenticationToken or UsernamePasswordAuthenticationToken.
If you need to create a custom Authentication object, here is an example.
public class CustomAuthenticationToken extends AbstractAuthenticationToken {
private String token;
public CustomAuthenticationToken(String token) {
super(Collections.emptyList());
this.token = token;
}
#Override
public Object getCredentials() {
return this.token;
}
#Override
public Object getPrincipal() {
return this.token;
}
}
If you are using a custom Authentication object, then you can create a custom AuthenticationProvider to support your custom Authentication, as shown below.
This is not necessary if you are using an existing Authentication (e.g UsernamePasswordAuthenticationToken) as there is already an AuthenticationProvider that supports it.
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if ("secret".equals(authentication.getCredentials())) {
return authentication;
} else {
throw new BadCredentialsException("Header did not match expected value");
}
}
#Override
public boolean supports(Class<?> authentication) {
return CustomAuthenticationToken.class.isAssignableFrom(authentication);
}
}
This section of the Spring Security reference documentation explains how all these components interact with one another.

refresh_token grant_type error: UserDetailsService is required. But I dont want to specify one

I'm trying to create an Oauth authentication/authorization server using spring boot and dependencies
* spring-security-oauth2-autoconfigure
* nimbus-jose-jwt
and I'm following docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security-oauth2-authorization-server
The issue is that I don't want to specify a UserDetailsService since the information about the user account is in another service that doesn't expose passwords. That service just has an API in which input is user/pass and output is user info (if the user exists/credentials are correct).
So my code/configuration is a little deviated from the documentation.
#EnableAuthorizationServer
#Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
//injections
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(jwtTokenStore)
.accessTokenConverter(accessTokenConverter)
.authenticationManager(authenticationManager);
}
}
and
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//injections
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(travelerAuthenticationProvider); //my custom // authentication provider that calls the other service for checking credentials
}
}
and
#Component
public class TravelerAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TravelerAuthenticationProvider.class);
private OrderTravelerProfileClient travelerProfileClient;
public TravelerAuthenticationProvider(OrderTravelerProfileClient travelerProfileClient) {
this.travelerProfileClient = travelerProfileClient;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication.getName() == null || (authentication.getCredentials().toString().isEmpty())) {
return null;
}
var username = authentication.getName();
var password = authentication.getCredentials().toString();
try {
travelerProfileClient.authenticate(username, password);
} catch (Exception e) {
LOGGER.error("checking traveler {} credentials failed", username, e);
throw new BadCredentialsException("wrong traveler credentials");
}
var authorities = Set.of(new SimpleGrantedAuthority("traveler"));
var updatedAuthentication = new UsernamePasswordAuthenticationToken(username, password, authorities);
return updatedAuthentication;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Everything related to client_credentials and password flow works but when I try to use refresh_token flow, it complains that UserDetailsService is required. How should I solve the issue without defining a UserDetailsService and just relaying on my custom authentication provider?
UPDATE:
apparently refresh_token flow has a recheck for authentication (credentials) which needs another authentication provider for type PreAuthenticatedAuthenticationToken.class.
So I created a new auth provider like this:
#Component
public class TravelerRefreshTokenBasedAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TravelerRefreshTokenBasedAuthenticationProvider.class);
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
var currentAuthentication = (PreAuthenticatedAuthenticationToken) authentication;
//.....
return updatedAuthentication;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(PreAuthenticatedAuthenticationToken.class);
}
}
and update my security configs to:
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//injections
//this bean will be more configured by the method below and it will be used by spring boot
//for authenticating requests. Its kind of an equivalent to userDetailsService
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(travelerUserPassBasedAuthenticationProvider);
authenticationManagerBuilder.authenticationProvider(travelerRefreshTokenBasedAuthenticationProvider);
}
}
the issue is spring doesn't recognize my auth providers in refresh_token flow and tries to use a default one. And the default one is trying to use a UserDetailsService that doesn't exist.
I also feel that I don't need to create another provider and I can reuse the previous one. Because the check for which spring is failing to use my custom provider is a check against user/pass; which I was doing in my previous auth provider.
so all in all, until now, I feel I have to introduce my custom provider to spring differently for refresh_token flow comparing to password flow
Your AuthenticationProvider implementation only supports UsernamePasswordAuthenticationToken, which is used for username/password authentication, while the refresh_token flow tries to renew authentication using PreAuthenticatedAuthenticationToken (see DefaultTokenServices.java).
So you need to create another AuthenticationProvider for PreAuthenticatedAuthenticationToken and add it to AuthenticationManagerBuilder.
Update:
I've found that AuthorizationServerEndpointsConfigurer creates a new instance of DefaultTokenServices, if none is assigned, which in turn creates a new instance of PreAuthenticatedAuthenticationProvider and does not use the provided AuthenticationManager. To avoid this, you can create your own instance of DefaultTokenServices and pass it to AuthorizationServerEndpointsConfigurer:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(jwtTokenStore)
.accessTokenConverter(accessTokenConverter)
.tokenEnhancer(accessTokenConverter)
.authenticationManager(authenticationManager)
.tokenServices(createTokenServices(endpoints, authenticationManager));
}
private DefaultTokenServices createTokenServices(AuthorizationServerEndpointsConfigurer endpoints, AuthenticationManager authenticationManager) {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAuthenticationManager(authenticationManager);
return tokenServices;
}

WebSocket, Spring Security integration

I'm having small REST API application that is running on Spring boot. For security I'm using external provider (Auth0 in this case), and frontend Angular application provide token for each API call. This works great with minimal configuration:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
JwtWebSecurityConfigurer
.forRS256(apiAudience, issuer)
.configure(http)
.authorizeRequests()
.antMatchers("/websocket/**").permitAll()
.anyRequest().authenticated();
}
}
Now I'm trying to add some websocket support in it for notify users on some events. Some basic things:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").withSockJS();
}
}
Connection is working and I can introduce some HandshakeInterceptor to validate user's token that is sent throw url on connect:
public class HttpSessionHandshakeInterceptor implements HandshakeInterceptor {
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpServletRequest httpServletRequest = servletRequest.getServletRequest();
String token = httpServletRequest.getParameter("token");
AuthAPI auth = new AuthAPI("account url", "user id", "secret");
Request<UserInfo> authRequest = auth.userInfo(token);
try {
UserInfo info = authRequest.execute();
if (info.getValues().get("name") != null) {
return true;
}
} catch (APIException exception) {
} catch (Auth0Exception exception) {
}
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return false;
}
}
I'm having problem with current API calls that I want to be available also on sockets:
#Controller
public class TestController {
#PreAuthorize("hasAuthority('read:photos')")
#RequestMapping(value = "/photos", method = RequestMethod.GET)
#MessageMapping("/photos")
#ResponseBody
public String getPhotos() {
return "All good. You can see this because you are Authenticated with a Token granted the 'read:photos' scope";
}
}
Calling this from socket throws that, An Authentication object was not found in the SecurityContext exception. Is their any why to provide SecurityContext on socket calls? Maybe throw ChannelInterceptorAdapter.preSend? I found a lot of questions about this, but no answers have given (example). Auth0 team also not provide any working example of this.
I also tried to use WebSocket Security, but cant rewire it to Auth0.
Do anyone have any working solution with this more granular approach? Small note, on frontend using SockJS and Stomp. Can send token throw headers or throw url.

Custom AuthenticationProvider is not called

I want to have a basic auth-protected REST app. I followed the general instructions from http://www.baeldung.com/spring-security-authentication-provider in order to get the security working.
I ended up creating my implementation of AuthenticationProvider, but it never gets called by Spring. All requests end up with an error:
{"timestamp":1460199213227,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/test"}
without the AuthenticationProvider ever doing anything.
The app is annotation-based and here are the relevant bits:
Security setup
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
CustomAuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authenticationProvider(authenticationProvider)
.authorizeRequests()
.anyRequest().authenticated().and().httpBasic();
}
}
AuthenticationProvider
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserDAO userDAO;
#Autowired
private Authenticator authenticator;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// This never gets called, I checked with debugger
String username = authentication.getName();
String password = authentication.getCredentials().toString();
User user = userDAO.findByUsername(username);
User authenticatedUser = authenticator.authenticate(user, password);
if (authenticatedUser == null){
throw new RESTAuthenticationException("Auth failed");
}
List<GrantedAuthority> authorityList = new ArrayList<>();
return new UsernamePasswordAuthenticationToken(user, authorityList);
}
#Override
public boolean supports(Class<?> aClass) {
return aClass.equals(UsernamePasswordAuthenticationToken.class);
}
}
Controller
#RestController
public class UserController {
#RequestMapping(value = "/test")
public ResponseEntity test(#AuthenticationPrincipal User user) {
return ResponseEntity.ok().body(user);
}
}
You receive a response with status code 401. This is the "unauthorized" http status code. It is probably caused by a missing/malformed Authorization header in your request.
You are using Http-Basic: it requires the following header in the request :
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
where the string QWxhZGRpbjpPcGVuU2VzYW1l is the string <user>:<password> base64 encoded.

Resources