Spring Session + REST + Custom Authentication Filter(read credentials from JSON rather query param) - spring

I'm trying to convert my rest services authentication from basic authentication to form based authentication the below code works fine(Note I've commented out custom authentication filter) if I send authentication details in url as query parameter something like this http://localhost:8080/login?username=dfdf&&password=sdsdd
However I'm not keen on sending credentials as query parameter instead I would prefer to send it as json, Hence I've created custom authentication filter. When I add the custom authentication filter, my spring session stops working. I cannot find x-auth-token field in my response header. Any suggestion how to enable spring session and custom authentication together/or may be easier way to handle json input for credentials.
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private ObjectMapper objectMapper;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/register", "/index.html").permitAll().and().authorizeRequests()
.anyRequest().authenticated().and().requestCache().requestCache(new NullRequestCache()).and()
.formLogin().failureHandler(getRESTAuthenticationFailureHandler())
.successHandler(getRESTAuthenticationSuccessHandler()).usernameParameter("username")
.passwordParameter("password").and().exceptionHandling()
.authenticationEntryPoint(getRESTAuthenticationEntryPoint()).and()
//.addFilter(getAuthenticationFilter())
.csrf().disable();
}
#Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
#Bean
public RESTAuthenticationEntryPoint getRESTAuthenticationEntryPoint() {
return new RESTAuthenticationEntryPoint();
}
#Bean
public RESTAuthenticationSuccessHandler getRESTAuthenticationSuccessHandler() {
return new RESTAuthenticationSuccessHandler();
}
#Bean
public RESTAuthenticationFailureHandler getRESTAuthenticationFailureHandler() {
return new RESTAuthenticationFailureHandler();
}
#Bean
public AuthenticationFilter getAuthenticationFilter() {
AuthenticationFilter filter = new AuthenticationFilter();
try {
filter.setAuthenticationManager(this.authenticationManager());
} catch (Exception e) {
e.printStackTrace();
}
return filter;
}
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
clearAuthenticationAttributes(request);
}
}
public class RESTAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
}
}
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final Logger LOG = LoggerFactory.getLogger(AuthenticationFilter.class);
private boolean postOnly = true;
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = null;
String password = null;
UserDetails userDetails = null;
if ("application/json".equals(request.getHeader("Content-Type"))) {
userDetails = getJson(request);
if (userDetails != null) {
username = userDetails.getUsername();
}
} else {
username = obtainUsername(request);
}
if ("application/json".equals(request.getHeader("Content-Type"))) {
if (userDetails != null) {
password = userDetails.getPassword();
}
} else {
password = obtainPassword(request);
}
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,
password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private UserDetails getJson(HttpServletRequest request) {
try {
final List<String> data = IOUtils.readLines(request.getReader());
final String jsonData = data.stream().collect(Collectors.joining());
LOG.info(jsonData);
UserDetails userDetails = objectMapper.readValue(jsonData, UserDetails.class);
return userDetails;
} catch (IOException e) {
LOG.error("Failed to read data {}", e.getMessage(), e);
return null;
}
}
}
}

