I am trying to configure spring security with two distinct filters.
What i want is to have some URLs which will be processed by one filter, and some URLs which will be processed by other filter.
This is the setup i came up with :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled=true)
public class SecurityConfigurationContext {
#Order(1)
#Configuration
public static class ConfigurerAdapter1 extends WebSecurityConfigurerAdapter{
...
#Override
protected void configure(final HttpSecurity http) throws Exception {
// Handlers and entry points
http.exceptionHandling()
.authenticationEntryPoint(MyCustomAuthenticationEntryPoint);
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/filter1-urls/*").hasRole("USER");
http.addFilterBefore(myCustomFilter1, ChannelProcessingFilter.class);
http.csrf().disable();
http.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myCustomAuthenticationService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
#Order(2)
#Configuration
public static class ConfigurerAdapter2 extends WebSecurityConfigurerAdapter{
...
#Override
protected void configure(final HttpSecurity http) throws Exception {
// Handlers and entry points
http.exceptionHandling()
.authenticationEntryPoint(MyCustomAuthenticationEntryPoint);
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/filter2-urls/*").hasRole("SUPER_USER");
http.addFilterBefore(myCustomFilter2, ChannelProcessingFilter.class);
http.csrf().disable();
http.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myCustomAuthenticationService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
}
And filters look like this :
Filter1 :
#Component
#Order(1)
public final class MyCustomFilter1 implements Filter{
public MyCustomFilter1() {
super();
}
#Override
public final void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
// logic for processing filter1-urls
}
}
Filter2 :
#Component
#Order(2)
public final class MyCustomFilter2 implements Filter{
public MyCustomFilter2() {
super();
}
#Override
public final void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
// logic for processing filter2-urls
}
}
The problem is that both of these filters are invoked in a chain for every request. Any request i make, it first passes through one filter and then the other, rather then just through one.
How do i fix this ?
Thanks in advance.
You should have the only one ConfigurerAdapter. You can combine it into one configuration:
http.exceptionHandling()
.authenticationEntryPoint(MyCustomAuthenticationEntryPoint);
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/filter1-urls/*").hasRole("USER")
.antMatchers(HttpMethod.GET, "/filter2-urls/*").hasRole("SUPER_USER");
http.addFilterBefore(myCustomFilter, ChannelProcessingFilter.class);
Both of filters will be invoked because you set such behavior. You should left the only one filter to achieve the result you want.
#Component
public final class MyCustomFilter implements Filter {
public MyCustomFilter() {
super();
}
#Override
public final void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
// logic for processing filter2-urls and filter1-urls
}
}
Anyway your filters will be invoked in a chain. If you want to split your filters you should add if statement in your filters to filter only needed url (you can get request url from ServletRequest
#Component
public final class MyCustomFilter2 implements Filter {
public MyCustomFilter2() {
super();
}
#Override
public final void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
if (/*is filter2 urls*/) {
// logic for processing filter2-urls
}
}
}
Related
Is it possible not to redirect to a view without query parameters? I have a login view and I want to redirect to login?error url in browser when login fails. I have my own AuthenticationFailureHandler:
public class SomeCustomHandler implements AuthenticationFailureHandler {
private final SimpleUrlAuthenticationFailureHandler authenticationFailureHandler = new SimpleUrlAuthenticationFailureHandler("/login?error");
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
authenticationFailureHandler.onAuthenticationFailure(request, response, exception);
}
}
configured in WebSecurityConfigurerAdapter:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.failureHandler(new SomeCustomHandler())
.permitAll();
}
But when login fails, returned login?error is redirected in user to login ignoring ?error parameter.
Here is my MvcWebConfigurer:
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login/**").setViewName("login");
}
I think you can try to do something like that:
public class SomeCustomHandler implements AuthenticationFailureHandler {
private final SimpleUrlAuthenticationFailureHandler authenticationFailureHandler = new SimpleUrlAuthenticationFailureHandler("/login?error");
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
if(request.getPathInfo().startsWith("/login") && !request.getParameterMap().isEmpty()) {
authenticationFailureHandler.setUseForward(true);
}
authenticationFailureHandler.onAuthenticationFailure(request, response, exception);
}
}
I'm writing test for a Rest API controller. This endpoint is accessible without any authorization:
#EnableWebSecurity
#Configuration
#Import(AppConfig.class)
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsRepository accountRepository;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Autowired
private JWTAuthenticationFilter jwtAuthenticationFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated().and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
/*
* Apparently, permitAll() doesn't work for custom filters, therefore we ignore the signup and login endpoints
* here
*/
#Override
public void configure(WebSecurity web)
throws Exception {
web.ignoring()
.antMatchers(HttpMethod.POST, "/login")
.antMatchers(HttpMethod.POST, "/signup");
}
/*
* set user details services and password encoder
*/
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean()).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/* Stopping spring from adding filter by default */
#Bean
public FilterRegistrationBean rolesAuthenticationFilterRegistrationDisable(JWTAuthenticationFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
}
The JWTAuthenticationFilter class:
#Component
public class JWTAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
private UserDetailsService customUserDetailsService;
private static Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
private final static UrlPathHelper urlPathHelper = new UrlPathHelper();
final static String defaultFilterProcessesUrl = "/**";
public JWTAuthenticationFilter() {
super(defaultFilterProcessesUrl);
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl)); //Authentication will only be initiated for the request url matching this pattern
setAuthenticationManager(new NoOpAuthenticationManager());
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
Authentication authentication = AuthenticationService.getAuthentication(request, customUserDetailsService);
return getAuthenticationManager().authenticate(authentication);
}
#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));
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Authentication Failed");
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
super.doFilter(req, res, chain);
}
}
When I make a request (using postman) to 'signup' endpoint it works fine. But when I run the test, it hits doFilter and fails, as it doesn't get authenticated.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class AuthenticationControllerFTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private AuthenticationManager authenticationManager;
#Test
public void testCreate() throws Exception {
Authentication authentication = Mockito.mock(Authentication.class);
Mockito.when(authentication.getName()).thenReturn("DUMMY_USERNAME");
Mockito.when(
authenticationManager.authenticate(Mockito
.any(UsernamePasswordAuthenticationToken.class)))
.thenReturn(authentication);
String exampleUserInfo = "{\"name\":\"Test1234\",\"username\":\"test#test.com\",\"password\":\"Salam12345\"}";
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post("/signup")
.accept(MediaType.APPLICATION_JSON).content(exampleUserInfo)
.contentType(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = result.getResponse();
int status = response.getStatus();
String content = response.getContentAsString();
System.out.println(content);
Assert.assertEquals("http response status is wrong", 200, status);
}
}
Any idea on how to fix this issue ?
The issue was resolved by adding the following code to the test class:
#Autowired
private WebApplicationContext context;
#Autowired
private Filter springSecurityFilterChain;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.addFilters(springSecurityFilterChain).build();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
}
I have a org.springframework.web.filter.GenericFilterBean filter. I would like to throw an exception when user is not authorized and catch this exception with #ControllerAdvice. But it seems that the handler doesn't do that.
Filter method
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
ClientAuthentication auth = (ClientAuthentication) authenticationService.getAuthentication(httpRequest, httpResponse);
SecurityContextHolder.getContext().setAuthentication(auth);
if (auth.isAuthenticated()) {
chain.doFilter(request, response);
} else {
ObjectMapper mapper = new ObjectMapper();
response.setCharacterEncoding("UTF-8");
httpResponse.getWriter().write(mapper.writeValueAsString(auth.getInfo()));
}
}
It works but a disadvantage is that I want to catch exception and render exception message back to the client with respect to Content-Type and Accept HTTP header. This solution renders what I want but into JSON only and my application has to handle XML and JSON.
Exception handler
it works when I throw exceptions from #Controller or #RestController but not from HTTP filter.
#ControllerAdvice
class GlobalExceptionHandler {
private static final Logger LOGGER = LogManager.getLogger(GlobalExceptionHandler.class);
#ExceptionHandler(BadRequestException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public
#ResponseBody
ResponseMessage badRequestException(BadRequestException ex) {
return ex.getResponseMessage();
}
/** rest omitted .... **/
}
Update #1
This is my Spring Security config where AuthFilter filter is set.
#EnableWebSecurity
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Configuration
#Order(1)
public static class ApiSecurity extends WebSecurityConfigurerAdapter {
private static Logger LOG = LogManager.getLogger(ApiSecurity.class);
#Bean
AuthenticationEntryPoint authenticationEntryPoint() {
LOG.debug("authenticationEntryPoint bean");
return new RestAuthenticationEntryPoint();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
.and()
.servletApi()
.and()
.headers().cacheControl();
http.antMatcher(ApiController.API_ROOT + "/**").authorizeRequests().anyRequest().authenticated()
.and()
.addFilterBefore(new AuthFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(ApiController.API_ROOT + "/sandbox/**");
}
}
#Configuration
public static class WebFormSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService defaultUserService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(defaultUserService).passwordEncoder(passwordEncoder);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/public/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin()
.loginPage("/login").failureUrl("/login?error").permitAll().and()
.logout().permitAll();
}
}
}
How can I switch security model in runtime so that
an existing spring security component can produce an Authentication, and
an existing spring security component can validate the Authentication
I think I resolved (2) but cannot quite figure out (1).
Spring Security configuration
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").authenticated().and()
.addFilterBefore(switchingFilter);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(switchingAuthenticationProvider);
}
#Bean
public SwitchingAuthenticationProvider switchingAuthenticationProvider() {
return new SwitchingAuthenticationProvider();
}
#Bean
public SwitchingFilter switchingFilter() {
return new SwitchingFilter();
}
}
The SwitchingAuthenticationProvider is straight forward: simply delegate to some other AuthenticationProvder (i.e., LDAP/OAUTH2 or otherwise)
(inspired by Switching authentication approaches at runtime with Spring Security).
public class SwitchingAuthenticationProvider implements AuthenticationProvider {
private AuthenticationProvider[] authProviders = // ...
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return authProvider[i].authenticate(authentication);
}
}
But what creates the Authentication? As I understand it one option is to let the GenericFilterBean create the Authentication as illustrated below.
public class SwitchingFilter extends GenericFilterBean {
private AuthProviderService authProviders = // ...
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Authentication authentication = authProviders.getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
SecurityContextHolder.getContext().setAuthentication(null);
}
}
... where an AuthProviderService would delegate to something that creates an authentication. But how can I plug it with e.g., the equivalent of HttpSecurity#httpBasic() or HttpSecurity#openIdLogin()?
Bonus question: What's the difference between HttpSecurity#authenticationProvider(..) and AuthenticationManagerBuilder.authenticationProvider(..)?
It appears the Filter is responsible to create the Authentication (not sure if anything else is too).
The AnonymousAuthenticationFilter as an example
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(
createAuthentication((HttpServletRequest) req));
}
Similar I think the SwitchingFilter should be similar to SwitchingAuthenticationProvider
public class SwitchingFilter extends GenericFilterBean {
private Filter[] filters = // ...
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
filters[i].doFilter(request, response, chain);
// do filterChain.doFilter(request, response); ??
}
}
.. for some mechanism of selecting a suitable index i.
I am trying to secure my Spring Rest API with token here is my custom filter
public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger logger = LoggerFactory.getLogger(CustomTokenAuthenticationFilter.class);
public CustomTokenAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl));
setAuthenticationManager(new NoOpAuthenticationManager());
setAuthenticationSuccessHandler(new TokenSimpleUrlAuthenticationSuccessHandler());
}
public final String HEADER_SECURITY_TOKEN = "X-CustomToken";
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String token = request.getHeader(HEADER_SECURITY_TOKEN);
logger.info("token found:"+token);
AbstractAuthenticationToken userAuthenticationToken = authUserByToken(token);
if(userAuthenticationToken == null || userAuthenticationToken.getPrincipal().equals("guest")) throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
return userAuthenticationToken;
}
/**
* authenticate the user based on token
* #return
*/
private AbstractAuthenticationToken authUserByToken(String token) {
if(token==null) {
return null;
}
AbstractAuthenticationToken authToken = new MyToken(token);
try {
return authToken;
} catch (Exception e) {
logger.error("Authenticate user by token error: ", e);
}
return authToken;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
super.doFilter(req, res, chain);
}
}
and here is how I configured it
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
protected AbstractAuthenticationProcessingFilter getFilter() {
return new CustomTokenAuthenticationFilter("/api/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(getFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable();
}
}
If you look at the getFilter(), I have passed "/api/*" as a filter processing url, but I want to configure these urls with HttpSecurity object, some thing as follows
http.authorizeRequests().antMatchers("/", "/rome").permitAll()
.antMatchers("/api/admin", "/api/newUser").access("hasRole('ADMIN')")
.antMatchers("/api/db").access("hasRole('ADMIN') or hasRole('DBA')")
Problem I see is that, the Custom filter requires a String as "filter processing url" but I do not want specify anything. That information should be passed by configuring HttpSecurity object through antMatchers etc.
Is it really possible? if yes how can I achieve that?
I used OncePerRequestFilter.
public class MyAuthenticationFilter extends OncePerRequestFilter {
// private RequestMatcher requestMatcher;
private List<RequestMatcher> includedPathMatchers = new ArrayList<>();
private List<RequestMatcher> excludedPathMatchers = new ArrayList<>();
// implement getters and setters
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// your filter implementation and security logics
}
}
You can treat this class as a normal bean (use #Autowired and so on). Then you just need do register it in your context and inject it in the security chain.
Hope it helps.
This answer will be useful to you. It says to use setter setFilterProcessingURL() available in AbstractAuthenticationProcessingFilter