Accessing UserDetails from Filter (Spring) - java

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;
}
}
}

Related

Adding additional Spring Security method annotations

I'm writing a library that uses Spring Security and method security to check whether a user is licensed to perform a certain operation. This is in addition to the usual role-based security, and this is causing a problem.
The annotations look like they do in this test class:
#RestController
class TestController {
#RolesAllowed("ROLE_USER")
#Licensed("a")
public ResponseEntity<String> a() {
return ResponseEntity.ok("a");
}
#RolesAllowed("ROLE_USER")
#Licensed("b")
public ResponseEntity<String> b() {
return ResponseEntity.ok("b");
}
#RolesAllowed("ROLE_USER")
#Licensed("c")
public ResponseEntity<String> c() {
return ResponseEntity.ok("c");
}
}
Having the annotations processed seems simple enough, because you add a customMethodSecurityDataSource:
#EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
#Configuration
public class LicenceSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new LicensedAnnotationSecurityMetadataSource();
}
// more configurations
}
But the problem is in Spring's implementation:
#Override
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
DefaultCacheKey cacheKey = new DefaultCacheKey(method, targetClass);
synchronized (this.attributeCache) {
Collection<ConfigAttribute> cached = this.attributeCache.get(cacheKey);
// Check for canonical value indicating there is no config attribute,
if (cached != null) {
return cached;
}
// No cached value, so query the sources to find a result
Collection<ConfigAttribute> attributes = null;
for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) {
attributes = s.getAttributes(method, targetClass);
if (attributes != null && !attributes.isEmpty()) {
break;
}
}
// Put it in the cache.
if (attributes == null || attributes.isEmpty()) {
this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
return NULL_CONFIG_ATTRIBUTE;
}
this.logger.debug(LogMessage.format("Caching method [%s] with attributes %s", cacheKey, attributes));
this.attributeCache.put(cacheKey, attributes);
return attributes;
}
My custom metadata source is processed first, and as soon as it finds an annotation that it recognises, it stops processing. Specifically, in this if-block:
if (attributes != null && !attributes.isEmpty()) {
break;
}
The result is that my LicenceDecisionVoter votes to abstain; after all, there could be other annotation processors that check roles. And because there are no more attributes to vote upon, only ACCESS_ABSTAIN is returned, and as per Spring's default and recommended configuration, access is denied. The roles are never checked.
Do I have an alternative, other than to implement scanning for Spring's own annotation processors, like the #Secured and JSR-250 annotations?
Or was the mistake to use Spring Security in the first place for this specific purpose?
As promised, the solution. It was more work than I imagined, and the code may have issues because it is partly copied from Spring, and some of that code looks dodgy (or at least, IntelliJ thinks it does).
The key is to remove the GlobalMethodSecurityConfiguration. Leave that to the application itself. The (auto) configuration class looks like the following:
#EnableConfigurationProperties(LicenceProperties.class)
#Configuration
#Import(LicensedMetadataSourceAdvisorRegistrar.class)
public class LicenceAutoConfiguration {
#Bean public <T extends Licence> LicenceChecker<T> licenceChecker(
#Lazy #Autowired final LicenceProperties properties,
#Lazy #Autowired final LicenceFactory<T> factory
) throws InvalidSignatureException, LicenceExpiredException, WrappedApiException,
IOException, ParseException, InvalidKeySpecException {
final LicenceLoader loader = new LicenceLoader(factory.getPublicKey());
final T licence = loader.load(properties.getLicenceFile(), factory.getType());
return factory.getChecker(licence);
}
#Bean MethodSecurityInterceptor licenceSecurityInterceptor(
final LicensedMetadataSource metadataSource,
final LicenceChecker<?> licenceChecker
) {
final MethodSecurityInterceptor interceptor = new MethodSecurityInterceptor();
interceptor.setAccessDecisionManager(decisionManager(licenceChecker));
interceptor.setSecurityMetadataSource(metadataSource);
return interceptor;
}
#Bean LicenceAccessDecisionManager decisionManager(#Autowired final LicenceChecker<?> licenceChecker) {
return new LicenceAccessDecisionManager(licenceChecker);
}
#Bean LicensedMetadataSource licensedMetadataSource() {
return new LicensedMetadataSource();
}
}
The registrar:
public class LicensedMetadataSourceAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
#Override
public void registerBeanDefinitions(final AnnotationMetadata importingClassMetadata,
final BeanDefinitionRegistry registry) {
final BeanDefinitionBuilder advisor = BeanDefinitionBuilder
.rootBeanDefinition(LicensedMetadataSourceAdvisor.class);
advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisor.addConstructorArgReference("licensedMetadataSource");
registry.registerBeanDefinition("licensedMetadataSourceAdvisor", advisor.getBeanDefinition());
}
}
And finally, the advisor:
public class LicensedMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
private final LicenceMetadataSourcePointcut pointcut = new LicenceMetadataSourcePointcut();
private transient LicensedMetadataSource attributeSource;
private transient BeanFactory beanFactory;
private transient MethodInterceptor interceptor;
private transient volatile Object adviceMonitor = new Object();
public LicensedMetadataSourceAdvisor(final LicensedMetadataSource attributeSource) {
this.attributeSource = attributeSource;
}
#Override public void setBeanFactory(final BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override public Pointcut getPointcut() {
return pointcut;
}
#Override public Advice getAdvice() {
synchronized (this.adviceMonitor) {
if (this.interceptor == null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");
this.interceptor = this.beanFactory.getBean("licenceSecurityInterceptor", MethodInterceptor.class);
}
return this.interceptor;
}
}
class LicenceMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
#Override public boolean matches(final Method method, final Class<?> targetClass) {
final LicensedMetadataSource source = LicensedMetadataSourceAdvisor.this.attributeSource;
final Collection<ConfigAttribute> attributes = source.getAttributes(method, targetClass);
return attributes != null && !attributes.isEmpty();
}
}
}
The latter two classes are copied and modified from Spring. The advisor was copied from MethodSecurityMetadataSourceAdvisor, and that's a class that somebody at Spring might have a look at, because of the transient volatile synchronisation object (which I copied, because I can't yet establish if it should be final instead), and because it has a private method that is never used.

