SecurityContextHolder.getContext().getAuthentication() is null in JWT authentication - java

im trying to do simple JWT authorization application like here: https://github.com/eazybytes/spring-security/tree/main/section9/springsecsection9
but my authentication in JWTTokengeneratorFilter is null and I don't have a idea why.
I can do it the old way with attemptAuthentication methods etc. and it works great, but I wanted to do it like in this tutorial and I can't se what am I doing wrong here.
Can some one tell me what am I missing here?
AuthSecurityConfig.class
#Configuration
public class AuthSecurityConfig {
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.cors().configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Collections.singletonList("http://localhost:3000"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowCredentials(true);
config.setAllowedHeaders(Collections.singletonList("*"));
config.setExposedHeaders(List.of("Authorization"));
config.setMaxAge(3600L);
return config;
}).and().csrf().disable()
.addFilterBefore(new JWTTokenValidatorFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(new JWTTokenGeneratorFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests((auth) -> auth
.antMatchers( "/login").permitAll()
.antMatchers("/register").authenticated()
.antMatchers("/user").permitAll()
).httpBasic(Customizer.withDefaults());
return http.build();
}
}
JWTTokenGeneratorFilter.class
public class JWTTokenGeneratorFilter extends OncePerRequestFilter {
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // here authentication is null
if (null != authentication) {
SecretKey key = Keys.hmacShaKeyFor(SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
String jwt = Jwts.builder().setIssuer("Pollongz").setSubject("JWT Token")
.claim("username", authentication.getName())
.claim("authorities", populateAuthorities(authentication.getAuthorities()))
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + 300000000))
.signWith(key).compact();
response.setHeader(SecurityConstants.JWT_HEADER, jwt);
}
chain.doFilter(request, response);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return !request.getServletPath().equals("/login");
}
private String populateAuthorities(Collection<? extends GrantedAuthority> collection) {
Set<String> authoritiesSet = new HashSet<>();
for (GrantedAuthority authority : collection) {
authoritiesSet.add(authority.getAuthority());
}
return String.join(",", authoritiesSet);
}
}
JWTTokenValidatorFilter.class
public class JWTTokenValidatorFilter extends OncePerRequestFilter {
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String jwtToken = request.getHeader(SecurityConstants.JWT_HEADER);
if (null != jwtToken) {
try {
SecretKey key = Keys.hmacShaKeyFor(
SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(jwtToken)
.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!");
}
}
chain.doFilter(request, response);
}
#Override protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getServletPath().equals("/login"); }
}
AuthUserDetails.class
#Service
public class AuthUserDetails implements UserDetailsService {
private final UserRepository userRepository;
#Autowired
public AuthUserDetails(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByEmail(username);
if (user == null) {
throw new UsernameNotFoundException("User details not found for the user : " + username);
}
return new UserDetailsSecurity(user);
}
}
UserDetailsSecurity.class
public class UserDetailsSecurity implements UserDetails {
private final User user;
public UserDetailsSecurity(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Stream.of(user.getRoles().split(","))
.map(role -> "ROLE_" + role.toUpperCase())
.collect(Collectors.toList())
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getEmail();
}
//.. other override methods
}
AuthApplication.class
#SpringBootApplication
#ComponentScans({ #ComponentScan("com.vue.auth.controller"), #ComponentScan("com.vue.auth.config")})
#EnableJpaRepositories("com.vue.auth.repository")
#EntityScan("com.vue.auth.model")
#EnableWebSecurity(debug = true)
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}

Related

Method not defined for UserDetaildsServiceImpl