As suggested I've created custom filter which converts json object to request parameter and adds to HttpServletRequest, However are there any inbuilt spring security custom filters to do the same job?
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private ObjectMapper objectMapper;
#Bean
public FilterRegistrationBean contextFilterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
CstFilter contextFilter = new CstFilter();
registrationBean.addUrlPatterns("/login");
registrationBean.setFilter(contextFilter);
registrationBean.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
return registrationBean;
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(getRESTAuthenticationEntryPoint())
.and()
.formLogin()
.permitAll()
.loginProcessingUrl("/login")
.failureHandler(getRESTAuthenticationFailureHandler())
.successHandler(getRESTAuthenticationSuccessHandler())
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.permitAll()
.logoutSuccessHandler(getRESTLogoutSuccessHandler())
.and()
.authorizeRequests()
.antMatchers("/", "/index.html")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache());
}
#Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
#Bean
public RESTAuthenticationEntryPoint getRESTAuthenticationEntryPoint() {
return new RESTAuthenticationEntryPoint();
}
#Bean
public RESTAuthenticationSuccessHandler getRESTAuthenticationSuccessHandler() {
return new RESTAuthenticationSuccessHandler();
}
#Bean
public RESTAuthenticationFailureHandler getRESTAuthenticationFailureHandler() {
return new RESTAuthenticationFailureHandler();
}
#Bean
public RESTLogoutSuccessHandler getRESTLogoutSuccessHandler() {
return new RESTLogoutSuccessHandler();
}
public class JsonConvertFilter extends HttpServletRequestWrapper {
private final Logger LOG = LoggerFactory.getLogger(JsonConvertFilter.class);
private UserDetails userDetails;
public JsonConvertFilter(HttpServletRequest request) {
super((HttpServletRequest)request);
userDetails = getJson();
}
public String getParameter(String key){
if(userDetails!=null){
if("username".equals(key)){
return userDetails.getUsername();
}
if("password".equals(key)){
return userDetails.getPassword();
}
}
System.out.println("Called wrapper");
return super.getParameter(key);
}
private UserDetails getJson() {
try {
final List<String> data = IOUtils.readLines(super.getReader());
final String jsonData = data.stream().collect(Collectors.joining());
LOG.info(jsonData);
UserDetails userDetails = objectMapper.readValue(jsonData, UserDetails.class);
return userDetails;
} catch (IOException e) {
LOG.warn("Failed to read data {}", e.getMessage(), e);
return null;
}
}
}
public class CstFilter implements Filter{
#Override
public void destroy() {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(new JsonConvertFilter((HttpServletRequest)request), response);
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}
public class RESTLogoutSuccessHandler implements LogoutSuccessHandler{
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
String uri = request.getRequestURI();
if ("logout".equals(uri)) {
response.sendError(HttpServletResponse.SC_OK, "Succesfully Logged out");
}
}
}
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().print("Unauthorizated....");
}
}
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
clearAuthenticationAttributes(request);
}
}
public class RESTAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String uri = request.getRequestURI();
if ("logout".equals(uri)) {
response.sendError(HttpServletResponse.SC_OK, "Succesfully Logged out");
} else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Authentication Failed: " + exception.getMessage());
}
}
}
}
For any one want to test this using jquery ajax.
/* Alerts the results */
$.ajax({
url : "login",
type : "POST",
async : false,
contentType: "application/json",
data : "{ \"username\":\""+username+"\", \"password\":\"" + password + "\"}",
success : function(data, status, request) {
alert("Success");
authtokenKey = request.getResponseHeader('x-auth-token');
},
error : function(xhr, status, error) {
alert(error);
}
});

Related

Can't authenticate or authorize with JWT

