Secure MVC Controller methods with #Secured annotation - java

Here's my single security config for an API made with SpringBoot:
#Configuration
#EnableWebSecurity
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Autowired
private TokenAuthenticationService tokenAuthenticationService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/static/**").permitAll()
.antMatchers(HttpMethod.POST, "/api/user/registration").permitAll()
.antMatchers(HttpMethod.POST, "/api/user/authentication").permitAll()
.anyRequest().authenticated().and()
.addFilterBefore(new TokenLoginFilter("/api/user/authentication", authenticationManagerBean(), tokenAuthenticationService),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new TokenAuthenticationFilter(tokenAuthenticationService),UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
}
All URLs are mapped to spring web mvc controllers and I would like to manually specify access levels for controllers and their methods like this:
#RestController
#RequestMapping("/api/resource/")
#Secured("ROLE_ANONYMOUS") //as default role
public class ResourceController {
...
#Secured("ROLE_USER")
some_method...
}
But when I perform an /api/resource/* requests as anonymous user the application responses with 403 status code, but I expect the method call. It looks like #Secured annotation does no effect on authorization, and all controller methods allowed for ROLE_USER only.
The TokenAuthenticationFilter does action only if token present, so I guess it has no effect.
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
String token = httpServletRequest.getHeader(TokenAuthenticationService.AUTH_HEADER_NAME);
if (token != null) {
try {
SecurityContextHolder.getContext()
.setAuthentication(tokenAuthenticationService.verifyToken(new TokenAuthentication(token)));
} catch (AuthenticationException e) {
((HttpServletResponse) res).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
chain.doFilter(req, res);
}
Update:
Considering comments below this question I realized that #Secured annotation is the part of global method security concept, and not in general part of web security. Now I have folowing tradeoff:
Use #Secured annotation and have method access level info spread over Controller classes, which may lead to situation of diffucult determining method access level in fufture with API grown.
Keep all method access info in the same place(config), but have to support the equality of #RequestMapping value with urls in config.
Which one would you consider the best approach, or tell please if I missed something?

In order to tell Spring to watch for #Secured annotation, on your Security Config you must add the following:
#EnableGlobalMethodSecurity(securedEnabled = true)

Related

Disabling a filter for only a few paths in spring security

How do I get a filter to apply to every request off the root path except for ones I want to ignore? Here's my example:
I have a Spring Security filter like so:
private static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().antMatcher("/**")
.addFilterBefore(new AuthenticationFilter(), BasicAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) {
web.ignoring().requestMatchers(SecurityServletRequestMatchers.servletIgnoreAuthMatcher());
}
}
This filter populates a CustomApiToken object which contains all of our header information and puts it in the spring security context SecurityContextHolder.getContext().setAuthentication(token) for easy access to the token on the requesting controller.
I'm trying to add springfox to the project, which means I want to disable the filter for the UI and api docs pages.
My original attempt was to add a clause to the method:
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().antMatcher("/**")
.addFilterBefore(new AuthenticationFilter(), BasicAuthenticationFilter.class);
http.requestMatcher(SecurityServletRequestMatchers.servletIgnoreAuthMatcher()).headers() //.servletIgnoreAuthMatchers has all the swagger urls also
.defaultsDisabled()
.disable()
.authorizeRequests()
.anyRequest().authenticated();
}
However I discovered that this only takes the second clause into account due to spring security only accepting the last clause.
I've since tried:
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/**")
.addFilterBefore(new AuthenticationFilter(), BasicAuthenticationFilter.class)
.requestMatcher(SecurityServletRequestMatchers.servletIgnoreAuthMatcher()).headers()
.defaultsDisabled()
.disable()
.authorizeRequests()
.anyRequest().authenticated();
}
But that left the webfilter on the springfox url giving me a missing authentication token error.
I've tried looking around here, and on the internet, but none of the examples have given me an acceptable response yet.
In your custom AuthenticationFilter you can define a RequestMatcher and use it before doing your logic, like so:
public class AuthenticationFilter extends OncePerRequestFilter {
private final RequestMatcher ignoredPaths = new AntPathRequestMatcher("/swagger-ui");
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
if (this.ignoredPaths.matches(request)) {
filterChain.doFilter(request, response);
return;
}
// do your logic
filterChain.doFilter(request, response);
}
}
You can override shouldNotFilter method of OncePerRequestFilter in your custom filter to split your filter and not_filter logic, e.g. like this:
public class AuthenticationFilter extends OncePerRequestFilter {
private final List<AntPathRequestMatcher> excludedMatchers;
public AuthenticationFilter (List<AntPathRequestMatcher> excludedMatchers) {
this.excludedMatchers = excludedMatchers;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// your filter logic here
filterChain.doFilter(request, response);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return excludedMatchers.stream()
.anyMatch(matcher -> matcher.matches(request));
}
}
By default this method always returns false, so a filter is applied to any request unless otherwise specified.
Note, that AntPathRequestMatcher's constructor also can take http method, allowing to create more specific not_filter logic, if you have multiple endpoints with the same path and different request methods, but want to allow a free access only to a specific one.

The auto inject of Principle will be null if I manually set authentication

I'm trying to customize a login process in my own login controller instead of using UsernamePasswordAuthenticationFilter
#PostMapping(value = "/login")
public ResponseEntity<?> login(
HttpServletRequest httpRequest,
#RequestBody AuthenticationRequest authenticationRequest) {
// authentication code here
Authentication authenticate=this.authenticationManager.authenticate(authRequest);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(authentication);
return handlerAuthLogin(httpRequest, result, authorizationRequest);
}
But I can't auto inject Principal in other controllers if I login success as below:
#Controller
public class UsersController {
#RequestMapping(value = "/me")
public string getMyName(Principal principal){
return principal.getName(); // principal is null
}
}
Any guys know why to fix it?
When you execute context.setAuthentication(authentication) the authentication is only valid for the current request. So for the second /me request you need to set the authentication as well.
Therefore you need to authenticate the user on a per-request base. That can be done by implementing a GenericFilterBean:
public class CustomAuthenticationFilter extends GenericFilterBean {
private final AuthenticationManager authenticationManager;
public CustomAuthenticationFilter(
AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
/*
Note that you need to receive the authentication token in different manner now.
Usually headers are used for that.
*/
Authentication authenticate = authenticationManager.authenticate(request.getHeader("authToken"));
SecurityContext context = SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
}
After implementing the filter you need to register it in the servlet container at the position where it is best suited. Spring Security handles the security filters per WebsecutiryConfigurer, so you need to register your filter in the config of the respective configurer of your users.
As an example I put it after ConcurrentSessionFilter:
#Configuration
#Order(1)
public static class UserWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter filter = new PlayerAuthenticationFilter(jwtService,
objectMapper);
http.addFilterAfter(filter, ConcurrentSessionFilter.class);
(...)
}
}
Check out the documentation about filter ordering to find the position best suited for your method.
Update
I wrote a more in-depth blog post about this topic. Fell free to check it out.
#Marcus
Thanks for your clarification, I found the cause for my case.
I wrongly config a
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public WebSecurityConfig() {
super(true); // I disable the default config, so the SecurityContextPersistenceFilter didn't be added by default and all my SecurityContext info is not persistent
}
}

