Defined Property from Application.property getting NULL in class file using Spring boot Filter Implementation - spring-boot

I am trying to do one sample of rest template and property injection for variable defined from application.property. For this I created one filter implementation and done like the following. But when I am accessing the property I am getting error like following
java.lang.IllegalArgumentException: URI must not be null
And I created my filter implementation code like the following,
#Component
public class CustomSecurityFilter extends OncePerRequestFilter implements Filter{
#Value ("${securityTriggerServiceApiUrl}" )
public String triggerUrl;
#Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
String authHeaderToken = request.getHeader("authToken");
System.out.println("token :"+authHeaderToken);
if(checkAuthenticationByAuthToken(authHeaderToken))
{
filterChain.doFilter(request, response);
}
else
{
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
}
private static HttpHeaders getApiHeaders(){
String plainCredentials="${seccurityTriggerEncodingCredential}";
String base64Credentials = new String(Base64.encodeBase64(plainCredentials.getBytes()));
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Credentials);
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
public Boolean checkAuthenticationByAuthToken(String authTokenRequest) {
AuthorizationRequest authApiRequestObj = new AuthorizationRequest();
authApiRequestObj.auth_token = authTokenRequest;
RestTemplate restTemplate = new RestTemplate();
HttpEntity<Object> request = new HttpEntity<Object>(authApiRequestObj, getApiHeaders());
AuthorizationResponse authorizationResponseObj = restTemplate.postForObject(getApiTriggerStringUrl(), request, AuthorizationResponse.class);
System.out.println("RoleId is :"+authorizationResponseObj.role_id);
if(authorizationResponseObj.role_id >= 0 ) {
return true;
}
return false;
}
public String getApiTriggerStringUrl() {
return this.triggerUrl;
}
}
My application.property file added like the following,
seccurityTriggerEncodingCredential=test:test
securityTriggerServiceApiUrl=http://localhost:8065/test/security/authorizeUser
My Security config file contains like the following way,
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.addFilterAfter(new CustomSecurityFilter(), BasicAuthenticationFilter.class);
}
}
Why I am getting the error like this?

The problem is while registering your filter, you have created a new filter with new. So configuration is not picking the one created by Spring. You can do either of following -
1) Autowire the existing spring bean in your configuration class -
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomSecurityFilter customSecurityFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.addFilterAfter(customSecurityFilter, BasicAuthenticationFilter.class);
}
}
You can do autowiring any way either directly through #Autowired or through constructor.
2) Second way is to create CustomSecurityFilter bean by yourself. Sample code as per your code -
a) Don't annotate your filter with #Component. Remove #Value and create a setter for your property.
public class CustomSecurityFilter extends OncePerRequestFilter implements Filter{
public String triggerUrl;
public void setTriggerUrl(String triggerUrl) {
this.triggerUrl = triggerUrl;
}
#Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
String authHeaderToken = request.getHeader("authToken");
System.out.println("token :"+authHeaderToken);
if(checkAuthenticationByAuthToken(authHeaderToken))
{
filterChain.doFilter(request, response);
}
else
{
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
}
private static HttpHeaders getApiHeaders(){
String plainCredentials="${seccurityTriggerEncodingCredential}";
String base64Credentials = new String(Base64.encodeBase64(plainCredentials.getBytes()));
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Credentials);
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
public Boolean checkAuthenticationByAuthToken(String authTokenRequest) {
AuthorizationRequest authApiRequestObj = new AuthorizationRequest();
authApiRequestObj.auth_token = authTokenRequest;
RestTemplate restTemplate = new RestTemplate();
HttpEntity<Object> request = new HttpEntity<Object>(authApiRequestObj, getApiHeaders());
AuthorizationResponse authorizationResponseObj = restTemplate.postForObject(getApiTriggerStringUrl(), request, AuthorizationResponse.class);
System.out.println("RoleId is :"+authorizationResponseObj.role_id);
if(authorizationResponseObj.role_id >= 0 ) {
return true;
}
return false;
}
public String getApiTriggerStringUrl() {
return this.triggerUrl;
}
}
b) Your configuration class will be then -
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.addFilterAfter(customSecurityFilter(), BasicAuthenticationFilter.class);
}
#Bean
public CustomSecurityFilter customSecurityFilter() {
CustomSecurityFilter customSecurityFilter = new CustomSecurityFilter();
customSecurityFilter.setTriggerUrl(<property value>);
return customSecurityFilter;
}
}