Spring: set User as parameter in "all" controllers and controller-functions (instead of Principal)

In a simple Web Application I could retrieve a user (from session attributes) and set it as a parameter for all servlets, using a filter:
Inside Filter:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
checkUser((HttpServletRequest) request);
chain.doFilter(request, response);
}
private void checkUser(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
request.setAttribute("user", session.getAttribute("user"));
}
}
Then I could use it in my JSP files (<c:if test="${not empty user}"> blablabla):
${user.getDisplayName}
In a Spring application I have to Inject principal in every Controller function. Even in that case a Principal is not a User, so I need to use the UserService every time:
#Controller
#RequestMapping("/")
public class IndexController {
private final UserService userService;
#Autowired
IndexController(UserService userService) {
this.userService = userService;
}
private static final String VIEW = "index";
#RequestMapping(method = RequestMethod.GET)
ModelAndView index(Principal principal) {
User user = null;
if (principal != null) {
user = userService.findByUsername(principal.getName());
}
return new ModelAndView(VIEW, "user", user);
}
}
I need the user (entity class) object because it has different functions I use. E.g. getDisplayName() which I use for my navbar (on every page):
User class:
public String getDisplayName() {
if (firstname == null && lastname == null) {
return username;
}
if (firstname != null && lastname != null) {
return String.format("%s %s", firstname, lastname);
}
if (firstname != null) {
return firstname;
}
return lastname;
}
I cannot use this specific user-function (getDisplayName) in Spring Framework using Security tag library functions:
<security:authentication property="name"/> <%-- So, this is not what I am looking for --%>
Ok, I even tried to make a custom interceptor and register it (what I don't want to do, because I have to read my user every time from database again). But then I have problems with Autowiring the UserService inside an interceptor.
What's the best way to solve this problem? Is it possible to find the User from database (findByUsername) once after a successful login, then put it in a session, then make some kind of interceptor that can do the same I did with filters before or retrieve user every time from database for every page but avoid this devious repeting code?
You can either provide a login success handler like
http.formLogin().successHandler(new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest arg0,
HttpServletResponse arg1, Authentication arg2) throws IOException,
ServletException {
// here you can put your logic to save User object into session then forward/redirect to where ever you want
}
})
Or alternatively you can forward a login success to some end point like
http.formLogin().successForwardUrl("/loginsuccess")
and provide endpont implementation like
#RequestMapping({ "/loginsuccess" })
public ResponseEntity<?> loginSuccess(Principal user) {
// here you can put your logic to save User object into session then forward/redirect to where ever you want
return "";
}
Choose whichever way you find more convenient.
Solved:
I made a custom interceptor, injected an autowired User Service into it, and registered that custom interceptor. It works!
public class MyWebConfig extends WebMvcConfigurerAdapter {
#Bean
public UsernameInjectionInterceptor usernameInjectionInterceptor() {
return new UsernameInjectionInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(usernameInjectionInterceptor());
}
}
public class UsernameInjectionInterceptor extends HandlerInterceptorAdapter {
#Autowired
private UserService userService;
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
Principal principal = request.getUserPrincipal();
if (principal != null) {
HttpSession session = request.getSession(false);
if (session != null) {
String displayName = (String) session.getAttribute("displayName");
if (displayName == null) {
User user = userService.findByUsername(principal.getName());
if (user != null) {
displayName = user.getDisplayName();
session.setAttribute("displayName", displayName);
}
}
modelAndView.addObject("displayName", displayName);
}
}
}
}