public class JWTTokenGeneratorFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (null != authentication) {
SecretKey key = Keys.hmacShaKeyFor(SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
String jwt = Jwts.builder().setIssuer("Eazy Bank").setSubject("JWT Token")
.claim("username", authentication.getName())
.claim("authorities", populateAuthorities(authentication.getAuthorities()))
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + 30000000))
.signWith(key).compact();
response.setHeader(SecurityConstants.JWT_HEADER, jwt);
}
filterChain.doFilter(request, response);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return !request.getServletPath().equals("/user");
}
private String populateAuthorities(Collection<? extends GrantedAuthority> collection) {
Set<String> authoritiesSet = new HashSet<>();
for (GrantedAuthority authority : collection) {
authoritiesSet.add(authority.getAuthority());
}
return String.join(",", authoritiesSet);
}
}
This is my jwtgenerator class
public class JWTTokenValidatorFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String jwt = request.getHeader(SecurityConstants.JWT_HEADER);
if (null != jwt) {
try {
SecretKey key = Keys.hmacShaKeyFor(
SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(jwt)
.getBody();
String username = String.valueOf(claims.get("username"));
String authorities = (String) claims.get("authorities");
Authentication auth = new UsernamePasswordAuthenticationToken(username, null,
AuthorityUtils.commaSeparatedStringToAuthorityList(authorities));
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception e) {
throw new BadCredentialsException("Invalid Token received!");
}
}
filterChain.doFilter(request, response);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getServletPath().equals("/user");
}
}
this is my jwt validator class
Configuration
public class ProjectSecurityConfig {
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
requestHandler.setCsrfRequestAttributeName("_csrf");
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.cors().configurationSource(new CorsConfigurationSource() {
#Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Collections.singletonList("http://localhost:4200"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowCredentials(true);
config.setAllowedHeaders(Collections.singletonList("*"));
config.setExposedHeaders(Arrays.asList("Authorization"));
config.setMaxAge(3600L);
return config;
}
}).and().csrf((csrf) -> csrf.csrfTokenRequestHandler(requestHandler).ignoringRequestMatchers("/contact","/register")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(new RequestValidationBeforeFilter(), BasicAuthenticationFilter.class)
.addFilterAt(new AuthoritiesLoggingAtFilter(),BasicAuthenticationFilter.class)
.addFilterAfter(new AuthoritiesLoggingAfterFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(new JWTTokenGeneratorFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(new JWTTokenValidatorFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests()
.requestMatchers("/myAccount").hasRole("ADMIN")
.requestMatchers("/myBalance").hasAnyRole("USER","ADMIN")
.requestMatchers("/myLoans").hasRole("USER")
.requestMatchers("/myCards").hasRole("USER")
.requestMatchers("/user").authenticated()
.requestMatchers("/notices","/contact","/register").permitAll()
.and().formLogin()
.and().httpBasic();
return http.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
this is configuration
and this is interface
public interface SecurityConstants {
public static final String JWT_KEY = "jxgEQeXHuPq8VdbyYFNkANdudQ53YUn4";
public static final String JWT_HEADER = "Authorization";
}
problem is i cant authorize on my endpoint for example localhost:8080/myAccount?id=1

Add additional user requirements in spring security login and handle various exceptions

I am new to Spring security, I have implemented a basic user login functionality for my app using JWT. Aside from checking for username and password at login I would like to add other parameters such as a "account is verified" boolean condition but I am not sure where to add this requirement. Additionally, I need to return a 403 forbidden response status message if the "account is verified" condition is false and return a different response status message if the username password combination isn't found at all. Here Is the code I currently have which correctly handles the login of an existing user (without checking for the "account is verified" condition) and always throws a 401 when the user is found. Any feedback would be helpful.
WebSecurityConfigurerAdapter
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final ApplicationUserDetailsService applicationUserDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurityConfig(ApplicationUserDetailsService userDetailsService) {
this.applicationUserDetailsService = userDetailsService;
this.bCryptPasswordEncoder = new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilter(new AuthenticationFilter(authenticationManager()))
.addFilter(new AuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder encoder() {
return this.bCryptPasswordEncoder;
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(applicationUserDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
}
UserDetailsService
public class ApplicationUserDetailsService implements UserDetailsService {
private final ApplicationUserRepository applicationUserRepository;
public ApplicationUserDetailsService(ApplicationUserRepository applicationUserRepository) {
this.applicationUserRepository = applicationUserRepository;
}
#Override
public UserDetails loadUserByUsername(String nickname)
throws UsernameNotFoundException, UserIsNotActiveException {
Optional<ApplicationUser> applicationUser =
applicationUserRepository.findByNickname(nickname);
if (!applicationUser.isPresent()) {
throw new UsernameNotFoundException(nickname);
}
return new User(
applicationUser.get().getNickname(),
applicationUser.get().getPassword(),
emptyList());
}
}
AuthenticationFilter
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
ApplicationUser applicationUser =
new ObjectMapper().readValue(req.getInputStream(), ApplicationUser.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
applicationUser.getNickname(),
applicationUser.getPassword(),
new ArrayList<>()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(
HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) {
Date exp = new Date(System.currentTimeMillis() + EXPIRATION_TIME);
Key key = Keys.hmacShaKeyFor(KEY.getBytes());
Claims claims = Jwts.claims().setSubject(((User) auth.getPrincipal()).getUsername());
String token =
Jwts.builder()
.setClaims(claims)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(exp)
.compact();
res.addHeader("token", token);
}
}
AuthorizationFilter
public AuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader(HEADER_NAME);
if (header == null) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authentication = authenticate(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken authenticate(HttpServletRequest request) {
String token = request.getHeader(HEADER_NAME);
if (token != null) {
Jws<Claims> user =
Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(KEY.getBytes()))
.build()
.parseClaimsJws(token);
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
} else {
return null;
}
}
return null;
}
ApplicationUser
public class ApplicationUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(unique = true)
String email;
#Column(unique = true)
String nickname;
String biography;
String password; // Hashed
#Builder.Default boolean isActive = false;
}
The interface UserDetails (that is returned by the UserDetailsService) has some utility methods that can help you with it.
While the account is not activated, you can return false from the UserDetails#isEnabled method, or maybe you can use UserDetails#isAccountNonLocked as well.
Those methods will then be automatically validated on the AbstractUserDetailsAuthenticationProvider$Default(Pre/Post)AuthenticationChecks class.
After the user goes through the activation flow, you can change the property to true and it will allow the user to authenticate.
Tip: add the logging.level.org.springframework.security=TRACE to your application.properties to help to debug.

Getting null token in Jwt token filter when a request is sent in?

Just started using Jwt tokens when securing my microservices and keep getting a null token in my JwtTokenFilter class when a request is sent in but don't know where from, and finding it hard to understand why?
JwtTokenFilter.class
public class JwtTokenFilter extends OncePerRequestFilter {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = jwtTokenProvider.resolveToken(request);
System.out.println("Token: " + token);
try {
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (CustomException ex) {
SecurityContextHolder.clearContext();
response.sendError(ex.getHttpStatus().value(), ex.getMessage());
return;
}
filterChain.doFilter(request, response);
}
SecurityConfig.class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/login").permitAll()
.antMatchers("/auth/register").permitAll()
.antMatchers("/auth/{username}").permitAll()
.anyRequest()
.authenticated();
http.addFilterBefore(new JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}
JwtTokenProvider.class
#Component
public class JwtTokenProvider {
#Value("$security.jwt.token.secret-key")
private String secretKey;
private long validityInMilliseconds = 3600000;
#Autowired
private CustomUserDetails customUserDetails;
#PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public String createToken(String username, List<Role> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("auth", roles.stream().map(s -> new SimpleGrantedAuthority(s.getAuthority())).collect(Collectors.toList()));
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()//
.setClaims(claims)//
.setIssuedAt(now)//
.setExpiration(validity)//
.signWith(SignatureAlgorithm.HS256, secretKey)//
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = customUserDetails.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
public String getUsername(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new CustomException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
If you need any more classes shown just ask, thanks for the help.
Realised the problem was how I registered it with my Gateway microservice in the spring configuration. All sorted thanks for the help.

Spring security jet always redirecting to "/" after successful auth

I have made a Rest API project with Spring Boot 2. I have used jwt for authentication. I can generate tokens fine. But when I send the generated token in a header with a request, it always redirects me to "/" path instead of the requested path (in my case "/rest/hello").
This is my custom AuthenticationProvider
#Component
public class JwtAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
#Autowired
private JwtValidator jwtValidator;
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
}
#Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken)authentication;
String token = jwtAuthenticationToken.getToken();
JwtUser jwtUser = jwtValidator.validate(token);
if (jwtUser == null) {
throw new RuntimeException("JWT Token not correct");
}
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(jwtUser.getRole());
return new JwtUserDetails(jwtUser.getUserName(), jwtUser.getId(), grantedAuthorities, token);
}
#Override
public boolean supports(Class<?> authentication) {
return JwtAuthenticationToken.class.isAssignableFrom(authentication);
}
}
This is my custom Filter
public class JwtAuthenticationTokenFilter extends AbstractAuthenticationProcessingFilter {
public JwtAuthenticationTokenFilter() {
super("/rest/**");
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Token ")) {
throw new RuntimeException("JWT Token is missing");
}
String authenticationToken = header.substring(6);
JwtAuthenticationToken token = new JwtAuthenticationToken(authenticationToken);
return this.getAuthenticationManager().authenticate(token);
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
public void setAuthenticationSuccessHandler(JwtSuccessHandler jwtSuccessHandler) {
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
}
This is my Security config
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class JwtSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationProvider authenticationProvider;
#Autowired
private JwtAuthenticationEntryPoint entryPoint;
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Collections.singletonList(authenticationProvider));
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilter(){
JwtAuthenticationTokenFilter filter = new JwtAuthenticationTokenFilter();
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(new JwtSuccessHandler());
return filter;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/rest/**").authenticated().and().exceptionHandling().authenticationEntryPoint(entryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.headers().cacheControl();
}
}
I have custom classes for user details and token as well.

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