Related

#Value variable is null in customFilter implementing Filter

my custom filter is not taking value from .properties/.yml file
Note: property file is located at src/main/resources folder
#Slf4j
public class CustomFilter implements Filter {
#Value("${xyz.domainName:http://localhost:8080/x1}")
private String DOMAIN_NAME;
private static final String REDIRECT_URL_ENDPOINT = "/v1/xyz/abc/";
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String url = request.getRequestURL().toString();
String id = url.substring(url.lastIndexOf("/") + 1);
if (url.startsWith(DOMAIN_NAME)) {
ServletContext context = request.getServletContext();
RequestDispatcher dispatcher = context.getRequestDispatcher(REDIRECT_URL_ENDPOINT + id);
dispatcher.forward(request, response);
}
else
filterChain.doFilter(request, response);
}
}
Edit (Added WebSecurityConfigClass): My WebSecurityConfig class looks like:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new CustomFilter(), ApiKeyAuthFilter.class);
}
}
Looking at the code, you filter is not managed by the Spring Context, therefore Spring-related functionalities like #Value do not work. So, let Spring take care of your filter.
#Component
#Slf4j
public class CustomFilter implements Filter {
#Value("${xyz.domainName:http://localhost:8080/x1}")
private String DOMAIN_NAME;
private static final String REDIRECT_URL_ENDPOINT = "/v1/xyz/abc/";
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String url = request.getRequestURL().toString();
String id = url.substring(url.lastIndexOf("/") + 1);
if (url.startsWith(DOMAIN_NAME)) {
ServletContext context = request.getServletContext();
RequestDispatcher dispatcher = context.getRequestDispatcher(REDIRECT_URL_ENDPOINT + id);
dispatcher.forward(request, response);
}
else
filterChain.doFilter(request, response);
}
#Bean
public FilterRegistrationBean registerFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(this);
registration.addUrlPatterns("/*");
return registration;
}
}
If you want to register the filter before a Spring security filter you can do this:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Customfilter customfilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(customfilter, ApiKeyAuthFilter.class);
}
}

How does Spring Security Filter Work With Custom Authentication and How To Combine It with Servlet Filter?

