Mohamed Kamal Mohamed Kamal - 4 months ago 143
Java Question

Spring security 4. JSON REST with custom authentication and authorization

We're developing Spring 4 REST/JSON API but we need to have custom Authentication service to authenticate against 3rd party service.

restrictions: We don't want to ask the user for username/password. we are authenticating using "cookie" (send with the request initiator). And we need this authentication process in background. (Might sound weird but that's the case).

we could implement that using registering custom authentication/authorization request filters. but that made us loosing the power of spring "authorization" module that we are planning to use afterwards.

So what we did till now, wrote custom WebSecurityConfigurerAdapter with our own custom AuthenticationProvider and UserDetailsService but these configuration doesn't seem to work.

the application doesn't go inside AuthenticationProvider.authenticate

here is the configuration we had.

AuthenticationProvider.java:

@Service
public class AuthenticationService implements AuthenticationProvider, UserDetailsService {

@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
// DOESN'T ENTER THIS FUNCTION
// do authentication stuff
}


@Override
public boolean supports(Class<?> authentication) {
// JUST FOR TESTING
return true;
}


@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {

// DOESN'T ENTER THIS METHOD
return null;
}
}


SecurityConfig.java:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

private static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfig.class);

@Autowired
private AuthenticationService authService;

@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/ignoredURL/**");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //HTTP with Disable CSRF
.authorizeRequests()
.antMatchers("/api/XYZ/**").hasRole("ADMIN")
.anyRequest().authenticated();
// adding ".httpBasic()" automatically prompts user for username/password
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// THIS IS NOT TYPO, I use one service implements both interfaces.
auth.userDetailsService(authService);
auth.authenticationProvider(authService);
}


}

Answer

Fixed by adding 2 more classes (Filter extending AbstractPreAuthenticatedProcessingFilter and another class CustomUser implements UserDetails) then made my AuthenticaitonService implements spring UserDetailsService here are the details:

please have a look on what this filter does and how it works

1- Create AbcFilter extends spring AbstractPreAuthenticatedProcessingFilter

2- Override "getPreAuthenticatedPrincipal". This method extracted the cookie value and return it. (Whatever the Object you return here is what you get as a parameter in UserDetailsService.loadUserByUsername).

3- Modified my service to implement spring UserDetailsServiceand inside loadUserByUsername did all authentication logic here and set all logged in user in CustomUser object. Then return it.

4- Whenever request matches /api/XYZ/**, Spring will call your CustomUser.getAuthorities and try to find ADMIN role there.