I want to secure a Spring Boot REST API which is consumed by an Angular app. No user and no other app should have access to this API.
I have no control over this angular app, but I have been told that the actual authentication of the users is being done on the Angular app, which sets and sends a JWT after authenticating it's users. Since the angular app and my API are deployed on the same domain, I am indeed able to access the JWT which is sent as a cookie with the request. I am also able to successfully extract the username from it.
I must now authenticate and authorize the request using the JWT username and the roles associated with it to restrict access to certain endpoints. In order to do this, I have been given access to several database views which contain the required information, however I don't get any sort of password information. I have no control over the views or anything else in that database. Obviously, the user is unauthorized if the username from the token is not found in these views. Same goes for the roles.
My question is how do I configure WebSecurityConfigurerAdapter
and UserDetails
?
Whatever I try, I either instantly get 401 unauthorized or SecurityContextHolder.getContext().getAuthentication().getPrincipal()
returns "anonymousUser"
instead of the UserDetails
object when called from the controller code, even though it is being set correctly.
This is just a guess, but maybe it returns 401 because the overriden getPassword()
method in my UserDetails
class returns an empty string (return "";
), but what should it return since I don't, at any point, have access to anything resembling a user's password?
WebSecurityConfigurerAdapter:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
httpSecurity.cors().and().csrf().disable()
// I will eventually filter multiple endpoints by roles
// but at this point I just want it to work
.authorizeRequests().anyRequest().authenticated().and() // I am guessing it fails here because of the password issue?
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
UserDetails:
public class MyUserDetails implements UserDetails
{
private static final long serialVersionUID = 7371936004280236913L;
private final MyUserDto MyUser;
public MyUserDetails(MyUserDto MyUser)
{
this.MyUser = MyUser;
}
public MyUserDto getMyUser()
{ return MyUser; }
@Override
public Collection<? extends GrantedAuthority> getAuthorities()
{
List<MyUserAuthority> authorities = new ArrayList<MyUserAuthority>();
for(MyRoleDto userRole : MyUser.getRoles())
authorities.add(new MyUserAuthority(userRole));
return authorities;
}
@Override
public String getPassword()
{ return ""; } // What should this return?
@Override
public String getUsername()
{ return this.MyUser.getUserId(); }
@Override
public boolean isAccountNonExpired()
{ return true; }
@Override
public boolean isAccountNonLocked()
{ return true; }
@Override
public boolean isCredentialsNonExpired()
{ return true; }
@Override
public boolean isEnabled()
{ return true; }
}