So I have a question regarding Spring Security. So I want to check authentication using custom header which then I want to check the token given in the custom header to redis value and set the data object as credentials at custom implementation of abstract authentication token.
I have already followed the tutorial in this web: https://shout.setfive.com/2015/11/02/spring-boot-authentication-with-custom-http-header/, but I can't update the authentication interface in SecurityContextHolder.getContext() (I set the credentials in my implementation of Authentication Interface, but when I get it in the service, the credentials is null).
I also found other problems, I actually want to order the filter like this:
ExceptionHandlerFilter (to catch exception error in the filter) -> Other filter or CustomWebSecurityConfigurerAdapter.
But when the url matches the antMatcher, I found that ExceptionHandlerFilter was skipped by the application.
I was so confused by this and could not find better tutorial in implementing custom authentication using Spring Security. So I want to ask whether you guys can tell me how Spring Security works and how to combine it with Filter?
Here is my first filter to catch exception
#Component
#Order(0)
public class ExceptionHandlerFilter extends OncePerRequestFilter {
private JaminExceptionHandler exceptionHandler;
private ObjectMapper objectMapper = new ObjectMapper();
#Autowired
public ExceptionHandlerFilter(JaminExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (Throwable exception) {
ResponseEntity<?> responseEntity = this.exceptionHandler.handleException(exception, request);
response.setStatus(responseEntity.getStatusCode().value());
response.setHeader("Content-Type", "application/json");
response.getWriter().write(this.objectMapper.writeValueAsString(responseEntity.getBody()));
}
}
}
Here is my Auth Filter
#Component
public class AuthFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("J-Auth");
if (token != null) {
Authentication auth = new JaminAuthenticationToken(token);
SecurityContextHolder.getContext().setAuthentication(auth);
filterChain.doFilter(request, response);
} else {
throw new JaminException("Not authorized", JaminExceptionType.NOT_AUTHORIZED, HttpStatus.UNAUTHORIZED);
}
}
}
Authentication Provider
#Component
public class JaminAuthenticationProvider implements AuthenticationProvider {
private RedisTemplate<String, String> authRedis;
private ObjectMapper objectMapper = new ObjectMapper();
#Autowired
public JaminAuthenticationProvider(#Qualifier("authRedis") RedisTemplate<String, String> authRedis) {
this.authRedis = authRedis;
}
private UserDTO getUserDTO(String token) throws IOException {
String userData = this.authRedis.opsForValue().get(token);
if (userData == null) {
throw new JaminException("Not authorized", JaminExceptionType.NOT_AUTHORIZED, HttpStatus.UNAUTHORIZED);
}
return this.objectMapper.readValue(userData, UserDTO.class);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
JaminAuthenticationToken auth = (JaminAuthenticationToken) authentication;
try {
UserDTO userDTO = this.getUserDTO(auth.getToken());
auth.setCredentials(userDTO);
return auth;
} catch (IOException e) {
e.printStackTrace();
}
throw new JaminException("Not authorized", JaminExceptionType.NOT_AUTHORIZED, HttpStatus.UNAUTHORIZED);
}
#Override
public boolean supports(Class<?> authentication) {
return JaminAuthenticationToken.class.isAssignableFrom(authentication);
}
}
WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
#Order(1)
public class JaminSecurityAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private JaminAuthenticationProvider jaminAuthenticationProvider;
private void disableDefaultSecurity(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable();
http.formLogin().disable();
http.logout().disable();
http.httpBasic().disable();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
this.disableDefaultSecurity(http);
http.antMatcher("/auth/check")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(new AuthFilter(), BasicAuthenticationFilter.class);
// http.authorizeRequests().anyRequest().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jaminAuthenticationProvider);
}
}
Spring Security has some "before and after" steps. There are a few Handlers that can help. I don't know your code, but if you can get your authentication ok, maybe you just have to extend a SuccessHandler and set the authentication there, like i did in my blog project:
if(checkEmail(authentication)) {
val adminRole = SimpleGrantedAuthority("ROLE_ADMIN")
val oldAuthorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities()
val updateAuthorities = mutableListOf<GrantedAuthority>()
updateAuthorities.add(adminRole)
updateAuthorities.addAll(oldAuthorities)
SecurityContextHolder.getContext().setAuthentication(UsernamePasswordAuthenticationToken(authentication.getPrincipal(),
authentication.getCredentials(),
updateAuthorities))
}
And about the filters, maybe you can find your answer here. I don't like using filters and interceptors, but sometimes they are really necessary.

Spring security using JWT, how to exclude certain endpoints like /login from being authenticated using JwtAuthenticationFilter?

I want to exclude /login url from being authenticated by spring security.
My configuration class looks like'
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests().antMatchers("/v1/pricing/login").permitAll()
.antMatchers("v1/pricing/**").authenticated().and()
.addFilterBefore(corsFilter,UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v1/pricing/login");
}
JwtAuthenticationFilter looks like
- commented the exception part, as it starts throwing exception in login also
#Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
#Autowired
JwtTokenProvider jwtTokenProvider;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) {
String[] userInfo = jwtTokenProvider.getUserDetailsFromJWT(jwt);
UserDetails userDetails = new UserPrincipal(Long.parseLong(userInfo[0]), userInfo[1], userInfo[2], null,
userInfo[3]);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, null);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if (StringUtils.hasText(token)) {
return token;
} /*else {
throw new AuthenticationServiceException("Authorization header cannot be blank!");
}*/
return null;
}
}
Any request with /v1/pricing/login still goes to JWtAuthentication filter and fails.
JwtTokenAuthenticationProcessingFilter filter is configured to skip following endpoints: /api/auth/login and /api/auth/token. This is achieved with SkipPathRequestMatcher implementation of RequestMatcher.
public class SkipPathRequestMatcher implements RequestMatcher {
private OrRequestMatcher matchers;
private RequestMatcher processingMatcher;
public SkipPathRequestMatcher(List<String> pathsToSkip, String processingPath) {
Assert.notNull(pathsToSkip);
List<RequestMatcher> m = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList());
matchers = new OrRequestMatcher(m);
processingMatcher = new AntPathRequestMatcher(processingPath);
}
#Override
public boolean matches(HttpServletRequest request) {
if (matchers.matches(request)) {
return false;
}
return processingMatcher.matches(request) ? true : false;
}
}
Then call :
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization";
public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
List<String> pathsToSkip = Arrays.asList(TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT);
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
JwtTokenAuthenticationProcessingFilter filter
= new JwtTokenAuthenticationProcessingFilter(failureHandler, tokenExtractor, matcher);
filter.setAuthenticationManager(this.authenticationManager);
return filter;
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(ajaxAuthenticationProvider);
auth.authenticationProvider(jwtAuthenticationProvider);
}
#Bean
protected BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // We don't need CSRF for JWT based authentication
.exceptionHandling()
.authenticationEntryPoint(this.authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
.antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
.antMatchers("/console").permitAll() // H2 Console Dash-board - only for testing
.and()
.authorizeRequests()
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
.and()
.addFilterBefore(buildAjaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
}
}

