Deny resource without ROLE in spring - spring

I am not able to deny (403) the resources that are without rules
When I try to access the resource / app1 / b it is not being denied
I'm not wondering where the problem is
CLASS WebSecurityConfiguration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private AwsCognitoJwtAuthenticationFilter awsCognitoJwtAuthenticationFilter;
#Autowired
private JwtAuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/signin", "/signup")
.permitAll()
.anyRequest()
.authenticated().and()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(awsCognitoJwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
CLASS AwsCognitoJwtAuthenticationFilter
#Slf4j
public class AwsCognitoJwtAuthenticationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Authentication authentication;
try {
authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (ForbiddenException e) {
SecurityContextHolder.clearContext();
log.error(e.getMessage());
handleUnauthorizedResponse(response, HttpStatus.FORBIDDEN, e);
return;
} catch (Exception e) {
SecurityContextHolder.clearContext();
log.error(e.getMessage());
handleUnauthorizedResponse(response, HttpStatus.UNAUTHORIZED, e);
return;
}
filterChain.doFilter(request, response);
}
.
.
.
}
CLASS AppResource
The resource "/app1/b" is not being denied
#RestController
#RequestMapping("/app1")
public class AppResource {
#GetMapping
#RolesAllowed({"ROLE_ADMIN"})
public Map all() {
return Collections.singletonMap("nome", "Mauricio");
}
#GetMapping("/a")
#RolesAllowed("XXS")
public Map all2() {
return Collections.singletonMap("nome", "XXX");
}
#GetMapping("/b")
public Map noAuth() {
return Collections.singletonMap("nome", "no auth");
}
}

This seems like expected behavior.
.anyRequest()
.authenticated()
These two lines allow access to any of your endpoints, as long as the user is authenticated.
What other behavior would you like?

Related

SpringBoot different auths (MS AD & JWT) to one Controller

