I'm using Spring Boot + Spring Security and trying to implement an AuthenticationSuccessHandler so that the app can save a last login timestamp to the database for the user.
Below is my attempt at the code. The issue is I'm getting a class cast exception because it does not like my cast to User from authentication.
Error
java.lang.ClassCastException: org.springframework.security.core.userdetails.User cannot be cast to com.example.model.User
Code
#Component
public class SecurityHandler implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException {
// Exception throw here during cast
User user = (User)authentication.getPrincipal();
user.setLastLogin(new Date());
userService.saveUser(user);
handle(request, response, authentication);
}
protected void handle(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException {
String targetUrl = "/dashboard";
redirectStrategy.sendRedirect(request, response, targetUrl);
}
}
Related
I am fairly new to Java and I have a spring app. For the logging-in feature I'm using AuthenticationProvider. However, I am unable to show the custom error message if the authentication is not successful. It logs the correct message, but I'm unable to pass it to the view.
// CustomAuthenticationProvider.java
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
UserAuthenticationRequest loginRequest = new UserAuthenticationRequest(name, password);
Client client = ClientBuilder.newBuilder()
.register(JacksonConfig.class)
.register(JacksonFeature.class)
.build();
Invocation.Builder request = client.target(apiBaseUrl)
.path("login")
.request(MediaType.APPLICATION_JSON);
Response response = request.post(Entity.json(loginRequest));
if (response.getStatus() == 200) {
return new UsernamePasswordAuthenticationToken(name, password, new ArrayList<>());
} else {
ExceptionResponse errorResponse = response.readEntity(ExceptionResponse.class);
return null;
}
}
}
If I log errorResponse.getMessage() - I get the message I need, but how do I pass it to the view? I'm already showing a generic message if there's a param ?error.
So far I tried to add it to Model, save it to session but it threw an error 500 each time.
In your case, you probably should make custom AuthenticationFailureHandler to handle AuthenticationException thrown in AuthenticationProvider.
public class CustomHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
// logic here
}
}
Then you can manipulate with the response.
You have to register it in your security configuration http.formLogin().failureHandler(new CustomHandler());.
EDIT 1
Throw one of implementations of AuthenticationException.
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new BadCredentialsException("Wrong credentials");
}
I have implemented authentication for my APIs and it works as expected . The user first access the auth api to get a token by passing username and password. This api returns a token. The user then calls the secure apis by passing the tokens.
This issue is when the user passes an invalid token or does not pass a token the default error object is returned from Spring Boot. I wanna customize this object and for this, I wrote a custom exception handler extending ResponseEntityExceptionHandler but this is not getting triggered as the exception is thrown before the controller kicks in.
#ExceptionHandler(value = {InsufficientAuthenticationException.class})
public final ResponseEntity<Object>
authenticationException(InsufficientAuthenticationException ex) {
List<String> details = new ArrayList<>();
details.add("Authentication is required to access this resource");
ErrorResponse error = new ErrorResponse("error", "Unauthorized", details);
return new ResponseEntity(error, HttpStatus.FORBIDDEN);
}
The AuthenticationProvider is responsible to find user based on the authentication token sent by the client in the header. This is how our Spring based token authentication provider looks like:
#Component
public class AuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
#Autowired
CustomerService customerService;
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
//
}
#Override
protected UserDetails retrieveUser(String userName, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
Object token = usernamePasswordAuthenticationToken.getCredentials();
return Optional
.ofNullable(token)
.map(String::valueOf)
.flatMap(customerService::findByToken)
.orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token));
}
The token authentication filter is responsible to get the authentication filter from the header and call the authentication manager for authentication. This is how the authentication filter looks like:
public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {
AuthenticationFilter(final RequestMatcher requiresAuth) {
super(requiresAuth);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
Optional tokenParam = Optional.ofNullable(httpServletRequest.getHeader(AUTHORIZATION)); //Authorization: Bearer TOKEN
String token= httpServletRequest.getHeader(AUTHORIZATION);
token= StringUtils.removeStart(token, "Bearer").trim();
Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(token, token);
return getAuthenticationManager().authenticate(requestAuthentication);
}
#Override
protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}
Spring security configuration looks like:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/api/**")
);
AuthenticationProvider provider;
public SecurityConfiguration(final AuthenticationProvider authenticationProvider) {
super();
this.provider = authenticationProvider;
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(provider);
}
#Override
public void configure(final WebSecurity webSecurity) {
webSecurity.ignoring().antMatchers("/token/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.and()
.authenticationProvider(provider)
.addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS)
.authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
}
#Bean
AuthenticationFilter authenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
//filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(HttpStatus.FORBIDDEN);
}
#Autowired
private HandlerExceptionResolver handlerExceptionResolver;
public AuthenticationEntryPoint authenticationEntryPoint() {
log.error("in authenticationEntryPoint");
return new AuthenticationEntryPoint() {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
log.error("in commence");
try {
log.error(authException.getLocalizedMessage());
handlerExceptionResolver.resolveException(request, response, null, authException);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new ServletException(e);
}
}
};
}
}
P.S.: Refer to https://www.javadevjournal.com/spring/securing-a-restful-web-service-with-spring-security/
Since you are customising AbstractAuthenticationProcessingFilter , you can also customise its AuthenticationFailureHandler which will be invoked when attemptAuthentication() throw AuthenticationException. You can then handle the error at there.
An example is :
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler{
#Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
//create your custom error object
CustomError error = xxxxx;
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
// Format the custom error object as JSON string , for example using Jackson :
ObjectMapper mapper = new ObjectMapper();
response.getWriter().write(mapper.writeValueAsString(error));
}
}
And configure to use it:
#Bean
AuthenticationFilter authenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
return filter;
}
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 have the following filter definition:
#Component
public class JWTAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
private UserDetailsService customUserDetailsService;
#Autowired
private AuthenticationManager authenticationManager;
private static Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
private final static UrlPathHelper urlPathHelper = new UrlPathHelper();
public JWTAuthenticationFilter() {
super("/**"); // what should I pass here ?
setAuthenticationManager(new NoOpAuthenticationManager());
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request, customUserDetailsService);
return authentication;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(request, response);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,FilterChain chain, Authentication authentication) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authentication);
logger.debug("successful authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
logger.debug("failed authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
}
}
and the following two configure methods in web configuration for my Spring security:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/login**", "/signup**");
}
There are a couple of problems with this code:
I don't want the filter to exectue when calls are made to /login and /signup endpoints, but doFilter still gets hit even when a call is made to these two endpoints.
attemptAuthentication, successfulAuthentication and unsuccessfulAuthentication don't get hit, but doFilter does. Why? why these methods are not called ?
What is happening here ? and why ?
Try this(but not tested!):
public class JWTAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
private UserDetailsService customUserDetailsService;
#Autowired
private AuthenticationManager authenticationManager;
private static Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
private final static UrlPathHelper urlPathHelper = new UrlPathHelper();
public JWTAuthenticationFilter() {
super("/**"); // what should I pass here ?
setAuthenticationManager(new NoOpAuthenticationManager());
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request, customUserDetailsService);
return authentication;
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,FilterChain chain, Authentication authentication) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authentication);
logger.debug("successful authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
logger.debug("failed authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
}
}
In your configuration file:
#Bean("jwtAuthenticationFilter")
public LoginRequestFilter jwtAuthenticationFilter(){
LoginRequestFilter filter = new LoginRequestFilter();
filter.setAuthenticationManager(authenticationManager);
filter.setAuthenticationSuccessHandler(successHandler);
filter.setAuthenticationFailureHandler(failureHandler);
//filter.setAuthenticationFailureHandler(failureHandler);
return filter;
}
and add this as well
http.authorizeRequests().and()
.addFilterAt(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
I don't want the filter to exectue when calls are made to /login and /signup endpoints, but doFilter still gets hit even when a call is made to these two endpoints.
While I can't explain the relationship betweeh the two configure() methods you've written, you can remove configure(WebSecurity) and include all path expressions in configure(HttpSecurity), like this:
.authorizeRequests()
.antMatchers("/login", "/signup")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/**")
.authenticated();
attemptAuthentication, successfulAuthentication and unsuccessfulAuthentication don't get hit, but doFilter does. Why? why these methods are not called ?
AbstractAuthenticationProcessingFilter.doFilter() would normally invoke your overridden attemptAuthentication() (and other methods, as appropriate). Your overridden implementation effectively turns this filter into a "no op" by handing the request down to the remainder of the filter chain unconditionally.
Probably too late, however, I've given this answer to a similar question, so it can also be useful here:
Your custom implementation extends the UsernamePasswordAuthenticationFilter (which in its turn extends the AbstractAuthenticationProcessingFilter). The UsernamePasswordAuthenticationFilter, by default, is used for .formLogin authentication, handling the default AntRequestMatcher "/login". If you use a different protected endpoint, the filter's attemptAuthentication() method never gets action. So, if you want to use a different matcher (a different protected endpoint), you have to override the default AntRequestMatcher. For instance, you can do so within your custom filter constructor, by using something like that:
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/signin", "GET"));
I have a rest api where I am authenticating using spring security Basic Authorization where client sends username and password for each request.
Now, I wanted to implement token based authentication where I will send a token in response header when user is authenticated at first. For further requests, client can include that token in the header which will be used to authenticate the user to the resources. I have two authentication providers tokenAuthenticationProvider and daoAuthenticationProvider
#Component
public class TokenAuthenticationProvider implements AuthenticationProvider {
#Autowired
private TokenAuthentcationService service;
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
final HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
final String token = request.getHeader(Constants.AUTH_HEADER_NAME);
final Token tokenObj = this.service.getToken(token);
final AuthenticationToken authToken = new AuthenticationToken(tokenObj);
return authToken;
}
#Override
public boolean supports(final Class<?> authentication) {
return AuthenticationToken.class.isAssignableFrom(authentication);
}
}
And in daoAuthenticationProvider I am setting custom userDetailsService and authenticating against user login details by fetching it from the database (which is working fine as long as user name and password are passed using Authorization:Basic bGllQXBpVXNlcjogN21wXidMQjRdTURtR04pag== as header)
But when I include token in the header using X-AUTH-TOKEN (which is Constants.AUTH_HEADER_NAME), tokenAuthenticationProvider is not being called. I am getting error as
{"timestamp":1487626368308,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/find"}
And here is how I am adding authentication providers.
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
final UsernamePasswordAuthenticationProvider daoProvider = new
UsernamePasswordAuthenticationProvider(this.service, this.passwordEncoder());
auth.authenticationProvider(this.tokenAuthenticationProvider);
auth.authenticationProvider(daoProvider);
}
Please suggest how can I implement Token based authentication without hurting the current behavior of spring security.
Here is how I was able to implement token based authentication and basic authentication
SpringSecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(this.participantService).passwordEncoder(this.passwordEncoder());
}
#Override
protected void configure(final HttpSecurity http) throws Exception
{
//Implementing Token based authentication in this filter
final TokenAuthenticationFilter tokenFilter = new TokenAuthenticationFilter();
http.addFilterBefore(tokenFilter, BasicAuthenticationFilter.class);
//Creating token when basic authentication is successful and the same token can be used to authenticate for further requests
final CustomBasicAuthenticationFilter customBasicAuthFilter = new CustomBasicAuthenticationFilter(this.authenticationManager() );
http.addFilter(customBasicAuthFilter);
}
}
TokenAuthenticationFilter.java
public class TokenAuthenticationFilter extends GenericFilterBean
{
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
throws IOException, ServletException
{
final HttpServletRequest httpRequest = (HttpServletRequest)request;
//extract token from header
final String accessToken = httpRequest.getHeader("header-name");
if (null != accessToken) {
//get and check whether token is valid ( from DB or file wherever you are storing the token)
//Populate SecurityContextHolder by fetching relevant information using token
final User user = new User(
"username",
"password",
true,
true,
true,
true,
authorities);
final UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
CustomBasicAuthenticationFilter.java
#Component
public class CustomBasicAuthenticationFilter extends BasicAuthenticationFilter {
#Autowired
public CustomBasicAuthenticationFilter(final AuthenticationManager authenticationManager) {
super(authenticationManager);
}
#Override
protected void onSuccessfulAuthentication(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response, final Authentication authResult) {
//Generate Token
//Save the token for the logged in user
//send token in the response
response.setHeader("header-name" , "token");
}
}
As our CustomBasicAuthenticationFilter has been configured and added as a filter to the spring security,
Whenever basic authentication is successful the request will be redirected to onSuccessfulAuthentication where we set the token and send it in the response with some header "header-name".
If "header-name" is sent for further request, then the request will go through TokenAuthenticationFilter first before attempting to try Basic Authentication.
You can try setting your custom AuthenticationToken token in your authentication filter, for example:
public class AuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final String authTokenHeader = ((HttpServletRequest)request).getHeader(Constants.AUTH_HEADER_NAME);
if (authTokenHeader != null) {
SecurityContextHolder.getContext().setAuthentication(createAuthenticationToken(authTokenHeader));
}
chain.doFilter( request, response );
}
}