PermitAll not working in Spring Security

I have two rules, the first one every url from oauth/** should be without security and and other url have to security. But now all urls are secure include url from oauth/**.
This is my security config rule.
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// JWT dont need CSRF
httpSecurity.csrf().disable().exceptionHandling().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.antMatchers("oauth/**").permitAll().and()
.addFilterBefore(new JwtAuthenticationTokenFilter(), BasicAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
}
when I request the url http://localhost:8080/oauth/fb that is enter my JwtAuthenticationTokenFilter, and I want this url don't enter this filter.
You can override configure method with WebSecurity parameter.
#Override
public void configure(final WebSecurity web) throws Exception
{
web.ignoring().antMatchers("oauth/**");
}
This method should be used when serving static content such as css/* js/*, suggested in the documentation, however I couldn't find another way to permit URL mapping with custom filter in Spring Security.
<security:http pattern="/support/**" security="none"/>
You would probably need to write the Java equivalent of the above XML configuration. Basically, you are setting a new filter chain with no security for the above pattern.
I faced a similar problem. My security config:
// ... imports
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
private final JwtFilter jwtFilter;
#Autowired
public SecurityConfig(#Qualifier("userDetailsServiceImpl") UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder,
JwtProvider jwtProvider) {
this.userDetailsService = userDetailsService;
this.passwordEncoder = passwordEncoder;
this.jwtFilter = new JwtFilter(jwtProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.httpBasic().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/auth/**").permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
protected DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
}
And my security filter:
// ... imports
public class JwtFilter extends GenericFilterBean {
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
public static final int TOKEN_START_POSITION = 7;
private final JwtProvider jwtProvider;
#Autowired
public JwtFilter(JwtProvider jwtProvider) {
this.jwtProvider = jwtProvider;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
String token = getTokenFromRequest((HttpServletRequest) servletRequest);
if (token != null && jwtProvider.validateToken(token)) {
Map<String, Object> properties = jwtProvider.getUserPropertiesFromToken(token);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
properties.get("login"),
null,
(Set<GrantedAuthority>) properties.get("authirities"));
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(servletRequest, servletResponse);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearer = request.getHeader(AUTHORIZATION_HEADER);
if (bearer != null && bearer.startsWith(TOKEN_PREFIX)) {
return bearer.substring(TOKEN_START_POSITION);
}
return null;
}
}
The reason my code didn't work for me was that I skipped the line filterChain.doFilter(servletRequest, servletResponse); in my filter, i.e. I didn't pass on the request and response to the next entity in the chain.

Handle spring security authentication exceptions with #ExceptionHandler

I'm using Spring MVC's #ControllerAdvice and #ExceptionHandler to handle all the exception of a REST Api. It works fine for exceptions thrown by web mvc controllers but it does not work for exceptions thrown by spring security custom filters because they run before the controller methods are invoked.
I have a custom spring security filter that does a token based auth:
public class AegisAuthenticationFilter extends GenericFilterBean {
...
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
try {
...
} catch(AuthenticationException authenticationException) {
SecurityContextHolder.clearContext();
authenticationEntryPoint.commence(request, response, authenticationException);
}
}
}
With this custom entry point:
#Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
}
}
And with this class to handle exceptions globally:
#ControllerAdvice
public class RestEntityResponseExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({ InvalidTokenException.class, AuthenticationException.class })
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
#ResponseBody
public RestError handleAuthenticationException(Exception ex) {
int errorCode = AegisErrorCode.GenericAuthenticationError;
if(ex instanceof AegisException) {
errorCode = ((AegisException)ex).getCode();
}
RestError re = new RestError(
HttpStatus.UNAUTHORIZED,
errorCode,
"...",
ex.getMessage());
return re;
}
}
What I need to do is to return a detailed JSON body even for spring security AuthenticationException. Is there a way make spring security AuthenticationEntryPoint and spring mvc #ExceptionHandler work together?
I'm using spring security 3.1.4 and spring mvc 3.2.4.
Ok, I tried as suggested writing the json myself from the AuthenticationEntryPoint and it works.
Just for testing I changed the AutenticationEntryPoint by removing response.sendError
#Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getOutputStream().println("{ \"error\": \"" + authenticationException.getMessage() + "\" }");
}
}
In this way you can send custom json data along with the 401 unauthorized even if you are using Spring Security AuthenticationEntryPoint.
Obviously you would not build the json as I did for testing purposes but you would serialize some class instance.
In Spring Boot, you should add it to http.authenticationEntryPoint() part of SecurityConfiguration file.
The best way I've found is to delegate the exception to the HandlerExceptionResolver
#Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Autowired
private HandlerExceptionResolver resolver;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
resolver.resolveException(request, response, null, exception);
}
}
then you can use #ExceptionHandler to format the response the way you want.
This is a very interesting problem that Spring Security and Spring Web framework is not quite consistent in the way they handle the response. I believe it has to natively support error message handling with MessageConverter in a handy way.
I tried to find an elegant way to inject MessageConverter into Spring Security so that they could catch the exception and return them in a right format according to content negotiation. Still, my solution below is not elegant but at least make use of Spring code.
I assume you know how to include Jackson and JAXB library, otherwise there is no point to proceed. There are 3 Steps in total.
Step 1 - Create a standalone class, storing MessageConverters
This class plays no magic. It simply stores the message converters and a processor RequestResponseBodyMethodProcessor. The magic is inside that processor which will do all the job including content negotiation and converting the response body accordingly.
public class MessageProcessor { // Any name you like
// List of HttpMessageConverter
private List<HttpMessageConverter<?>> messageConverters;
// under org.springframework.web.servlet.mvc.method.annotation
private RequestResponseBodyMethodProcessor processor;
/**
* Below class name are copied from the framework.
* (And yes, they are hard-coded, too)
*/
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder", MessageProcessor.class.getClassLoader());
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", MessageProcessor.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", MessageProcessor.class.getClassLoader());
private static final boolean gsonPresent =
ClassUtils.isPresent("com.google.gson.Gson", MessageProcessor.class.getClassLoader());
public MessageProcessor() {
this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter());
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
}
processor = new RequestResponseBodyMethodProcessor(this.messageConverters);
}
/**
* This method will convert the response body to the desire format.
*/
public void handle(Object returnValue, HttpServletRequest request,
HttpServletResponse response) throws Exception {
ServletWebRequest nativeRequest = new ServletWebRequest(request, response);
processor.handleReturnValue(returnValue, null, new ModelAndViewContainer(), nativeRequest);
}
/**
* #return list of message converters
*/
public List<HttpMessageConverter<?>> getMessageConverters() {
return messageConverters;
}
}
Step 2 - Create AuthenticationEntryPoint
As in many tutorials, this class is essential to implement custom error handling.
public class CustomEntryPoint implements AuthenticationEntryPoint {
// The class from Step 1
private MessageProcessor processor;
public CustomEntryPoint() {
// It is up to you to decide when to instantiate
processor = new MessageProcessor();
}
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
// This object is just like the model class,
// the processor will convert it to appropriate format in response body
CustomExceptionObject returnValue = new CustomExceptionObject();
try {
processor.handle(returnValue, request, response);
} catch (Exception e) {
throw new ServletException();
}
}
}
Step 3 - Register the entry point
As mentioned, I do it with Java Config. I just show the relevant configuration here, there should be other configuration such as session stateless, etc.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(new CustomEntryPoint());
}
}
Try with some authentication fail cases, remember the request header should include Accept : XXX and you should get the exception in JSON, XML or some other formats.
We need to use HandlerExceptionResolver in that case.
#Component
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Autowired
//#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
resolver.resolveException(request, response, null, authException);
}
}
Also, you need to add in the exception handler class to return your object.
#RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(AuthenticationException.class)
public GenericResponseBean handleAuthenticationException(AuthenticationException ex, HttpServletResponse response){
GenericResponseBean genericResponseBean = GenericResponseBean.build(MessageKeys.UNAUTHORIZED);
genericResponseBean.setError(true);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return genericResponseBean;
}
}
may you get an error at the time of running a project because of multiple implementations of HandlerExceptionResolver, In that case you have to add #Qualifier("handlerExceptionResolver") on HandlerExceptionResolver
In case of Spring Boot and #EnableResourceServer, it is relatively easy and convenient to extend ResourceServerConfigurerAdapter instead of WebSecurityConfigurerAdapter in the Java configuration and register a custom AuthenticationEntryPoint by overriding configure(ResourceServerSecurityConfigurer resources) and using resources.authenticationEntryPoint(customAuthEntryPoint()) inside the method.
Something like this:
#Configuration
#EnableResourceServer
public class CommonSecurityConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.authenticationEntryPoint(customAuthEntryPoint());
}
#Bean
public AuthenticationEntryPoint customAuthEntryPoint(){
return new AuthFailureHandler();
}
}
There's also a nice OAuth2AuthenticationEntryPoint that can be extended (since it's not final) and partially re-used while implementing a custom AuthenticationEntryPoint. In particular, it adds "WWW-Authenticate" headers with error-related details.
Hope this will help someone.
Taking answers from #Nicola and #Victor Wing and adding a more standardized way:
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class UnauthorizedErrorAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {
private HttpMessageConverter messageConverter;
#SuppressWarnings("unchecked")
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
MyGenericError error = new MyGenericError();
error.setDescription(exception.getMessage());
ServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
outputMessage.setStatusCode(HttpStatus.UNAUTHORIZED);
messageConverter.write(error, null, outputMessage);
}
public void setMessageConverter(HttpMessageConverter messageConverter) {
this.messageConverter = messageConverter;
}
#Override
public void afterPropertiesSet() throws Exception {
if (messageConverter == null) {
throw new IllegalArgumentException("Property 'messageConverter' is required");
}
}
}
Now, you can inject configured Jackson, Jaxb or whatever you use to convert response bodies on your MVC annotation or XML based configuration with its serializers, deserializers and so on.
Update: If you like and prefer to see the code directly, then I have two examples for you, one using standard Spring Security which is what you are looking for, the other one is using the equivalent of Reactive Web and Reactive Security:
- Normal Web + Jwt Security
- Reactive Jwt
The one that I always use for my JSON based endpoints looks like the following:
#Component
public class JwtAuthEntryPoint implements AuthenticationEntryPoint {
#Autowired
ObjectMapper mapper;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class);
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e)
throws IOException, ServletException {
// Called when the user tries to access an endpoint which requires to be authenticated
// we just return unauthorizaed
logger.error("Unauthorized error. Message - {}", e.getMessage());
ServletServerHttpResponse res = new ServletServerHttpResponse(response);
res.setStatusCode(HttpStatus.UNAUTHORIZED);
res.getServletResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
res.getBody().write(mapper.writeValueAsString(new ErrorResponse("You must authenticated")).getBytes());
}
}
The object mapper becomes a bean once you add the spring web starter, but I prefer to customize it, so here is my implementation for ObjectMapper:
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.modules(new JavaTimeModule());
// for example: Use created_at instead of createdAt
builder.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
// skip null fields
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return builder;
}
The default AuthenticationEntryPoint you set in your WebSecurityConfigurerAdapter class:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// ............
#Autowired
private JwtAuthEntryPoint unauthorizedHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
// .antMatchers("/api/auth**", "/api/login**", "**").permitAll()
.anyRequest().permitAll()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.headers().frameOptions().disable(); // otherwise H2 console is not available
// There are many ways to ways of placing our Filter in a position in the chain
// You can troubleshoot any error enabling debug(see below), it will print the chain of Filters
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
// ..........
}
I was able to handle that by simply overriding the method 'unsuccessfulAuthentication' in my filter. There, I send an error response to the client with the desired HTTP status code.
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
if (failed.getCause() instanceof RecordNotFoundException) {
response.sendError((HttpServletResponse.SC_NOT_FOUND), failed.getMessage());
}
}
Customize the filter, and determine what kind of abnormality, there should be a better method than this
public class ExceptionFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
String msg = "";
try {
filterChain.doFilter(request, response);
} catch (Exception e) {
if (e instanceof JwtException) {
msg = e.getMessage();
}
response.setCharacterEncoding("UTF-8");
response.setContentType(MediaType.APPLICATION_JSON.getType());
response.getWriter().write(JSON.toJSONString(Resp.error(msg)));
return;
}
}
}
If you need a super quick solution, #Christophe Bornet purposed the easiest one.
Create a Bean to send authentication exceptions to an exception resolver.
#Bean(name = "restAuthenticationEntryPoint")
public AuthenticationEntryPoint authenticationEntryPoint(#Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
return (request, response, exception) -> resolver.resolveException(request, response, null, exception);
}
*You may put this bean somewhere inside your existing security config class.
Add an exception handler method to catch the error, so you can return the response and status you want.
#ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<String> handleValidationException(AccessDeniedException e) {
return ResponseEntity.status(401).body("{\"status\":\"FAILED\", \"reason\": \"Unauthorized\"}");
}
*You may put it right near your auth endpoint in the controller.
In ResourceServerConfigurerAdapter class, below code snipped worked for me. http.exceptionHandling().authenticationEntryPoint(new AuthFailureHandler()).and.csrf().. did not work. That's why I wrote it as separate call.
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(new AuthFailureHandler());
http.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/subscribers/**").authenticated()
.antMatchers("/requests/**").authenticated();
}
Implementation of AuthenticationEntryPoint for catching token expiry and missing authorization header.
public class AuthFailureHandler implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e)
throws IOException, ServletException {
httpServletResponse.setContentType("application/json");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
if( e instanceof InsufficientAuthenticationException) {
if( e.getCause() instanceof InvalidTokenException ){
httpServletResponse.getOutputStream().println(
"{ "
+ "\"message\": \"Token has expired\","
+ "\"type\": \"Unauthorized\","
+ "\"status\": 401"
+ "}");
}
}
if( e instanceof AuthenticationCredentialsNotFoundException) {
httpServletResponse.getOutputStream().println(
"{ "
+ "\"message\": \"Missing Authorization Header\","
+ "\"type\": \"Unauthorized\","
+ "\"status\": 401"
+ "}");
}
}
}
I'm using the objectMapper. Every Rest Service is mostly working with json, and in one of your configs you have already configured an object mapper.
Code is written in Kotlin, hopefully it will be ok.
#Bean
fun objectMapper(): ObjectMapper {
val objectMapper = ObjectMapper()
objectMapper.registerModule(JodaModule())
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
return objectMapper
}
class UnauthorizedAuthenticationEntryPoint : BasicAuthenticationEntryPoint() {
#Autowired
lateinit var objectMapper: ObjectMapper
#Throws(IOException::class, ServletException::class)
override fun commence(request: HttpServletRequest, response: HttpServletResponse, authException: AuthenticationException) {
response.addHeader("Content-Type", "application/json")
response.status = HttpServletResponse.SC_UNAUTHORIZED
val responseError = ResponseError(
message = "${authException.message}",
)
objectMapper.writeValue(response.writer, responseError)
}}
You can use objectMapper instead to write the value
ApiError response = new ApiError(HttpStatus.UNAUTHORIZED);
String message = messageSource.getMessage("errors.app.unauthorized", null, httpServletRequest.getLocale());
response.setMessage(message);
httpServletResponse.setContentType("application/json");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
OutputStream out = httpServletResponse.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(out, response);
out.flush();
I just create one class that handle all the exceptions regarding authentication
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
logger.error("Responding with unauthorized error. Message - {}", e.getMessage());
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
}
}

Resources