I tried to implement small API Gateway for my Mobile App on Spring Boot.
In my architecture i uses MS Active Directory Server for auth staff of company and in future will sms verify code for clients company for sending JWT.
I'm not use layer DAO, UsersRepository and DB connect.
All HTTP requests sending via RestTemplate from Services layer to our inthernal CRM-system.
I implements LDAP AD auth is very simple HttpBasic configuration bellow:
#Configuration
#EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf()
.disable()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/v1/send/**").permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider = new
ActiveDirectoryLdapAuthenticationProvider("mydomain.com", "ldap://192.168.0.100:389/");
activeDirectoryLdapAuthenticationProvider.setConvertSubErrorCodesToExceptions(true);
activeDirectoryLdapAuthenticationProvider.setUseAuthenticationRequestCredentials(true);
activeDirectoryLdapAuthenticationProvider.setSearchFilter("(&(objectClass=user)(userPrincipalName={0})(memberOf=CN=mobileaccess,OU=User Groups,OU=DomainAccountsUsers,DC=MYDOMAIN,DC=COM))");
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider);
auth.eraseCredentials(true);
}
}
I have two RestController V1 and V2 for example:
#RequestMapping("api/v1")
//get token for staff (AD user) HttpBasic auth
#PostMapping("auth/get/stafftoken")
public ResponseEntity<?> getToken() {
// some code...
HttpHeaders tokenHeaders = new HttpHeaders();
tokenHeaders.setBearerAuth(tokenAuthenticationService.getToken());
return new ResponseEntity<>(tokenHeaders, HttpStatus.OK);
}
//get JWT if code from sms == code in my CRM-system (for client) not auth - permitAll
#PostMapping("send/clienttoken")
public #ResponseStatus
ResponseEntity<?> sendVerifyCode(#RequestParam("verifycode") String verifycode) {
// some code...
HttpHeaders tokenHeaders = new HttpHeaders();
tokenHeaders.setBearerAuth(tokenAuthenticationService.getToken());
return new ResponseEntity<>(tokenHeaders, HttpStatus.OK);
}
#RequestMapping("api/v2")
#GetMapping("get/contract/{number:[0-9]{6}")
public Contract getContract(#PathVariable String number) {
return contractsService.getContract(number);
}
How to implements Bearer Auth requests to Controller APIv2 with JWT tokens (clients and staff)?
I think this is implemented through filter chain?
So guys
If you implements multi authentification as in my example, first of all create utility class for builds token and validation users JWT. This is standard code, for example:
public static String createUserToken(Authentication authentication) {
return Jwts.builder()
.setSubject(authentication.getName())
.claim(authentication.getAuthorities())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SIGN_KEY)
.compact();
}
public static Authentication getAuthentication(HttpServletRequest request) {
String token = extractJwt(request);
try {
if (token != null) {
Claims claims = Jwts.parser().setSigningKey(SIGN_KEY).parseClaimsJws(token).getBody();
String username = Jwts.parser().setSigningKey(SIGN_KEY).parseClaimsJws(token).getBody().getSubject();
return username != null ? new UsernamePasswordAuthenticationToken(username, "", Collections.EMPTY_LIST) : null;
}
} catch (ExpiredJwtException e) {
}
return null;
}
Аfter you should create two filters:
LoginAuthentificationFilter extends BasicAuthenticationFilter
JwtAuthenticationFilter extends GenericFilterBean
Code example below
public class LoginAuthentificationFilter extends BasicAuthenticationFilter {
public LoginAuthentificationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
super.doFilterInternal(request, response, chain);
}
}
public class JwtAuthenticationFilter extends GenericFilterBean {
private RequestMatcher requestMatcher;
public JwtAuthenticationFilter(String path) {
this.requestMatcher = new AntPathRequestMatcher(path);
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (!requiresAuthentication((HttpServletRequest) servletRequest)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
Authentication authentication = JwtUtils.getAuthentication((HttpServletRequest) servletRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(servletRequest, servletResponse);
}
private boolean requiresAuthentication(HttpServletRequest request) {
return requestMatcher.matches(request);
}
}
And at the end
Settings WebSecurityConfigurerAdapter
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/v1/noauth_endpoints").permitAll()
.anyRequest()
.authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterAt(jwtFilter(), BasicAuthenticationFilter.class)
.addFilter(loginFilter());
http.headers().cacheControl();
}
Beans
#Bean
public LoginAuthentificationFilter loginFilter() {
return new LoginAuthentificationFilter(authenticationManager());
}
#Bean
public JwtAuthenticationFilter jwtFilter() {
return new JwtAuthenticationFilter("/api/v2/**");
}
#Bean
#Override
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("bzaimy.com", "ldap://192.168.0.100:389/");
provider.setSearchFilter("(&(objectClass=user)(userPrincipalName={0})(memberOf=CN=mobileaccess,OU=User Groups,OU=DomainAccountsUsers,DC=MYDOMAIN,DC=COM))");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}

URL access denying when implementing the Spring Security for URL authentication

I am trying to implement URL authentication before it giving the response through business logic. For that I am using the authentication provider from Spring Security and trying to do one simple demo for testing authenticationProvider working properly. After this I am going to modify by adding my business logic.
My security config file SecurityConfig.java like the following,
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.anyRequest()
.authenticated()
.and().httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.authenticationProvider(authenticationProvider);
}
}
And My CustomAuthenticationProvider.java implementation like the following,
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider
{
#SuppressWarnings("unused")
#Override
public Authentication authenticate(Authentication authToken) throws AuthenticationException {
String userToken = (String) authToken.getName();
String responseString = "test";
String password = "test";
if(responseString.equals(userToken)) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userToken, password);
return auth;
}
else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
And my TestSecurity.java like the following,
#RestController
public class TestSecurity {
#GetMapping("/security/load")
public String LoadSecureUsers() {
return "hello spring security";
}
}
When I am calling the URL localhost:8585/security/load with headers authToken: "test" from POSTMAN application, I am getting the following,
{
"timestamp": "2019-10-30T07:24:25.165+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/security/load"
}
If the condition are satisfying in IF, then how the URL is not able to access? Did I make any mistake in authentication Provider implementation?
Instead of AuthenticationProvider use filter to process the request. This code might help you:
public class ApplicationAuthFilter extends BasicAuthenticationFilter {
public ApplicationAuthFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = String bearerToken = req.getHeader("accessToken");
String username = "test";
String password = "test"
if (username != null && !username.isEmpty()) {
return new UsernamePasswordAuthenticationToken(username, null, authorities);
}
return null;
}
}
And your security config file like this:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new ApplicationAuthFilter(authenticationManager()))
}
}
Basically you need to read the header information which you are passing with request and based on that you have to take action.
Hope this helps.

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

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

Handling OPTIONS and CORS when using a sign in filter instead of controller

I've got an AbstractAuthenticationProcessingFilter that I'm using to handle POST requests at path /sign-in. CORS preflight requests are coming back 404 because there is no path that matches. This makes sense to me.
What I would like to know is if there is a way to inform Spring that there is a filter handling the POST (rather than a controller), so that Spring can dispatch the OPTIONS in the same way it would if a controller were handling the POST. Would it be bad practice to write a controller with one PostMapping? I'm not sure how that would behave since technically the filter handles the POST.
Thanks for your help!
Update
Here's my setup. I originally posted from my phone so wasn't able to add these details then. See below. To reiterate, there is no controller for /sign-in. The POST is handled by the JwtSignInFilter.
CORS Config
#EnableWebMvc
#Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // TODO: Lock this down before deploying
.allowedHeaders("*")
.allowedMethods(HttpMethod.GET.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())
.allowCredentials(true);
}
}
Security Config
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public JwtSignInFilter signInFilter() throws Exception {
return new JwtSignInFilter(
new AntPathRequestMatcher("/sign-in", HttpMethod.POST.name()),
authenticationManager()
);
}
#Bean
public JwtAuthenticationFilter authFilter() {
return new JwtAuthenticationFilter();
}
#Autowired
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers(HttpMethod.POST, "/sign-in").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(
signInFilter(),
UsernamePasswordAuthenticationFilter.class
)
.addFilterBefore(
authFilter(),
UsernamePasswordAuthenticationFilter.class
);
}
}
Sign In Filter
public class JwtSignInFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
private TokenAuthenticationService tokenAuthService;
public JwtSignInFilter(RequestMatcher requestMatcher, AuthenticationManager authManager) {
super(requestMatcher);
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException, IOException, ServletException {
SignInRequest creds = new ObjectMapper().readValue(
req.getInputStream(),
SignInRequest.class
);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getEmail(),
creds.getPassword(),
emptyList()
)
);
}
#Override
protected void successfulAuthentication(
HttpServletRequest req,
HttpServletResponse res, FilterChain chain,
Authentication auth) throws IOException, ServletException {
tokenAuthService.addAuthentication(res, auth.getName());
}
}
Authentication Filter
public class JwtAuthenticationFilter extends GenericFilterBean {
#Autowired
private TokenAuthenticationService tokenAuthService;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = tokenAuthService.getAuthentication((HttpServletRequest)request);
SecurityContextHolder
.getContext()
.setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
Alright, finally found out how to fix this. After hours of tinkering and searching, I found that I needed to use a filter-based CORS configuration and then handle CORS preflights (OPTIONS requests) in the sign-in filter by simply returning 200 OK. The CORS filter will then add appropriate headers.
Updated configuration below (note that my CorsConfig is no longer needed, since we have a CORS filter in SecurityConfig, and JwtAuthenticationFilter is the same as before).
Security Config
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*"); // TODO: lock down before deploying
config.addAllowedHeader("*");
config.addExposedHeader(HttpHeaders.AUTHORIZATION);
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
#Bean
public JwtSignInFilter signInFilter() throws Exception {
return new JwtSignInFilter(
new AntPathRequestMatcher("/sign-in"),
authenticationManager()
);
}
#Bean
public JwtAuthenticationFilter authFilter() {
return new JwtAuthenticationFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/sign-in").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(
signInFilter(),
UsernamePasswordAuthenticationFilter.class
)
.addFilterBefore(
authFilter(),
UsernamePasswordAuthenticationFilter.class
);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Autowired
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Sign In Filter
public class JwtSignInFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
private TokenAuthenticationService tokenAuthService;
public JwtSignInFilter(RequestMatcher requestMatcher, AuthenticationManager authManager) {
super(requestMatcher);
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException, IOException, ServletException {
if (CorsUtils.isPreFlightRequest(req)) {
res.setStatus(HttpServletResponse.SC_OK);
return null;
}
if (!req.getMethod().equals(HttpMethod.POST.name())) {
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
return null;
}
SignInRequest creds = new ObjectMapper().readValue(
req.getInputStream(),
SignInRequest.class
);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getEmail(),
creds.getPassword(),
emptyList()
)
);
}
#Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {
tokenAuthService.addAuthentication(res, auth.getName());
}
}

permitAll() requires Authentication

I'm having a go at developing a REST application with Spring and using JWT for authentication.
At the moment, what I'm trying to achieve is:
GET /api/subjects/* should be accessible to all users.
POST /api/subjects/* should only accessible to admin users.
The issue is that for both cases, the JWT filter gets invoked and I get an error response stating the JWT token is missing.
I've implemented my WebSecurityConfig as follows, including a JWT filter to replace the BasicAuthenticationFilter:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
JWTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
JWTAuthenticationProvider jwtAuthenticationProvider;
#Override
public void configure(WebSecurity web) throws Exception {
//web.ignoring().antMatchers(HttpMethod.GET,"/api/subjects/*");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/subjects/*").permitAll()
.antMatchers(HttpMethod.POST, "/api/subjects/*").hasRole(Profile.Role.ADMIN.toString())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterAt(authenticationTokenFilter(), BasicAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
}
public JWTAuthenticationFilter authenticationTokenFilter() {
return new JWTAuthenticationFilter(authenticationManager(), authenticationEntryPoint);
}
public ProviderManager authenticationManager() {
return new ProviderManager(new ArrayList<AuthenticationProvider>(Arrays.asList(jwtAuthenticationProvider)));
}
}
My implementation of JWTAuthenticationFilter is based on the implementation of BasicAuthenticationFilter:
public class JWTAuthenticationFilter extends OncePerRequestFilter {
private static final String JWT_TOKEN_START = "JWT ";
private AuthenticationManager authenticationManager;
private AuthenticationEntryPoint authenticationEntryPoint;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
Assert.notNull(authenticationManager, "Authentication Manager must not be null");
Assert.notNull(authenticationEntryPoint, "Authentication Entry point must not be null");
this.authenticationManager = authenticationManager;
this.authenticationEntryPoint = authenticationEntryPoint;
}
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
String header = httpServletRequest.getHeader("Authorization");
if (header == null || !header.startsWith(JWT_TOKEN_START)) {
throw new IllegalStateException("Header does not contain: \"Authorization\":\"JWT <token>\". Value: "+header);
}
try {
String jwt = header.substring(JWT_TOKEN_START.length()).trim().replace("<", "").replace(">", "");
JWTAuthenticationToken jwtAuthenticationToken = new JWTAuthenticationToken(jwt);
this.authenticationManager.authenticate(jwtAuthenticationToken);
filterChain.doFilter(httpServletRequest, httpServletResponse);
} catch (AuthenticationException auth) {
SecurityContextHolder.clearContext();
this.authenticationEntryPoint.commence(httpServletRequest, httpServletResponse, auth);
}
}
}
What is causing this issue?

Resources