Spring security: My Authorization filter authorizes my request even tho the URL is permited - spring-boot

In my security configuration class i have permitted the request to the welcome url and any other url which follows the "welcome/**" format.
this is my securityconfiguration class:
#EnableGlobalMethodSecurity(prePostEnabled = true)
//#Configuration
#EnableWebSecurity
public class JwtSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
private final CustomerDetailsService customerDetailsService;
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
public JwtSecurityConfiguration(CustomerDetailsService customerDetailsService) {
this.customerDetailsService = customerDetailsService;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(customerDetailsService)
.passwordEncoder(passwordEncoderBean());
}
#Bean
public PasswordEncoder passwordEncoderBean() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("**/resources/static/**")
.and()
.ignoring()
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/index_assets/**"
);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/welcome/login").permitAll()
.antMatchers("/welcome").permitAll()
.antMatchers("/welcome/signup").permitAll()
.antMatchers("admin/rest/**").authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//http.addFilterBefore(new JWTAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(new JWTAuthorizationFilter(authenticationManager(),customerDetailsService),UsernamePasswordAuthenticationFilter.class);
// disable page caching
http
.headers()
.frameOptions().sameOrigin() // required to set for H2 else H2 Console will be blank.
.cacheControl();
//http.headers().cacheControl();
}
}
but I noticed that in my JWTAuthorizationFilter.class the doFilterInternal() method picks up this URL
public class JWTAuthorizationFilter extends OncePerRequestFilter {
private final CustomerDetailsService customerDetailsService;
#Autowired
DefaultCookieService defaultCookieService;
public JWTAuthorizationFilter(AuthenticationManager authenticationManager, CustomerDetailsService customerDetailsService) {
// super(authenticationManager);
this.customerDetailsService = customerDetailsService;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader(HEADER);
if(Objects.isNull(header) || !header.startsWith(TOKEN_PREFIX)){
return;
}
UsernamePasswordAuthenticationToken usernamePasswordAuth = getAuthenticationToken(request);
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuth);
chain.doFilter(request,response);
}
private UsernamePasswordAuthenticationToken getAuthenticationToken(HttpServletRequest request){
String token = request.getHeader(HEADER);
if(Objects.isNull(token)) return null;
String username = Jwts.parser().setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX,""))
.getBody()
.getSubject();
UserDetails userDetails = customerDetailsService.loadUserByUsername(username);
return username != null ? new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()) : null;
}
}
What is the cause of this ?

Filter is suppose to pick up each and every request. It doesn't matter if that you have permitted or not in security configuration.
You have got two options:
If you don't want welcome/** to go through the filter you add it to web ignore
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("**/resources/static/**")
.and()
.ignoring()
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/index_assets/**",
"/welcome/**"
);
}
But note, it will skip all filters and you may not want that.
In doFilterInternal method skip it when you find welcome/** pattern.

Related

How to apply a security filter only on a restricted http path [duplicate]

This question already has answers here:
Disabling a filter for only a few paths in spring security
(2 answers)
Closed 1 year ago.
I have a problem with my Spring security configuration. I just want to basically apply an authentication filter to some paths, and not to other path. But the filter i have defined is applied on all the HTTP request ever what i write in the configuration.
Here is my code.
SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtFilter jwtFilter;
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user")
.password("{bcrypt}$2a$10$DmzAlIznZz3faNQx1eBTBOw6fNiGE105fKoHkvskYTMXH5OFUE6iy")
.roles("USER");
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/admin/**")
.authorizeRequests() //
.anyRequest().authenticated() //
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
JWTFilter:
#Component
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JWTUtils jwtUtils;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException, ServletException, IOException {
String authorizationHeader = httpServletRequest.getHeader("Authorization");
String token = null;
String userName = null;
if (authorizationHeader != null) {
userName = jwtUtils.extractUsername(token);
}
if (jwtUtils.validateToken(token)) {
} else {
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userName, null);
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
For example if i try to call this servlet:
#PostMapping("/login")
public ResponseEntity<UserDetails> login(#RequestBody User user) throws Exception {
try {
Authentication authenticate = authenticate(user.getName(), user.getPassword());
UserDetails authenticatedUser = (UserDetails) authenticate.getPrincipal();
return ResponseEntity.ok()
.header(
HttpHeaders.AUTHORIZATION,
generateToken(authenticatedUser.getUsername())
)
.body(authenticatedUser);
} catch (BadCredentialsException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
My filter is called to verify if the client is authenticated but it is my login end point so my client is accordingly not authenticated yet...
For me the code I found on internet that should resolve this problem is this one:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/admin/**")
.authorizeRequests() //
.anyRequest().authenticated() //
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
You can find this code in the security config.
To exclude urls for the security filter, you should override the other configure method that accepts a WebSecurity as an argument and specify the url paths to ignore...
eg.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/login/**");
}
Another option you could look into is to configure form based login in spring security...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.and()
...
.logout();
}
It also looks like you're custom coding oauth2 authentication. Have you looked at what spring security 5 provides out of the box for securing urls with jwt tokens?
Check out the documentation at
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver.

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

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?

Spring Security with JWT - Authenticated at every tenth call

I've some strange problem with my spring security configuration.
I use it with an simple JWT implementation.
I use "GlobalMethodSecurity" and annotated my method with #PreAuthorize
Here a secured sample method:
#PreAuthorize("hasAnyRole('ROLE_USER')")
#RequestMapping(value = "/user/ok", method = RequestMethod.GET)
public ResponseEntity ok( ) {
return new ResponseEntity( "{text:\"ok\"}", HttpStatus.OK );
}
if I call this without a security token I get an "UNAUTHORIZED" nine times (as expected). At the exact tenth time I will get the "ok" json from the method. (The 11th to 19th call is "UNAUTHORIZED" and at the 20th I will became the "ok" message again and so on)
Here are my SecurityConfiguration:
#Configuration
#EnableGlobalMethodSecurity( prePostEnabled = true )
#EnableWebSecurity
#EnableTransactionManagement
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
#Autowired
public void configureAuthentication( AuthenticationManagerBuilder authenticationManagerBuilder ) throws Exception {
authenticationManagerBuilder
.userDetailsService( userDetailsService )
.passwordEncoder( new BCryptPasswordEncoder() );
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean(name = "authenticationTokenFilter")
public AuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager( super.authenticationManagerBean() );
return authenticationTokenFilter;
}
#Override
protected void configure( HttpSecurity httpSecurity ) throws Exception {
// Custom JWT based authentication
httpSecurity
.addFilterBefore( authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class )
.exceptionHandling()
.authenticationEntryPoint( authenticationEntryPoint )
.and()
.sessionManagement()
.sessionCreationPolicy( SessionCreationPolicy.STATELESS )
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
and my Filter Class:
public class AuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter {
#Autowired
private TokenUtils tokenUtils;
#Autowired
private UserDetailsService userDetailsService;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
tokenUtils = WebApplicationContextUtils
.getRequiredWebApplicationContext(this.getServletContext())
.getBean(TokenUtils.class);
userDetailsService = WebApplicationContextUtils
.getRequiredWebApplicationContext(this.getServletContext())
.getBean(UserDetailsService.class);
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH");
resp.setHeader("Access-Control-Max-Age", "3600");
resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Authorization, Accept, " + AppConstant.tokenHeader);
HttpServletRequest httpRequest = (HttpServletRequest) request;
String authToken = httpRequest.getHeader(AppConstant.tokenHeader);
String username = this.tokenUtils.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (this.tokenUtils.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
Edit
I found out that this only happen if a user was logged in once.
So in the example ok() method I had the previously logged in user in the security context although I haven't sent any token in the header.
But as I said - not in every request - There must be a kind of Thread or session thing I miss.
For a advice I would be very grateful
Thanks!
Take a look at this example: https://github.com/bfwg/springboot-jwt-starter
Live Demo: http://fanjin.computer:8080
SecurityConfiguration:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public TokenAuthenticationFilter jwtAuthenticationTokenFilter() throws Exception {
return new TokenAuthenticationFilter();
}
#Bean
public JwtLogoutHandler jwtLogoutHandler() {
return new JwtLogoutHandler();
}
#Autowired
CustomUserDetailsService jwtUserDetailsService;
#Autowired
RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService);
}
#Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS ).and()
.exceptionHandling().authenticationEntryPoint( restAuthenticationEntryPoint ).and()
.addFilterBefore(jwtAuthenticationTokenFilter(), BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/", "/index.html", "/login.html", "/home.html").permitAll()
.anyRequest()
.authenticated().and()
.formLogin()
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler).and()
.logout()
.addLogoutHandler(jwtLogoutHandler())
.logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))) ;
}
}
Filter Class:
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String authToken = getToken( request );
// get username from token
String username = tokenHelper.getUsernameFromToken( authToken );
if ( username != null ) {
// get user
UserDetails userDetails = userDetailsService.loadUserByUsername( username );
// create authentication
TokenBasedAuthentication authentication = new TokenBasedAuthentication( userDetails );
authentication.setToken( authToken );
authentication.setAuthenticated( true );
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
Hope this helps.

Issue with Spring Security remember me token not being set on SecurityContextHolder

I am encountering an issue with my remember me configuration:
[nio-8080-exec-8] s.s.w.a.r.RememberMeAuthenticationFilter : SecurityContextHolder not populated with remember-me token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken#73939efa: Principal: Member ...
Here is my Spring security configuration:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private MemberUserDetailsService memberUserDetailsService;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Autowired
private AccessDecisionManager accessDecisionManager;
#Autowired
private ApplicationEventPublisher eventPublisher;
#Override
protected void configure(HttpSecurity http) throws Exception {
//#formatter:off
http
.headers()
.cacheControl()
.and()
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.rememberMe()
.tokenValiditySeconds(60*60*24*7)
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.and()
.formLogin()
.loginProcessingUrl("/api/signin")
.failureHandler(authenticationFailureHandler())
.successHandler(authenticationSuccessHandler())
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/api/signout"))
.logoutSuccessHandler(logoutSuccessHandler())
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.authorizeRequests()
.accessDecisionManager(accessDecisionManager)
.antMatchers("/resources/**", "/**").permitAll()
.anyRequest().authenticated();
//#formatter:on
}
private LogoutSuccessHandler logoutSuccessHandler() {
return new LogoutSuccessHandler() {
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setStatus(HttpStatus.OK.value());
}
};
}
private AccessDeniedHandler accessDeniedHandler() {
return new AccessDeniedHandler() {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// TODO: deal with InvalidCsrfTokenException
response.setStatus(HttpStatus.FORBIDDEN.value());
}
};
}
private AuthenticationFailureHandler authenticationFailureHandler() {
return new AuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
};
}
private AuthenticationSuccessHandler authenticationSuccessHandler() {
return new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Member member = (Member) authentication.getPrincipal();
eventPublisher.publishEvent(new SigninApplicationEvent(member));
// TODO: overhaul below
response.addHeader("MEMBER_ROLE", member.getRole().name());
response.setStatus(HttpStatus.OK.value());
}
};
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberUserDetailsService).passwordEncoder(passwordEncoder);
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
and also:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class CoreSecurityConfiguration {
#Bean
public MemberUserDetailsService memberUserDetailsService() {
return new MemberUserDetailsService();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder;
}
#Bean
public SessionRegistryImpl sessionRegistry() {
SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
return sessionRegistry;
}
#Bean
public AffirmativeBased accessDecisionManager() {
AffirmativeBased accessDecisionManager = new AffirmativeBased(accessDecisionVoters());
return accessDecisionManager;
}
private List<AccessDecisionVoter<? extends Object>> accessDecisionVoters() {
List<AccessDecisionVoter<? extends Object>> accessDecisionVoters = new ArrayList<>();
accessDecisionVoters.add(roleHierarchyVoter());
accessDecisionVoters.add(webExpressionVoter());
return accessDecisionVoters;
}
#Bean
public WebExpressionVoter webExpressionVoter() {
WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
webExpressionVoter.setExpressionHandler(defaultWebSecurityExpressionHandler());
return webExpressionVoter;
}
#Bean
public DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
defaultWebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy());
return defaultWebSecurityExpressionHandler;
}
#Bean
public RoleHierarchyVoter roleHierarchyVoter() {
RoleHierarchyVoter roleHierarchyVoter = new RoleHierarchyVoter(roleHierarchy());
return roleHierarchyVoter;
}
#Bean
public RoleHierarchyImpl roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
//#formatter:off
roleHierarchy.setHierarchy(
"ROLE_ADMINISTRATOR > ROLE_MODERATOR\n" +
"ROLE_MODERATOR > ROLE_SUBSCRIBED_PARENTS\n" +
"ROLE_MODERATOR > ROLE_SUBSCRIBED_CHILDCARE_WORKER\n" +
"ROLE_SUBSCRIBED_PARENTS > ROLE_BASIC_PARENTS\n" +
"ROLE_SUBSCRIBED_CHILDCARE_WORKER > ROLE_BASIC_CHILDCARE_WORKER");
//#formatter:on
return roleHierarchy;
}
}
Can somemone please help?
edit 1:
MemberUserDetailsService:
#Component
public class MemberUserDetailsService implements UserDetailsService {
#Autowired
private MemberRepository memberRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Member member = memberRepository.findByEmail(email);
if (member == null) {
throw new UsernameNotFoundException("Username: " + email + " not found!");
}
return member;
}
}
edit 2: Here is the new config:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private MemberUserDetailsService memberUserDetailsService;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Autowired
private AccessDecisionManager accessDecisionManager;
#Autowired
private ApplicationEventPublisher eventPublisher;
#Autowired
private CsrfTokenRepository csrfTokenRepository;
#Override
protected void configure(HttpSecurity http) throws Exception {
//#formatter:off
http
.headers()
.cacheControl()
.and()
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.rememberMe()
.key("myKey")
.tokenValiditySeconds(60*60*24*7)
.userDetailsService(memberUserDetailsService)
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.and()
.formLogin()
.loginProcessingUrl("/api/signin")
.failureHandler(authenticationFailureHandler())
.successHandler(authenticationSuccessHandler())
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/api/signout"))
.logoutSuccessHandler(logoutSuccessHandler())
.and()
.addFilter(usernamePasswordAuthenticationFilter())
.addFilter(rememberMeAuthenticationFilter())
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.authorizeRequests()
.accessDecisionManager(accessDecisionManager)
.antMatchers("/resources/**", "/**").permitAll()
.anyRequest().authenticated();
//#formatter:on
}
private LogoutSuccessHandler logoutSuccessHandler() {
return new LogoutSuccessHandler() {
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setStatus(HttpStatus.OK.value());
}
};
}
private AccessDeniedHandler accessDeniedHandler() {
return new AccessDeniedHandler() {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// TODO: deal with InvalidCsrfTokenException & MissingCsrfTokenException
response.setStatus(HttpStatus.FORBIDDEN.value());
}
};
}
private AuthenticationFailureHandler authenticationFailureHandler() {
return new AuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
};
}
private AuthenticationSuccessHandler authenticationSuccessHandler() {
return new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setStatus(HttpStatus.OK.value());
Member member = (Member) authentication.getPrincipal();
eventPublisher.publishEvent(new SigninApplicationEvent(member));
response.setStatus(HttpStatus.OK.value());
// TODO: overhaul below
response.addHeader("MEMBER_ROLE", member.getRole().name());
}
};
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(rememberMeAuthenticationProvider()).userDetailsService(memberUserDetailsService).passwordEncoder(passwordEncoder);
}
#Bean
protected CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
#Bean
public RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
return new RememberMeAuthenticationProvider("myKey");
}
#Bean
public RememberMeServices rememberMeServices() {
return new TokenBasedRememberMeServices("myKey", memberUserDetailsService);
}
#Bean
public RememberMeAuthenticationFilter rememberMeAuthenticationFilter() throws Exception {
return new RememberMeAuthenticationFilter(authenticationManager(), rememberMeServices());
}
#Bean
public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() throws Exception {
UsernamePasswordAuthenticationFilter filter = new UsernamePasswordAuthenticationFilter();
filter.setRememberMeServices(rememberMeServices());
filter.setAuthenticationManager(authenticationManager());
return filter;
}
}
Since you have not specified the remember-me service implementation type, TokenBasedRememberMeServices is used by default.
Please find the below note from the documentation when using TokenBasedRememberMeServices:
Don't forget to add your RememberMeServices implementation to your
UsernamePasswordAuthenticationFilter.setRememberMeServices() property,
include the RememberMeAuthenticationProvider in your
AuthenticationManager.setProviders() list, and add
RememberMeAuthenticationFilter into your FilterChainProxy (typically
immediately after your UsernamePasswordAuthenticationFilter)
You need to make the following changes:
In configure() method you need to add a key and filters
http.rememberMe().key("yourKey")
.addFilter(usernamePasswordAuthenticationFilter())
.addFilter(rememberMeAuthenticationFilter())
Create UsernamePasswordAuthenticationFilter and RememberMeAuthenticationFilter
#Bean
public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter()
throws Exception {
UsernamePasswordAuthenticationFilter filter =
new UsernamePasswordAuthenticationFilter();
filter.setRememberMeServices(memberUserDetailsService);
filter.setAuthenticationManager(authenticationManager());
return filter;
}
#Bean
public RememberMeAuthenticationFilter rememberMeAuthenticationFilter()
throws Exception {
RememberMeAuthenticationFilter filter =
new RememberMeAuthenticationFilter(authenticationManager(), memberUserDetailsService);
return filter;
}
Add RememberMeAuthenticationProvider to the list of providers:
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(memberUserDetailsService)
.passwordEncoder(passwordEncoder)
.and()
.authenticationProvider(new RememberMeAuthenticationProvider("yourKey"));
}

Resources