Good day am not sure what is going i suspect eclipse but in a UserDetailsServiceImpl it
is defined since i implemented the spring`s UserDetailsService below is is my code
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e);
}
filterChain.doFilter(request, response);
}
private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7, headerAuth.length());
}
return null;
}
this is the line giving headache "UserDetails userDetails = userDetailsService.loadUserByUsername(username)"
Thank you in advance
Use
'#Autowired'
UserDetailService userDetailsService;

JSON Web Token in spring session

I have three servers in production and a global load balancer when a first request comes request is validated and jwt token is generated and a token is stored in spring session in the server but when the new request comes and it went to the different server it's got me logged out.
what should I leverage here I read a few things about storing tokens in Redis, but I don't understand the flow how exactly to use it, or what are the different. option do I have here?
In Dev, I just have one server so I cannot test it out.
Can someone refer me some practical example how exactly to solve this issue
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private LoginSuccessHandler loginSuccessHandler;
#Autowired
private JWTRequestFilter jwtRequestFilter;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
...
}
}
JWT Util File that contains All the function relative to JWT validation and generation
#Component
public class JWTUtil {
public static final long EXPIRATION_TIME = 60 * 60 * 24;
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
#Value("${jwt.secret}")
private String secret;
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(secret.getBytes()).build().parseClaimsJws(token).getBody();
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map<String, Object> claims, String username) {
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
return Jwts.builder().setClaims(claims)
.setSubject(username).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME * 1000))
.signWith(key).compact();
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public boolean validateToken(String token, String requestUsername) {
final String username = getUsernameFromToken(token);
return (username.equals(requestUsername) && !isTokenExpired(token));
}
}
An important part of Jwt if filter file that contains which URL to filter or not
#Service
public class JWTRequestFilter extends OncePerRequestFilter {
#Autowired
private JWTUtil jwtUtil;
#Autowired
private MyUserDetailsService myUserDetailsService;
Set<String> urls = new HashSet<String>(Arrays.asList("/api/login", "/api/register", "/api/"));
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String path = request.getRequestURI();
return urls.contains(path);
}
protected boolean shouldNotFilterErrorDispatch() {
return true;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String jwtToken = null;
String username = null;
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if (cookie.getName().equals("Authorization")) {
jwtToken = cookie.getValue();
}
}
if (jwtToken != null) {
try {
username = jwtUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
if (username != null) {
UserDetails userDetails = this.myUserDetailsService.loadUserByUsername(username);
if (!jwtUtil.validateToken(jwtToken, userDetails.getUsername())) {
logger.warn("JWT Token validation failed");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
doFilter(request, response, filterChain);
}
}

Spring Security with JWT for REST API

I have this class:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String SALT = "fd&lkj§isfs23#$1*(_)nof";
private final JwtAuthenticationEntryPoint unauthorizedHandler;
private final JwtTokenUtil jwtTokenUtil;
private final UserSecurityService userSecurityService;
#Value("${jwt.header}")
private String tokenHeader;
public ApiWebSecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, JwtTokenUtil jwtTokenUtil,
UserSecurityService userSecurityService) {
this.unauthorizedHandler = unauthorizedHandler;
this.jwtTokenUtil = jwtTokenUtil;
this.userSecurityService = userSecurityService;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userSecurityService)
.passwordEncoder(passwordEncoder());
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12, new SecureRandom(SALT.getBytes()));
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// Un-secure H2 Database
.antMatchers("/h2-console/**/**").permitAll()
.antMatchers("/api/v1/users").permitAll()
.antMatchers("/error").permitAll()
.anyRequest().authenticated();
// Custom JWT based security filter
JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
httpSecurity
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity
.headers()
.frameOptions()
.sameOrigin() // required to set for H2 else H2 Console will be blank.
.cacheControl();
}
#Override
public void configure(WebSecurity web) {
// AuthenticationTokenFilter will ignore the below paths
web
.ignoring()
.antMatchers(
HttpMethod.POST,
"/api/v1/auth"
)
.antMatchers(
HttpMethod.POST,
"/api/v1/users"
)
.antMatchers(
HttpMethod.GET,
"/api/v1/countries"
);
}
}
and
#Provider
#Slf4j
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
private UserDetailsService userDetailsService;
private JwtTokenUtil jwtTokenUtil;
private String tokenHeader;
public JwtAuthorizationTokenFilter(UserDetailsService userDetailsService,
JwtTokenUtil jwtTokenUtil,
String tokenHeader) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.tokenHeader = tokenHeader;
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return new AntPathMatcher().match("/api/v1/users",
request.getServletPath());
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException,
IOException {
log.info("processing authentication for '{}'", request.getRequestURL());
log.info("tokenHeader '{}'", tokenHeader);
final String requestHeader = request.getHeader(this.tokenHeader);
log.info("requestHeader '{}'", requestHeader);
String username = null;
String authToken = null;
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
authToken = requestHeader.substring(7);
log.info("authToken '{}'", authToken);
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException e) {
logger.info("an error occured during getting username from token", e);
} catch (ExpiredJwtException e) {
logger.info("the token is expired and not valid anymore", e);
}
} else {
logger.info("couldn't find bearer string, will ignore the header");
}
log.info("checking authentication for user '{}'", username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
logger.info("security context was null, so authorizating user");
// It is not compelling necessary to load the use details from the database. You could also store the information
// in the token and read it from it. It's up to you ;)
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// For simple validation it is completely sufficient to just check the token integrity. You don't have to call
// the database compellingly. Again it's up to you ;)
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
log.info("authorizated user '{}', setting security context", username);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
and
#RestController
#Slf4j
public class AuthenticationRestController {
private static final Logger LOG = LoggerFactory.getLogger (AuthenticationRestController.class);
#Value("${jwt.header}")
private String tokenHeader;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private UserSecurityService userSecurityService;
#PostMapping(path = "/api/v1/auth", consumes = "application/json", produces = "application/json")
public ResponseEntity<JwtAuthenticationResponse>
createAuthenticationToken( #RequestBody JwtAuthenticationRequest authenticationRequest,
HttpServletRequest request) throws AuthenticationException {
LOG.info("authenticating {} " , authenticationRequest.getUsername());
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
// Reload password post-security so we can generate the token
final User userDetails = (User) userSecurityService.loadUserByUsername(authenticationRequest.getUsername());
if (!userDetails.isEnabled()) {
throw new UserDisabledException();
}
if (LOG.isDebugEnabled()) {
LOG.debug("UserDetails userDetails [ " + authenticationRequest.getUsername() + " ]");
}
final String token = jwtTokenUtil.generateToken(userDetails);
// Return the token
return ResponseEntity.ok(new JwtAuthenticationResponse(token));
}
#GetMapping(path = "${jwt.route.authentication.refresh}", consumes = "application/json", produces = "application/json")
public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request) {
String authToken = request.getHeader(tokenHeader);
final String token = authToken.substring(7);
String username = jwtTokenUtil.getUsernameFromToken(token);
JwtUser user = (JwtUser) userSecurityService.loadUserByUsername(username);
if (jwtTokenUtil.canTokenBeRefreshed(token, user.getLastPasswordResetDate())) {
String refreshedToken = jwtTokenUtil.refreshToken(token);
return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken));
} else {
return ResponseEntity.badRequest().body(null);
}
}
#ExceptionHandler({AuthenticationException.class})
public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
/**
* Authenticates the user. If something is wrong, an {#link AuthenticationException} will be thrown
*/
private void authenticate(String username, String password) {
Objects.requireNonNull(username);
Objects.requireNonNull(password);
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
e.printStackTrace();
throw new AuthenticationException("User is disabled!", e);
} catch (BadCredentialsException e) {
throw new AuthenticationException("Bad credentials!", e);
} catch (Exception e) {
e.printStackTrace();
}
}
}
and
#RestController
#RequestMapping("/api/v1/styles")
#Slf4j
public class StyleResourceController {
#PutMapping(path = "/{styleCode}")
#ResponseStatus(HttpStatus.OK)
public void setAlerts(#RequestHeader(value = "Authorization") String authHeader, #PathVariable String styleCode)
throws DataAccessException {
System.out.println("add style {} ");
final User user = authUserOnPath(authHeader);
System.out.println("user {} " + user);
protected User authUserOnPath(String authHeader) {
String authToken = authHeader.substring(7);
String username = jwtTokenUtil.getUsernameFromToken(authToken);
User user = userService.findByUserName(username);
if (user == null)
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "UserNotFound");
return user;
}
}
}
and
#Component
public class JwtTokenUtil implements Serializable {
//static final String CLAIM_KEY_USERNAME = "sub";
//static final String CLAIM_KEY_CREATED = "iat";
private static final long serialVersionUID = -3301605591108950415L;
// #SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "It's okay here")
private Clock clock = DefaultClock.INSTANCE;
#Value("${jwt.secret}")
private String secret;
#Value("${jwt.expiration}")
private Long expiration;
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getIssuedAtDateFromToken(String token) {
return getClaimFromToken(token, Claims::getIssuedAt);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(clock.now());
}
private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
return (lastPasswordReset != null && created.before(lastPasswordReset));
}
private Boolean ignoreTokenExpiration(String token) {
// here you specify tokens, for that the expiration is ignored
return false;
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
final Date created = getIssuedAtDateFromToken(token);
return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
&& (!isTokenExpired(token) || ignoreTokenExpiration(token));
}
public String refreshToken(String token) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
final Claims claims = getAllClaimsFromToken(token);
claims.setIssuedAt(createdDate);
claims.setExpiration(expirationDate);
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
JwtUser user = (JwtUser) userDetails;
final String username = getUsernameFromToken(token);
final Date created = getIssuedAtDateFromToken(token);
return (
username.equals(user.getUsername())
&& !isTokenExpired(token)
&& !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
);
}
private Date calculateExpirationDate(Date createdDate) {
return new Date(createdDate.getTime() + expiration * 1000);
}
}
but when I get the user from the token is null:
17:19:22.017 [http-nio-1133-exec-8] INFO c.d.c.JwtAuthorizationTokenFilter - tokenHeader 'Authorization'
17:19:22.017 [http-nio-1133-exec-8] INFO c.d.c.JwtAuthorizationTokenFilter - requestHeader 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2OTE3NjcxMzYsImlhdCI6MTYzMTI4NzEzNn0.C9s3dbjWNVyGdV5k0LXsNhMGMvPzboTx1J6sGEbfXVOP1CzCLeZFgVPQ4o8jgugvgURF3BcnsWAk7ygd7RCvdg'
17:19:22.017 [http-nio-1133-exec-8] INFO c.d.c.JwtAuthorizationTokenFilter - authToken 'eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2OTE3NjcxMzYsImlhdCI6MTYzMTI4NzEzNn0.C9s3dbjWNVyGdV5k0LXsNhMGMvPzboTx1J6sGEbfXVOP1CzCLeZFgVPQ4o8jgugvgURF3BcnsWAk7ygd7RCvdg'
17:19:22.018 [http-nio-1133-exec-8] INFO c.d.c.JwtAuthorizationTokenFilter - checking authentication for user 'null'
Double check your jwt token. I think it miss sub attribute( subject or username here).
I also highly recommend you write the few unit test for few class such as JwtTokenUtil to make sure your code working as expected. You can use spring-test to do it easily.
It help you discover the bug easier and sooner.
Here is few test which i used to test the commands "jwt generate" and "jwt parse"
#Test
public void testInvalidKey() {
String result = execute("jwt generate ");
Assertions.assertEquals(JsonWebToken.ERR_LOAD_PRIVATE_KEY, result);
}
#Test
public void testInvalidExpiredDate() {
String result = execute(
"jwt generate -exp \"12-12-2020\"");
Assertions.assertEquals(JsonWebToken.ERR_INVALID_EXPIRED_DATE, result);
}
#Test
public void testGenerateOk() {
String result = execute(
"jwt generate");
Assertions.assertNotNull(result);
Assertions.assertTrue(result.length() > 100);
}
#Test
public void testParseToken() {
String result = execute(
"jwt generate -sub designer");
Assertions.assertNotNull(result);
String parse = execute("jwt parse -token " + result);
Assertions.assertTrue(parse.contains("sub:designer"));
}
And here is few other test which i used to check the controller and filter.
#Test
public void testInValidToken() throws Exception{
Map<String, Object> map = new LinkedHashMap<>(reqObj);
mockMvc.perform(postWithTokenAndData(getUrlProvider(), map)
.header(HEADER_AUTHORIZATION, String.format("Bearer %s", INVALID_JWT))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().is4xxClientError());
}
#Test
public void testExpired() throws Exception{
Date exp = Date.from(Instant.now().minusSeconds(3600));
Map<String, Object> map = new LinkedHashMap<>(reqObj);
mockMvc.perform(postWithTokenAndData(getUrlProvider(), map)
.header(HEADER_AUTHORIZATION, String.format("Bearer %s", JwtHelperTest.getJwtToken(exp,
"designer")))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().is4xxClientError());
}

Can't retreive logged user from ReactiveSecurityContextHolder in Spring Aspect

I want to write simple aspect for enabling Hibernate filtering on some services. I need it for implementing some "visibility rules".
But i don't know how to do it with WebFlux. ReactiveSecurityContextHolder.getContext() returns nothing, when i call it in #Around aspect. I have debugged code and found that authentication starts before Aspect execution and ending after Aspect execution finished. Anybody know solution to this problem ? How can i retreive logged in user from aspect when using webflux ?
My aspect:
#Pointcut("execution(* com.shs.crm.dao.service.user.*.*(..))")
public void dataAccessOperation() {
logger.info("dataAccessOperation performed");
}
#Around("com.shs.aspect.DataSecurityAspect.dataAccessOperation()")
public Object doAccessCheck(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
Object returnObject;
if (proceedingJoinPoint.getTarget() instanceof CrmUserService) {
try {
Long userId = customSecurityService.getCurrentUserId().toFuture().get();//here i have IllegalStateException
} catch (Exception noContext) {
logger.debug(
"No Security Context associated with current thread - assuming least access");
}
// Here i will enable filter
returnObject = proceedingJoinPoint.proceed();
} else {
// here i will disable filter
returnObject = proceedingJoinPoint.proceed();
}
return returnObject;
}
Service for retreiving current user:
#Service
public class CustomSecurityService {
public Mono<Long> getCurrentUserId() {
return ReactiveSecurityContextHolder.getContext().switchIfEmpty(Mono.error(new IllegalStateException("ReactiveSecurityContext is empty")))
.map(SecurityContext::getAuthentication)
.map(Authentication::getName)
.map(Long::parseLong);
}
}
Security:
#Component
public class SecurityContextRepository implements ServerSecurityContextRepository{
#Autowired
private AuthenticationManager authenticationManager;
#Override
public Mono<Void> save(ServerWebExchange swe, SecurityContext sc) {
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public Mono<SecurityContext> load(ServerWebExchange swe) {
ServerHttpRequest request = swe.getRequest();
String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String authToken = authHeader.substring(7);
Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
return this.authenticationManager.authenticate(auth).map((authentication) -> {
return new SecurityContextImpl(authentication);
});
} else {
return Mono.empty();
}
}
}
#Component
public class AuthenticationManager implements ReactiveAuthenticationManager {
#Autowired
private JWTUtil jwtUtil;
#Autowired
private CrmUserService userService;
#Override
public Mono<Authentication> authenticate(Authentication authentication) {
String authToken = authentication.getCredentials().toString();
Long userId;
try {
userId = jwtUtil.getUserIdFromToken(authToken);
} catch (Exception e) {
userId = null;
}
if (userId != null && jwtUtil.validateToken(authToken)) {
Map<String, Claim> claims = jwtUtil.getAllClaimsFromToken(authToken);
Set<Long> permissions = userService.getUserPermissionsIds(userId);
//List<Long> permissions = claims.get("permissions").asList(Long.class);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
userId,
null,
permissions.stream().map( permission -> new SimpleGrantedAuthority(permission.toString())).collect(Collectors.toList())
);
return Mono.just(auth);
} else {
return Mono.empty();
}
}
List<String> getPermissionsNamesByIds(List<Long> ids){
return ids.stream().map(id -> {
CrmPermission.AvailablePermissions permission = (CrmPermission.AvailablePermissions)CrmPermission.AvailablePermissions.fromValue(id.intValue());
return permission.getCode();
}).collect(Collectors.toList());
}
}
I can get current user only from controller:
#RequestMapping(method = RequestMethod.GET)
public Mono<CrmUserDto> getAccount(Principal principal) {
return customSecurityService.getCurrentUserId().flatMap(userId -> {
CrmUser currentUser = userRepository.findOneByIdWithRoles(userId);
CrmUserDto userDTO = modelMapperService.map(currentUser, CrmUserDto.class);
return Mono.just(userDTO);
});
}

Spring Security - Custom Pre Auth filter using java config

I am trying to configure a simple custom authentication filter that checks for a token on every page of the web app except the '/login' page. Right now, the filter is up and running, but no matter what settings I change, the filter is being called on every page, including '/login' which I have set to permitAll().
When I access localhost:8080/login, I expect it to not call this filter based on my configuration below, but instead it throws an exception in the filter because no session is found.
My question is how do I limit the filter to all pages except the '/login' page?
Here is my config:
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
private UserDetailsService userDetailsService;
private PreAuthenticatedAuthenticationProvider preAuthenticatedProvider;
public SecurityConfig() {
super();
userDetailsService = new UserDetailsServiceImpl();
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper =
new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(userDetailsService);
preAuthenticatedProvider = new PreAuthenticatedAuthenticationProvider();
preAuthenticatedProvider.setPreAuthenticatedUserDetailsService(wrapper);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preAuthenticatedProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
OpenTokenRequestAuthenticationFilter filter = new OpenTokenRequestAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
http
.addFilter(filter)
.authorizeRequests()
.antMatchers("/login").permitAll();
}
}
And here is the filter:
public class OpenTokenRequestAuthenticationFilter extends
AbstractPreAuthenticatedProcessingFilter {
/**
* logger for the class
*/
private static final Logger logger = LoggerFactory.getLogger(OpenTokenRequestAuthenticationFilter.class);
#Autowired
private ExceptionMappingAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private IOpenTokenReader openTokenReader;
private String logoutURL;
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
super.doFilter(httpServletRequest, httpServletResponse, chain);
HttpSession session = httpServletRequest.getSession(false);
if(session != null && session.getAttribute("ssoToken") != null)
{
SsoToken ssoToken = (SsoToken)session.getAttribute("ssoToken");
httpServletResponse.addHeader("agentName", ssoToken.getName());
httpServletResponse.addHeader("agentID", ""+ssoToken.getLoginId());
}
}
/**
*
*/
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String principal = null;
HttpSession session = null;
try {
session = request.getSession(false);
String tokenName = openTokenReader.getTokenName();
if (tokenName != null && request.getParameter(tokenName.trim()) != null && !request.getParameter(tokenName.trim()).isEmpty()) {
logger.info("Token found in request. Token Name: "+ tokenName);
SsoToken ssoToken = null;
if (session != null) {
session.invalidate();
logger.info("Invalidated old session and creating a new session since request found with new token.");
}
session = request.getSession(true);//create new session
logger.info("New session created: "+session.getId());
Agent agent = openTokenReader.getAgent();
Map result = agent.readToken(request);
if (result != null) {
principal = (String) result.get("subject");
ssoToken = new SsoToken();
ssoToken.setLogoutURL(getLogoutURL());
// ssoToken.setName((String) result.get("lastName") +", "+(String) result.get("firstName"));
ssoToken.setName((String) result.get("firstName"));
ssoToken.setAffiliate((result.get("isAffiliate") != null && !((String)result.get("isAffiliate")).trim().equals("false")) ? true : false);
if(ssoToken.isAffiliate())
ssoToken.setLoginId((String) result.get("affiliateId"));
else
ssoToken.setLoginId((String) result.get("subject"));
}
session.setAttribute("ssoToken", ssoToken);
boolean isInValidToken = hasInvalidTokenData(ssoToken);
if (isInValidToken) {
throw new PreAuthenticatedCredentialsNotFoundException("Invalid Token found in request.");
} else {
session.setAttribute("hasValidToken", true);
}
}
} catch (Exception e) {
logger.error("Exception while reading token " + e);
}
if(session == null )
throw new PreAuthenticatedCredentialsNotFoundException("No session found.");
if(session != null && session.getAttribute("hasValidToken") == null)
throw new PreAuthenticatedCredentialsNotFoundException("No attribute 'hasValidToken' found in session.");
if(session != null && session.getAttribute("hasValidToken") != null && !(Boolean) session.getAttribute("hasValidToken"))
throw new PreAuthenticatedCredentialsNotFoundException("value of attribute 'hasValidToken' is false in session.");
/*if (session == null || session.getAttribute("hasValidToken") == null || !((Boolean) session.getAttribute("hasValidToken"))){
throw new PreAuthenticatedCredentialsNotFoundException("Token not found in request.");
}*/
if(session != null && session.getAttribute("ssoToken") != null)
{
SsoToken ssoToken = (SsoToken)session.getAttribute("ssoToken");
principal = ssoToken.getLoginId();
}
return principal;
}
public boolean hasInvalidTokenData(SsoToken token) {
boolean hasInvalidTokenData = false;
if (token == null) {
hasInvalidTokenData = true;
} else {
if (StringUtils.isBlank(token.getLoginId())) {
logger.debug("Login ID was blank.");
hasInvalidTokenData = true;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Exiting: hasInvalidTokenData(SsoToken)");
logger.debug("hasInvalidTokenData=|" + hasInvalidTokenData + "|");
}
return hasInvalidTokenData;
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "";
}
/**
* #return the logoutURL
*/
public String getLogoutURL() {
return logoutURL;
}
/**
* #param logoutURL the logoutURL to set
*/
public void setLogoutURL(String logoutURL) {
this.logoutURL = logoutURL;
}
public void setAuthenticationFailureHandler(ExceptionMappingAuthenticationFailureHandler authenticationFailureHandler){
this.authenticationFailureHandler = authenticationFailureHandler;
}
public ExceptionMappingAuthenticationFailureHandler getAuthenticationFailureHandler(){
return authenticationFailureHandler;
}
The below code Filters the unauthorized/Session expired requests for a particular endpoint. The Bean should be configured along with the endpoint URL so the Auth filter will be applicable for that endpoint. Now by doing so you can restric the filter application for only the endpoints which you want.
#Bean
public FilterRegistrationBean<AuthFilter> filterRegistrationBean() {
FilterRegistrationBean<AuthFilter> registrationBean = new FilterRegistrationBean<>();
AuthFilter authFilter = new AuthFilter();
registrationBean.setFilter(authFilter);
// Include the URL patterns for which the Auth filter should be applicable
registrationBean.addUrlPatterns("/api/protectedendpoint/*");
return registrationBean;
}
Auth Filter
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class AuthFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String authHeader = httpRequest.getHeader("Authorization");
if (authHeader != null) {
String[] authHeaderArr = authHeader.split("Bearer ");
if (authHeaderArr.length > 1 && authHeaderArr[1] != null) {
String token = authHeaderArr[1];
try {
Claims claims = Jwts.parser().setSigningKey(YOUR_JWT_SECRET_KEY).parseClaimsJws(token).getBody();
httpRequest.setAttribute("email", claims.get("email").toString());
} catch (Exception e) {
httpResponse.sendError(HttpStatus.FORBIDDEN.value(), "invalid/expired token");
return;
}
} else {
httpResponse.sendError(HttpStatus.FORBIDDEN.value(), "Authorization token must be Bearer [token]");
return;
}
} else {
httpResponse.sendError(HttpStatus.UNAUTHORIZED.value(), "Authorization token must be provided");
return;
}
chain.doFilter(httpRequest, httpResponse);
}
}```

Categories