I'm trying to perform a custom filter to get a token and validate it. I'm following the approach in this response.
This is the relevant configuration:
SecurityConfig:
#Configuration
#EnableWebSecurity
#ComponentScan(basePackages = {"com.company.app"})
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Inject
AuthenticationTokenFilter authenticationTokenFilter;
#Inject
TokenAuthenticationProvider tokenAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(authenticationTokenFilter, BasicAuthenticationFilter.class)
.antMatcher("/*")
.authenticationProvider(tokenAuthenticationProvider)
.authorizeRequests()
.anyRequest().authenticated();
}
}
AuthenticationTokenFilter:
#Component
public class AuthenticationTokenFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(AuthenticationTokenFilter.class);
#Override
public void init(FilterConfig fc) throws ServletException {
logger.info("Init AuthenticationTokenFilter");
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain fc) throws IOException, ServletException {
SecurityContext context = SecurityContextHolder.getContext();
if (context.getAuthentication() != null && context.getAuthentication().isAuthenticated()) {
// do nothing
} else {
Map<String,String[]> params = req.getParameterMap();
if (!params.isEmpty() && params.containsKey("auth_token")) {
String token = params.get("auth_token")[0];
if (token != null) {
Authentication auth = new TokenAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
}
fc.doFilter(req, res);
}
#Override
public void destroy() {
}
}
TokenAuthentication:
public class TokenAuthentication implements Authentication {
private String token;
public TokenAuthentication(String token) {
this.token = token;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return new ArrayList<GrantedAuthority>(0);
}
#Override
public Object getCredentials() {
return token;
}
#Override
public Object getDetails() {
return null;
}
#Override
public Object getPrincipal() {
return null;
}
#Override
public boolean isAuthenticated() {
return false;
}
#Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
#Override
public String getName() {
return null;
}
}
TokenAuthenticationProvider:
#Component
public class TokenAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(TokenAuthenticationProvider.class);
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.isAuthenticated())
return auth;
String token = auth.getCredentials().toString();
User user = userSvc.validateApiAuthenticationToken(token);
if (user != null) {
auth = new PreAuthenticatedAuthenticationToken(user, token);
auth.setAuthenticated(true);
logger.debug("Token authentication. Token: ");
} else
throw new BadCredentialsException("Invalid token " + token);
return auth;
}
#Override
public boolean supports(Class<?> aClass) {
return true;
}
}
But it's like the AuthenticationTokenFilter is not being added to the chain. Debugging I can see that when I do a call it enters to the SecurityConfig and configure method but not to the filter.
What is missing?
try to disable anonymous authentication and change to fully authentication to your security rule.
something like this :
http
.addFilterBefore(authenticationTokenFilter, BasicAuthenticationFilter.class)
.antMatcher("/token")
.authenticationProvider(tokenAuthenticationProvider)
.authorizeUrls().anyRequest().fullyAuthenticated()
.and()
.anonymous().disable()
What you are missing is
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
in your web.xml or equivalent for intializers on your classpath:
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
#Order(value = 1)
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
This is separate from your WebApplicationInitializer. Note that:
your SecurityConfig (or anything annotated with #EnableWebSecurity) must be defined in the root context (not the dispatcher context)
you probably should understand order of initialization (I am not sure if I do):
"Ordering of WebApplicationInitializer"
If any servlet Filter mappings are added after AbstractSecurityWebApplicationInitializer is invoked, they might be accidentally added before springSecurityFilterChain. Unless an application contains Filter instances that do not need to be secured, springSecurityFilterChain should be before any other Filter mappings. The #Order annotation can be used to help ensure that any WebApplicationInitializer is loaded in a deterministic order.
Example:
#Order(value = 10)
public class AppWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { AppConfig.class, SecurityConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { RestConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/rest/*"};
}
}
To summarize, from Spring documentation:
When using servlet filters, you obviously need to declare them in your web.xml, or they will be ignored by the servlet container. In Spring Security, the filter classes are also Spring beans defined in the application context and thus able to take advantage of Spring's rich dependency-injection facilities and lifecycle interfaces. Spring's DelegatingFilterProxy provides the link between web.xml and the application context.
The Security Filter Chain
Old post, but I think authenticationProvider() needs to come BEFORE "addBeforeFilter". Not sure if it will make a difference today, but it might be important. It may not matter as much.
Also try add this on your configuration class to solve the issue:
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
FYI: using both the #Component on the Filter and #Inject with addFilterBefore will apply the filter twice! In your case it is just more processing time, so you wont see any errors. But if you are injecting lets say a metrics filter, then you will be getting wrong metrics.
Related
I need to configure the Spring Security so that users that have either Basic Auth or API Key can access the API in the absence of one other. The below code works for Basic Auth but when I switch to API Key in Postman, I can not access the API using the correct API key/value added to the Header in Postman in the Authorization tab and I am getting 401.
However, when I just use ApiKeyWebSecurityConfigurerAdapter by itself, I can access the API even with the wrong API key/value pair. Any help would be appreciated.
Note: I used this and https://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/html5/#multiple-httpsecurity as reference.
Security Config:
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.userDetailsService(userDetailsService())
.httpBasic(Customizer.withDefaults());
}
#Bean
public UserDetailsService userDetailsService() {
return new JpaUserDetailService();
}
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
#Configuration
public static class ApiKeyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Value("${myApp.http.auth-token-header-name}")
private String principalRequestHeader;
#Value("${myApp.http.auth-token}")
private String principalRequestValue;
#Override
protected void configure(HttpSecurity http) {
http
.csrf()
.disable();
APIKeyAuthFilter filter = new APIKeyAuthFilter(principalRequestHeader);
filter.setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String principal = (String) authentication.getPrincipal();
if (!principalRequestValue.equals(principal)) {
throw new BadCredentialsException("The API key was not found or not the expected value.");
}
authentication.setAuthenticated(true);
return authentication;
}
});
}
}
}
Filter:
public class APIKeyAuthFilter extends AbstractPreAuthenticatedProcessingFilter {
private String principalRequestHeader;
public APIKeyAuthFilter(String principalRequestHeader) {
this.principalRequestHeader = principalRequestHeader;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return request.getHeader(principalRequestHeader);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "N/A";
}
}
Can't figure out how to get my stylesheet to display for the homepage when I added WebApplicationConfig Class and AuthenticationFilter Class. My style she is located at resources/static/css. My current error says net::ERR_TOO_MANY_REDIRECTS...
I have tried adding the following to my WebApplicationConfig, but it didn't work either.
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**")
.addResourceLocations("/static/css/");
}
This is my file structure. I am needing home.css in resources/static/css to be displayed on the home page.
This is my WebApplicationConfig that implements WebMvcConigurer.
#Configuration
public class WebApplicationConfig implements WebMvcConfigurer {
// Create spring-managed object to allow the app to access our filter
#Bean
public AuthenticationFilter authenticationFilter() {
return new AuthenticationFilter();
}
// Register the filter with the Spring container
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationFilter());
}
}
This is my AuthenticationFilter Class that extends HandlerInterceptorAdapter.
public class AuthenticationFilter extends HandlerInterceptorAdapter {
#Autowired
UserRepository userRepository;
#Autowired
AuthenticationController authenticationController;
private static final List<String> whitelist = Arrays.asList("/login", "/register", "/logout", "/", "/css");
private static boolean isWhitelisted(String path) {
for (String pathRoot : whitelist) {
if (path.equals(pathRoot)) {
return true;
}
}
return false;
}
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws IOException {
if (isWhitelisted(request.getRequestURI())) {
// returning true indicates that the request may proceed
return true;
}
HttpSession session = request.getSession();
User user = authenticationController.getUserFromSession(session);
// The user is logged in
if (user != null) {
return true;
}
// The user is NOT logged in
response.sendRedirect("");
return false;
}
}
I am using Spring data rest and directly hitting the end point without a controller. As I wanted to validate the authorization, created an interceptor which validates the request based on some claims in the auth token.
#Configuration
#Order(1)
public class AuthConfig implements WebMvcConfigurer {
#Autowired
AuthInterceptor requestInterceptor;
#Bean
public MappedInterceptor exampleInterceptor() {
return new MappedInterceptor(new String[]{"/api/**"}, requestInterceptor);
}
}
#Component
public class AuthInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//validate authorization
if (authorized) {
return true;
} else {
return false;
}
}
}
this has been working great. Now I want to add other interceptor so that I can intercept one request and do some process. I am doing because this api doesn't have controller/service so intercepting it is what I thought way to do this
#Configuration
#Order(2)
public class ProcessConfiguration implements WebMvcConfigurer {
#Autowired
ProcessInterceptor requestInterceptor;
#Bean
public MappedInterceptor processInterceptor() {
return new MappedInterceptor(new String[]{"/api/entity/*"}, requestInterceptor);
}
}
#Component
public class ProcessInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// do some process
return true;
}
}
So I wanted to invoke auth interceptor first and process interceptor next if request is authorized. And I used #Order to reflect same but it is not working as expected. Is there any other way to do this or am I doing this wrong?
Specification
A filter that can call loadUserFromUsername() via userDetailsService in order to retrieve the tenant DB details from the custom UserDetails instance.
Problem
Regardless of what the filter precedence is set to, this custom filter runs before the security filter, and so the spring security context is unpopulated or null. I've confirmed that this context is populated when I access the principal object from a controller.
Attempts
I've set the spring security order in application.properties to 5, and when registering this filter I've used larger and smaller values, but it always runs before.
I'm aware that the generic filter bean should allow me to set it to come after in security configuration, but I don't know how to move the configuration and filter into one generic filter bean.
TenantFilter.java
#Component
public class TenantFilter implements Filter {
#Autowired
private TenantStore tenantStore;
#Autowired
private UserService userService;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
User user = null;
try {
user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
} catch (UsernameNotFoundException ignored) {}
String tenantId = user != null ? user.getSchool().getCode() : "";
try {
this.tenantStore.setTenantId(tenantId);
chain.doFilter(servletRequest, servletResponse);
} finally {
// Otherwise when a previously used container thread is used, it will have the old tenant id set and
// if for some reason this filter is skipped, tenantStore will hold an unreliable value
this.tenantStore.clear();
}
}
#Override
public void destroy() {
}
}
TenantFilterConfig.java
#Configuration
public class TenantFilterConfig {
#Bean
public Filter tenantFilter() {
return new TenantFilter();
}
#Bean
public FilterRegistrationBean tenantFilterRegistration() {
FilterRegistrationBean result = new FilterRegistrationBean();
result.setFilter(this.tenantFilter());
result.setUrlPatterns(Lists.newArrayList("/*"));
result.setName("Tenant Store Filter");
result.setOrder(Ordered.LOWEST_PRECEDENCE-1);
return result;
}
#Bean(destroyMethod = "destroy")
public ThreadLocalTargetSource threadLocalTenantStore() {
ThreadLocalTargetSource result = new ThreadLocalTargetSource();
result.setTargetBeanName("tenantStore");
return result;
}
#Primary
#Bean(name = "proxiedThreadLocalTargetSource")
public ProxyFactoryBean proxiedThreadLocalTargetSource(ThreadLocalTargetSource threadLocalTargetSource) {
ProxyFactoryBean result = new ProxyFactoryBean();
result.setTargetSource(threadLocalTargetSource);
return result;
}
#Bean(name = "tenantStore")
#Scope(scopeName = "prototype")
public TenantStore tenantStore() {
return new TenantStore();
}
}
Found a different way that works real nicely: Aspects!
The pointcut expression used means that this runs around all method calls from all classes within the controllers package in that project.
The tenant store is based of a safer usage of threadlocal to avoid memory leaks, as this way it is always cleared (due to the finally block)
Happy coding!
TenantAspect.java
#Component
#Aspect
public class TenantAspect {
private final
TenantStore tenantStore;
#Autowired
public TenantAspect(TenantStore tenantStore) {
this.tenantStore = tenantStore;
}
#Around(value = "execution(* com.things.stuff.controller..*(..))")
public Object assignForController(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
return assignTenant(proceedingJoinPoint);
}
private Object assignTenant(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
try {
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (user != null) tenantStore.setTenantId(user.getSchool().getCode());
} finally {
Object retVal;
retVal = proceedingJoinPoint.proceed();
tenantStore.clear();
return retVal;
}
}
}
I already found questions with answers but they don't help me.
I have a web servlet project where I use a Spring Controller (4.2.5) and Spring Security (4.0.2). I don't use Spring Boot.
My project works fine.
But now my task is this:
Make #RequestMapping(value={"auth/**"} configurable (replace "auth/**" with ${dm.filterPattern})
Problem: in #RequestMapping ${dm.filterPattern} isn't resolved, although #PropertySource is processed.
This is the entry dm.filterPattern in dmConfig.properties:
dm.filterPattern=/auth/*
Here is some essential code, with all Spring annotations.
Controller:
The output of the method init() shows me that #PropertySource is processed correctly. env.getProperty("...") returns correct values.
#Controller
#PropertySource("classpath:/dmConfig.properties")
#RequestMapping(value ={ "${dm.filterPattern}"})
public class DmProxyController implements ApplicationContextAware
{
private Environment env;
#Autowired
public DmProxyController(Environment env)
{
this.env = env;
}
#RequestMapping(path={"${dm.filterPattern}"} ,method = RequestMethod.POST)
protected void doPost(HttpServletRequest customerRequest, HttpServletResponse response)
throws ServletException, IOException, DmException
{
// code for POST request
}
#RequestMapping(path={"${dm.filterPattern}"} ,method = RequestMethod.GET)
protected void doGet(HttpServletRequest customerRequest, HttpServletResponse response)
throws ServletException, IOException, DmException
{
// code for GET request
}
#PostConstruct
public void init() throws ServletException
{
RequestMappingHandlerMapping requestMapping=
(RequestMappingHandlerMapping) appContext.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMapping.getHandlerMethods();
logger.debug("RequestMapping via dm.filterPattern: {}",
env.getProperty("dm.filterPattern"));
logger.debug("Handler Methods: {}", handlerMethods.size());
for (RequestMappingInfo mapInfo : handlerMethods.keySet())
{
logger.debug(" Mappinginfo: {} --> {}", mapInfo, handlerMethods.get(mapInfo));
}
}
}
Class with bean definitions
#Configuration
#PropertySource("classpath:/dmConfig.properties")
#ComponentScan(basePackages = "com.dm.filter, com.dm.controller")
#EnableTransactionManagement(mode = AdviceMode.PROXY, proxyTargetClass = false)
#Import({DmSecurityConfigurer.class, DmWebConfigurer.class})
public class DmRoot
{
}
DispatcherServletInitializer
public class DmDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
#Override
protected Class<?>[] getRootConfigClasses()
{ return new Class[]{DmRoot.class}; }
#Override
protected Class<?>[] getServletConfigClasses()
{ return null; }
#Override
protected String[] getServletMappings()
{ return new String[]{"/"}; }
#Override
protected String getServletName()
{ return "dmDispatcherServlet"; }
#Override
protected void customizeRegistration(ServletRegistration.Dynamic registration)
{
super.customizeRegistration(registration);
registration.setLoadOnStartup(1);
}
}
WebConfigurer
public class DmWebConfigurer extends WebMvcConfigurerAdapter
{
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
super.addResourceHandlers(registry);
registry.addResourceHandler("/index.html").addResourceLocations("/");
registry.setOrder(Integer.MAX_VALUE-5);
}
}
SecurityWebApplicationInitializer
public class DmSecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer
{
public DmSecurityWebApplicationInitializer()
{
// some logging
}
#Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext)
{ // adding own filters }
#Override
protected void afterSpringSecurityFilterChain(ServletContext servletContext)
{ // adding own filters }
}
SecurityConfigurer
#EnableWebMvc
#EnableWebSecurity
#PropertySource("classpath:dmConfig.properties")
public class DmSecurityConfigurer extends WebSecurityConfigurerAdapter
{
private static Logger logger = LogManager.getLogger(DmSecurityConfigurer.class.getName());
#Autowired
private Environment env;
#Autowired
private UserDetailsService dmUserDetailsService;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
String urlPattern = env.getProperty("dm.springSecurityPattern");
String realmName = env.getProperty("dm.springSecurityRealm");
httpSecurity.httpBasic().realmName(realmName)
.and().userDetailsService(dmUserDetailsService)
.authorizeRequests()
.antMatchers(urlPattern).authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
}
}
There is a possibility that PropertySourcesPlaceholderConfigurer is being initialised later in the spring context than your controller and hence the values are not resolved. try adding explicit bean definition for PropertySourcesPlaceholderConfigurer in one of the root configuration file as below;
#PropertySource("classpath:/dmConfig.properties")
public class DmWebConfigurer extends WebMvcConfigurerAdapter
{
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
super.addResourceHandlers(registry);
registry.addResourceHandler("/index.html").addResourceLocations("/");
registry.setOrder(Integer.MAX_VALUE-5);
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
The reason you can see the values properly in your init() method is because it is called after all the beans are initialised including PropertySourcesPlaceholderConfigurer.