Reputation: 35864
I've been scouring the web looking at a lot of different ways to implement token based authentication using Spring & Spring Security (SS). I'm not really wanting to go the full on Oauth route so I've been trying to do something and keep things pretty simple.
What I want is to pass a username/password to SS's built in mechanism and on success, generate a token that I pass back to the user. The user then makes all future requests with the token in a custom header. The token will expire after som length of time. I am aware this is kind of what Oauth does but again, don't want to use it.
So I've got something kind of working. I can login with username/password and get the token back. I can then make requests with the token successfully. What isn't working are authorities. Here's what I'm doing...
HttpServletResponse.SC_UNAUTHORIZED
HttpServletResponse.SC_OK
and the tokenHttpServletResponse.SC_UNAUTHORIZED
Now, I also have a custom UserDetails and UserDetailsService.
public class MyUserDetails implements UserDetails {
private User user; // this is my own User object
private List<GrantedAuthority> authorities;
public MyUserDetails(User user, List<GrantedAuthority> authorities) {
this.user = user;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public void setAuthorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
}
}
@Service
public class MyUserDetailsService implements UserDetailsService {
private final UserService userService;
@Autowired
public MyUserDetailsService(UserService userService) {
this.userService = userService;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
}
List<GrantedAuthority> authorities = new ArrayList<>();
// for now, just add something
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new MyUserDetails(user, authorities);
}
}
In order to look in the header for the token and tell spring all is well, I created a AuthTokenFilter...
public class AuthTokenFilter extends UsernamePasswordAuthenticationFilter {
@Autowired
private MyUserDetailsService userDetailsService;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String authToken = httpRequest.getHeader("X-TOKEN-AUTH");
String username = null;
if (authToken != null) {
username = Jwts.parser()
.setSigningKey("1234")
.parseClaimsJws(authToken)
.getBody()
.getSubject();
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// TODO: validate token
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
And this is how I've configured web security:
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private RestAuthEntryPoint authenticationEntryPoint;
@Autowired
private AuthSuccessHandler authSuccessHandler;
@Autowired
private AuthFailureHandler authFailureHandler;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(new ShaPasswordEncoder());
return authenticationProvider;
}
@Bean
public AuthTokenFilter authenticationTokenFilterBean() throws Exception {
AuthTokenFilter authenticationTokenFilter = new AuthTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(authenticationProvider())
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.formLogin()
.permitAll()
.loginProcessingUrl("/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(authSuccessHandler)
.failureHandler(authFailureHandler)
.and()
.logout()
.permitAll()
.and()
.sessionManagement()
.maximumSessions(1);
http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests().anyRequest().authenticated();
}
}
Everything seems to work accept SS isn't limiting access at all. If the token is there SS just lets everything pass.
Upvotes: 2
Views: 1160
Reputation: 35864
Well, after much trial and error it was as simple as adding the following to my SpringSecurityConfig
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
Kind of surprised that I didn't run into this sooner. Not sure if this is something somewhat new or what.
Upvotes: 3