Getting user info in java RESTful resource

I have a Rest based service using a ContianerRequestFilter (AuthFilter below) to validate a user or their token. Everything at that level works fine as the user is authorized or not authorized as expected. The question is how to do get the user info in the resource layer? For instance if a user requests a list of areas in AreasResource (below), how can I get the user info and use that to constrain the results return to him/her?
AuthFilter:
#Provider
#PreMatching
public class AuthFilter implements ContainerRequestFilter
{
#Autowired
IAuthenticator authenticator;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
//PUT, POST, GET, DELETE...
String method = requestContext.getMethod();
String path = requestContext.getUriInfo().getPath(true);
UserWrapper authenticationResult = null;
Date expireTime = new Date(new Date().getTime() + 60 * 1000);
if (!"init".equals(path))
{
if ("GET".equals(method) && ("application.wadl".equals(path) || "application.wadl/xsd0.xsd".equals(path)))
{
return;
}
String auth = requestContext.getHeaderString("authorization");
if(auth == null)
{
throw new WebApplicationException(Status.UNAUTHORIZED);
}
if (auth.startsWith("Bearer"))
{
String token = auth.substring("Bearer".length()).trim();
try
{
authenticationResult = validateToken(token);
}
catch (Exception e)
{
throw new WebApplicationException(Status.UNAUTHORIZED);
}
}
else
{
//lap: loginAndPassword
String[] lap = BasicAuth.decode(auth);
if (lap == null || lap.length != 2)
{
throw new WebApplicationException(Status.UNAUTHORIZED);
}
// Handle authentication validation here
authenticationResult = authenticator.authenticatUser(lap);
// if null then user can't be found or user name and password failed
if (authenticationResult == null)
{
throw new WebApplicationException(Status.UNAUTHORIZED);
}
}
}
else
{
authenticationResult = new UserWrapper(new User(), expireTime.getTime());
}
// We passed so we put the user in the security context here
String scheme = requestContext.getUriInfo().getRequestUri().getScheme();
requestContext.setSecurityContext(new ApplicationSecurityContext(authenticationResult, scheme));
}
private UserWrapper validateToken(String token) throws Exception
{
UserWrapper userWrapper = AuthenticatorCache.getInstance().getObj(token);
if (userWrapper == null)
{
throw new Exception("No session found");
}
return userWrapper;
}
}
Areas Resource:
#Path("/areas")
#Component
#Api(value = "/areas" )
public class AreasResource implements IAreas
{
#Override
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response listActiveAreas() {
return Response.ok('woo hoo it worked').build();
}
}
Overriding the SecurityContext
One possible way to achieve it is overriding the SecurityContext of the ContainerRequestContext in your ContainerRequestFilter implementation. It could be something as following:
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return username;
}
};
}
#Override
public boolean isUserInRole(String role) {
return true;
}
#Override
public boolean isSecure() {
return false;
}
#Override
public String getAuthenticationScheme() {
return null;
}
});
Then the SecurityContext can be injected in any resource class using the #Context annotation:
#Path("/example")
public class MyResource {
#Context
private SecurityContext securityContext;
...
}
It alson can be injected in a resource method parameter:
#GET
#Path("/{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response myResourceMethod(#PathParam("id") Long id,
#Context SecurityContext securityContext) {
...
}
And then get the Principal from the SecurityContext:
Principal principal = securityContext.getUserPrincipal();
String username = principal.getName();
I have initially described this approach in this answer.
Alternatives
If you don't want to override the SecurityContext for some reason, you could consider other approaches, depending on what you have available in your application:
With CDI, you could create a bean annotated with #RequestScoped to hold the name of the authenticated user. After performing the authentication, set the name of the user in the request scoped bean and inject it into your resource classes using #Inject.
Since you are using Spring, you could consider using Spring Security on the top of your JAX-RS application for authentication and authorization.
I guess you need to add your SecurityContext as parameter to the method annotated as #Context like that:
#Override
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response listActiveAreas(#Context SecurityContext securityCtx) {
// Do something with data in securityCtx...
return Response.ok("woo hoo it worked").build();
}
If it will not work (I did not try it and use other way):
You may set your securityContext as HttpRequest/HttpServletRequest or (Session) attribute with some name (as example user.security.ctx) and inject Request same way. i.e.
#Override
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response listActiveAreas(#Context HttpServletRequest request) {
SecurityContext securityCtx = (SecurityContext )request.getAttribute("user.security.ctx");
// Do something with data in securityCtx...
return Response.ok("woo hoo it worked").build();
}