Jwt filter accessed even on permitted urls

I'm working on a spring boot application where I defined my filter to be executed to manage getting and validating the token. So in my Web security class configuration, I managed to do this:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// allow anonymous resource requests
.antMatchers(HttpMethod.GET, "/", "/*.html", "/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
.antMatchers("/auth/**").permitAll()
.antMatchers("/places/public").permitAll()
.anyRequest().authenticated();
// Custom JWT based security filter
httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
The authenticationTokenFilterBean is a method annotated with #Bean that returns an instance of my filter:
public class JwtFilter extends OncePerRequestFilter{
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private JwtService jwtService;
#Value("${jwt.header}")
private String authorizationHeader;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = httpServletRequest.getHeader(authorizationHeader);
String username = jwtService.getUsernameFromToken(token);
if(username!=null && SecurityContextHolder.getContext().getAuthentication() ==null){
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if(jwtService.isTokenValid(token, userDetails)){
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
Since I'm extending OncePerRequestFilter, this filter is invoked only one time per request which is not the case with GenericFilter that need to be executed one time by the servlet and another time with Spring security.
The problem that I have is that the ant matcher described in my configuration class are also intercepted by the filter even if I permit them with the permitAll() method, I tried to override configure(WebSecurity web) method from WebSecurityConfigurerAdapter and ignore them but still didn't work.
How can I configure Spring security to skip my filter for these requests? I already checked this question Spring Security JWT Filter applies on all requests but it has no solution.
Thank you
Still handled inside the filter, but cleaner approach.
You can override shouldNotFilter(HttpServletRequest request) method of OncePerRequestFilter and based on URL patterns return true to skip filtering.
private static List<String> skipFilterUrls = Arrays.asList("/", "/*.html", "/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js");
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return skipFilterUrls.stream().anyMatch(url -> new AntPathRequestMatcher(url).matches(request));
}
Change your WebSecurityConfiguration class, it'll look like:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated();
// Custom JWT based security filter
httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
#Override
public void configure(WebSecurity web) throws Exception {
// Ignore spring security in these paths
web.ignoring().antMatchers("/", "/*.html", "/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js","/auth/**","/places/public");
}
You have to remove the permitAll matchers from HttpSecurity configure method and put it in WebSecurity configure method.
I avoid this problem in this way, you can take for reference:
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
if("/login".equals(req.getServletPath()) && "POST".equalsIgnoreCase(req.getMethod())){
// ......
}
filterChain.doFilter(req, res);
}

Spring webSecurity.ignoring() doesn't ignore custom filter

I have a set a custom authentication filter in my Spring 4 MVC + Security + Boot project. The filter does it's job well and now I want to disable the security for some URI (like /api/**). Here is my configuration:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity.ignoring().antMatchers("/api/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(filter, BasicAuthenticationFilter.class);
}
}
Unfortunately, when I call a resource under /api/... the filter is still chained. I've added println in my filter and it's written to the console on every call. Do you know what's wrong with my configuration?
UPDATE
Filter code:
#Component
public class EAccessAuthenticationFilter extends RequestHeaderAuthenticationFilter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("FILTER");
if(SecurityContextHolder.getContext().getAuthentication() == null){
//Do my authentication stuff
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(user, credential, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
super.doFilter(request, response, chain);
}
#Override
#Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
remove #Component on class EAccessAuthenticationFilter,and like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(new EAccessAuthenticationFilter(), BasicAuthenticationFilter.class);
}
https://github.com/spring-projects/spring-security/issues/3958
I don't have enough reputation to add a comment, but for anyone like me who was looking for a little more of an explanation for kimhom's answer, WebSecurityConfigurerAdapter will tell Spring Security to ignore any filters added through it. The filter was then still being invoked because the #Component (or any flavor of #Bean) annotation told Spring to add the filter (again) outside of the security chain. So while the filter was being ignored in the security chain, it was not being ignored by the other (non-security?) chain.
This solved two weeks of headaches for me. In my case my custom filter needed the Authentication object given by the SecurityContext where it kept coming up as null because the security chain was never executed.
I had the correct configuration to ignore some context path in the web security configuration as below..
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v1/api1").antMatchers("/v1/api2");
}
But I mistakenly had added #PreAuthorize(...) on my controller method and it seems like that method level security was overriding any security configuration set up at the start.
After few tests I realized that in fact my configurations are ok and it's just a comprehension problem. The spring.security.ignored=/api/** doesn't bypass or turn off the filter. In reality every request still pass through my custom filter, but the difference is that Spring Security doesn't mind of the authentication status nor the granted authority coming from the custom filter.
I was wondering that the "ignored" property simply bypass the spring security filters. It sounds like I was totally wrong...
I always found the easiest way to do this is to put this configuration in your application.properties:
security.ignored=/api/**
I think you also need it in the Filter class as well (extends RequestHeaderAuthenticationFilter) i.e.
public class EAccessAuthenticationFilter extends RequestHeaderAuthenticationFilter {
public EAccessAuthenticationFilter() {
super(new RequestMatcher() {
RequestMatcher matcher = new AntPathRequestMatcher("/v1/api1");
return matcher.matches(request);
});
}
}

Spring Security bypass URL or Filter

I have a Spring Boot application that is only exposing a REST API. I need to secure it and I'm using a token-based approach ― specifically JWT.
So far, this is what I have implemented:
//
// The Spring Security configuration class
#EnableGlobalAuthentication
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/login", "/api/logout").permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtFilter(), BasicAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
//
// The JWT filter class to check for the token in the HTTP request (headers)
public final class JwtFilter extends GenericFilterBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws
IOException, ServletException {
final HttpServletRequest req = (HttpServletRequest)request;
final String header = req.getHeader("Authorization");
logger.debug("{} {}", req.getMethod(), req.getRequestURI());
if ((null == header) || !header.startsWith("Bearer ")) {
logger.debug("Missing or invalid Authorization header");
}
try {
// Check the token here; the implementation is not relevant here
/*SecurityContextHolder.getContext()
.setAuthentication(manager.authenticate(new JwtToken(JWTParser.parse(header.substring(7)))));*/
chain.doFilter(request, response);
} catch (final AuthenticationException e) {
SecurityContextHolder.clearContext();
// Do some other stuff here
} catch (final ParseException e) { /* ... */ }
}
}
The issue is that the filter executes correctly for every single URI, but I want to be able to exclude some endpoints from the same set. My API is placed in this context /api/* and I want to exclude, for instance, /api/login and /api/logout.
NOTE: My Spring Boot application.yml file doesn't have settings to enable/modify any security-related features.
Filters will be executed for all the endpoints that are configured through HttpSecurity. If you do not want filters to be applied for certain endpoints, include them in a method that configures WebSecurity. For example,
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/api/login", "/api/logout");
}
Please read this post for more details.
I am doing the same as mentioned in the solution
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and().csrf().ignoringAntMatchers("/api/auth/**").disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers("api/content/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("api/auth/signup");
}
Every signup request is still hitting the doFilterInternal() method which was is a custom method.
While running EnableWebSecurity in debug mode i get :
Security filter chain:
[WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
LogoutFilter
AuthTokenFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor ]
What should i do to solve this ?

Categories

Resources