Combine Dynamic datasource routing with spring-data-rest

I'm using Dynamic datasource routing as indicated in this blog post:
http://spring.io/blog/2007/01/23/dynamic-datasource-routing/
This works fine, but when I combine it with spring-data-rest and browsing of my generated repositories I (rightfully) get an exception that my lookup-key is not defined (I do not set a default).
How and where can I hook into the Spring data rest request handling to set the lookup-key based on 'x' (user authorizations, path prefix, or other), before any connection is made to the database?
Code-wise my datasource configuration just mostly matches the blogpost at the top, with some basic entity classes, generated repositories and Spring Boot to wrap everything together. If need I could post some code, but there's nothing much to see there.
My first idea is to leverage Spring Security's authentication object to set current datasource based on authorities attached to the authentication.
Of course, you can put the lookup key in a custom UserDetails object or even a custom Authentication object, too. For sake of brevity I`ll concentrate on a solution based on authorities.
This solution requires a valid authentication object (anonymous user can have a valid authentication, too). Depending on your Spring Security configuration changing authority/datasource can be accomplished on a per request or session basis.
My second idea is to work with a javax.servlet.Filter to set lookup key in a thread local variable before Spring Data Rest kicks in. This solution is framework independent and can be used on a per request or session basis.
Datasource routing with Spring Security
Use SecurityContextHolder to access current authentication's authorities. Based on the authorities decide which datasource to use.
Just as your code I'm not setting a defaultTargetDataSource on my AbstractRoutingDataSource.
public class CustomRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
Set<String> authorities = getAuthoritiesOfCurrentUser();
if(authorities.contains("ROLE_TENANT1")) {
return "TENANT1";
}
return "TENANT2";
}
private Set<String> getAuthoritiesOfCurrentUser() {
if(SecurityContextHolder.getContext().getAuthentication() == null) {
return Collections.emptySet();
}
Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
return AuthorityUtils.authorityListToSet(authorities);
}
}
In your code you must replace the in memory UserDetailsService (inMemoryAuthentication) with a UserDetailsService that serves your need.
It shows you that there are two different users with different roles TENANT1 and TENANT2 used for the datasource routing.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user1").password("user1").roles("USER", "TENANT1")
.and()
.withUser("user2").password("user2").roles("USER", "TENANT2");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/**").hasRole("USER")
.and()
.httpBasic()
.and().csrf().disable();
}
}
Here is a complete example: https://github.com/ksokol/spring-sandbox/tree/sdr-routing-datasource-spring-security/spring-data
Datasource routing with javax.servlet.Filter
Create a new filter class and add it to your web.xml or register it with the AbstractAnnotationConfigDispatcherServletInitializer, respectively.
public class TenantFilter implements Filter {
private final Pattern pattern = Pattern.compile(";\\s*tenant\\s*=\\s*(\\w+)");
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String tenant = matchTenantSystemIDToken(httpRequest.getRequestURI());
Tenant.setCurrentTenant(tenant);
try {
chain.doFilter(request, response);
} finally {
Tenant.clearCurrentTenant();
}
}
private String matchTenantSystemIDToken(final String uri) {
final Matcher matcher = pattern.matcher(uri);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
}
Tenant class is a simple wrapper around a static ThreadLocal.
public class Tenant {
private static final ThreadLocal<String> TENANT = new ThreadLocal<>();
public static void setCurrentTenant(String tenant) { TENANT.set(tenant); }
public static String getCurrentTenant() { return TENANT.get(); }
public static void clearCurrentTenant() { TENANT.remove(); }
}
Just as your code I`m not setting a defaultTargetDataSource on my AbstractRoutingDataSource.
public class CustomRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
if(Tenant.getCurrentTenant() == null) {
return "TENANT1";
}
return Tenant.getCurrentTenant().toUpperCase();
}
}
Now you can switch datasource with http://localhost:8080/sandbox/myEntities;tenant=tenant1. Beware that tenant has to be set on every request. Alternatively, you can store the tenant in the HttpSession for subsequent requests.
Here is a complete example: https://github.com/ksokol/spring-sandbox/tree/sdr-routing-datasource-url/spring-data

Spring security custom token filter

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.

Categories

Resources