JSON Web Token in spring session - java

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

Related

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

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

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

TokenStore MongoDB Spring OAuth2

I'm trying to create token store with on MongoDB
I wanted to use the current DB connection in my app.
I've used the JdbcTokenStore and convert it but I think I've done it wrong since I was not able to #Autowired (it is null).
I guess it is because that bean is starting before the Mongo connection bean.
Is any fix for that or I need to create the connection from scratch?
Also the #Value("${spring.data.mongodb.host}") is not working....
The most helpful if someone already wrote the TokenStore on Mongo.
Because I guess I'll also have serialization issues after.
I'm attaching my not so working code, please advise.
import lombok.Getter;
import lombok.Setter;
public class MongoDBTokenStore implements TokenStore {
private static final Log LOG = LogFactory.getLog(MongoDBTokenStore.class);
private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();
#Autowired
AccessTokenRepository accessTokenRepository;
#Autowired
RefreshTokenRepository refreshTokenRepository;
#Value("${spring.data.mongodb.uri}")
String mongo_uri;
#Value("${spring.data.mongodb.host}")
String mongo_host;
#Value("${spring.data.mongodb.database}")
String mongo_database;
#Value("${spring.data.mongodb.port}")
String mongo_port;
public MongoDBTokenStore() {
//MongoClient mongoClient = new MongoClient();
//Assert.notNull(mongoTemplate, "DataSource required");
}
public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) {
this.authenticationKeyGenerator = authenticationKeyGenerator;
}
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
OAuth2AccessToken accessToken = null;
String key = authenticationKeyGenerator.extractKey(authentication);
try {
AccessTockenElement element = accessTokenRepository.findByAuthenticationId(key);
accessToken = element.getOAuth2AccessToken();
}
catch (EmptyResultDataAccessException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Failed to find access token for authentication " + authentication);
}
}
catch (IllegalArgumentException e) {
LOG.error("Could not extract access token for authentication " + authentication, e);
}
if (accessToken != null
&& !key.equals(authenticationKeyGenerator.extractKey(readAuthentication(accessToken.getValue())))) {
removeAccessToken(accessToken.getValue());
// Keep the store consistent (maybe the same user is represented by this authentication but the details have
// changed)
storeAccessToken(accessToken, authentication);
}
return accessToken;
}
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
String refreshToken = null;
if (token.getRefreshToken() != null) {
refreshToken = token.getRefreshToken().getValue();
}
if (readAccessToken(token.getValue())!=null) {
removeAccessToken(token.getValue());
}
AccessTockenElement element = new AccessTockenElement();
element.setTokenId(extractTokenKey(token.getValue()));
element.setOAuth2AccessToken(token);
element.setAuthenticationId(authenticationKeyGenerator.extractKey(authentication));
element.setUsername(authentication.isClientOnly() ? null : authentication.getName());
element.setClientId(authentication.getOAuth2Request().getClientId());
element.setOAuth2Authentication(authentication);
element.setRefreshToken(extractTokenKey(refreshToken));
accessTokenRepository.save(element);
}
public OAuth2AccessToken readAccessToken(String tokenValue) {
OAuth2AccessToken accessToken = null;
try {
AccessTockenElement element = accessTokenRepository.findByTokenId(extractTokenKey(tokenValue));
accessToken = element.getOAuth2AccessToken();
}
catch (EmptyResultDataAccessException e) {
if (LOG.isInfoEnabled()) {
LOG.info("Failed to find access token for token " + tokenValue);
}
}
catch (IllegalArgumentException e) {
LOG.warn("Failed to deserialize access token for " + tokenValue, e);
removeAccessToken(tokenValue);
}
return accessToken;
}
public void removeAccessToken(OAuth2AccessToken token) {
removeAccessToken(token.getValue());
}
public void removeAccessToken(String tokenValue) {
accessTokenRepository.delete(extractTokenKey(tokenValue));
//jdbcTemplate.update(deleteAccessTokenSql, extractTokenKey(tokenValue));
}
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
return readAuthentication(token.getValue());
}
public OAuth2Authentication readAuthentication(String token) {
OAuth2Authentication authentication = null;
try {
AccessTockenElement element = accessTokenRepository.findByTokenId(extractTokenKey(token));
authentication = element.getOAuth2Authentication();
}
catch (EmptyResultDataAccessException e) {
if (LOG.isInfoEnabled()) {
LOG.info("Failed to find access token for token " + token);
}
}
catch (IllegalArgumentException e) {
LOG.warn("Failed to deserialize authentication for " + token, e);
removeAccessToken(token);
}
return authentication;
}
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
RefreshTockenElement element = new RefreshTockenElement();
element.setTokenId(extractTokenKey(refreshToken.getValue()));
element.setOAuth2RefreshToken(refreshToken);
element.setOAuth2Authentication(authentication);
refreshTokenRepository.save(element);
}
public OAuth2RefreshToken readRefreshToken(String token) {
OAuth2RefreshToken refreshToken = null;
try {
RefreshTockenElement element = refreshTokenRepository.findByTokenId(extractTokenKey(token));
refreshToken = element.getOAuth2RefreshToken();
}
catch (EmptyResultDataAccessException e) {
if (LOG.isInfoEnabled()) {
LOG.info("Failed to find refresh token for token " + token);
}
}
catch (IllegalArgumentException e) {
LOG.warn("Failed to deserialize refresh token for token " + token, e);
removeRefreshToken(token);
}
return refreshToken;
}
public void removeRefreshToken(OAuth2RefreshToken token) {
removeRefreshToken(token.getValue());
}
public void removeRefreshToken(String token) {
refreshTokenRepository.delete(extractTokenKey(token));
}
public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
return readAuthenticationForRefreshToken(token.getValue());
}
public OAuth2Authentication readAuthenticationForRefreshToken(String value) {
OAuth2Authentication authentication = null;
try {
RefreshTockenElement element = refreshTokenRepository.findByTokenId(extractTokenKey(value));
authentication = element.getOAuth2Authentication();
}
catch (EmptyResultDataAccessException e) {
if (LOG.isInfoEnabled()) {
LOG.info("Failed to find access token for token " + value);
}
}
catch (IllegalArgumentException e) {
LOG.warn("Failed to deserialize access token for " + value, e);
removeRefreshToken(value);
}
return authentication;
}
public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
removeAccessTokenUsingRefreshToken(refreshToken.getValue());
}
public void removeAccessTokenUsingRefreshToken(String refreshToken) {
accessTokenRepository.deleteByRefreshToken(extractTokenKey(refreshToken));
}
public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>();
try {
List<AccessTockenElement> elements = accessTokenRepository.findByClientId(clientId);
for (AccessTockenElement element : elements) {
accessTokens.add(element.getOAuth2AccessToken());
}
}
catch (EmptyResultDataAccessException e) {
if (LOG.isInfoEnabled()) {
LOG.info("Failed to find access token for clientId " + clientId);
}
}
accessTokens = removeNulls(accessTokens);
return accessTokens;
}
public Collection<OAuth2AccessToken> findTokensByUserName(String userName) {
List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>();
try {
List<AccessTockenElement> elements = accessTokenRepository.findByUsername(userName);
for (AccessTockenElement element : elements) {
accessTokens.add(element.getOAuth2AccessToken());
}
}
catch (EmptyResultDataAccessException e) {
if (LOG.isInfoEnabled())
LOG.info("Failed to find access token for userName " + userName);
}
accessTokens = removeNulls(accessTokens);
return accessTokens;
}
public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>();
try {
List<AccessTockenElement> elements = accessTokenRepository.findByClientIdAndUsername(clientId, userName);
for (AccessTockenElement element : elements) {
accessTokens.add(element.getOAuth2AccessToken());
}
}
catch (EmptyResultDataAccessException e) {
if (LOG.isInfoEnabled()) {
LOG.info("Failed to find access token for clientId " + clientId + " and userName " + userName);
}
}
accessTokens = removeNulls(accessTokens);
return accessTokens;
}
private List<OAuth2AccessToken> removeNulls(List<OAuth2AccessToken> accessTokens) {
List<OAuth2AccessToken> tokens = new ArrayList<OAuth2AccessToken>();
for (OAuth2AccessToken token : accessTokens) {
if (token != null) {
tokens.add(token);
}
}
return tokens;
}
protected String extractTokenKey(String value) {
if (value == null) {
return null;
}
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
}
catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK).");
}
try {
byte[] bytes = digest.digest(value.getBytes("UTF-8"));
return String.format("%032x", new BigInteger(1, bytes));
}
catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK).");
}
}
protected byte[] serializeAccessToken(OAuth2AccessToken token) {
return SerializationUtils.serialize(token);
}
protected byte[] serializeRefreshToken(OAuth2RefreshToken token) {
return SerializationUtils.serialize(token);
}
protected byte[] serializeAuthentication(OAuth2Authentication authentication) {
return SerializationUtils.serialize(authentication);
}
protected OAuth2AccessToken deserializeAccessToken(byte[] token) {
return SerializationUtils.deserialize(token);
}
protected OAuth2RefreshToken deserializeRefreshToken(byte[] token) {
return SerializationUtils.deserialize(token);
}
protected OAuth2Authentication deserializeAuthentication(byte[] authentication) {
return SerializationUtils.deserialize(authentication);
}
#Document
#Setter
#Getter
class AccessTockenElement {
#Id
String tokenId;
OAuth2AccessToken OAuth2AccessToken;
#Indexed
String authenticationId;
#Indexed
String username;
#Indexed
String clientId;
OAuth2Authentication OAuth2Authentication;
#Indexed
String refreshToken;
}
#Document
#Setter
#Getter
class RefreshTockenElement {
#Id
String tokenId;
OAuth2RefreshToken OAuth2RefreshToken;
OAuth2Authentication OAuth2Authentication;
}
#RepositoryRestResource
interface AccessTokenRepository extends MongoRepository<AccessTockenElement, String> {
#Transactional
public AccessTockenElement findByTokenId(String tokenId);
#Transactional
public AccessTockenElement findByAuthenticationId(String tokenId);
#Transactional
public List<AccessTockenElement> findByClientId(String clientId);
#Transactional
public List<AccessTockenElement> findByUsername(String username);
#Transactional
public List<AccessTockenElement> findByClientIdAndUsername(String clientId, String username);
#Transactional
public void deleteByRefreshToken(String refreshTokenId);
}
#RepositoryRestResource
public interface RefreshTokenRepository extends MongoRepository<RefreshTockenElement, String> {
#Transactional
public RefreshTockenElement findByTokenId(String tokenId);
}
}
The calling to that TokenStore is like that:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new MongoDBTokenStore();
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private ExtendedUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(this.tokenStore)
.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailsService);
}
.
.
.
}
#Autowired and #Value fields are null because MongoDBTokenStore is not a bean actually. It created by new and Spring knows nothing about it.
Annotate MongoDBTokenStore class with #Component or create it by #Bean factory method.
Also, check out this implementation MongoTokenStore, I think this is exactly what you're looking for.

REST-ful webservice #Context Injection always returns null

I am creating my first Restful web service with Embedded Jetty with authentication and authorization and I have a filter in which I would like to inject a user object (Employee) which then I can retrieve in a service bean using ResteasyProviderFactory.pushContext() the #Context annotation, but whatever I try the object always is null. I would appreciate any kind of help.
#PreMatching
public class AuthenticationHandler implements ContainerRequestFilter {
#Inject private PxCredentialService credentialService;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Response faultresponse = createFaultResponse();
String authorization = requestContext.getHeaderString("Authorization");
String[] parts = authorization.split(" ");
if (parts.length != 2 || !"Basic".equals(parts[0])) {
requestContext.abortWith(createFaultResponse());
return;
}
String decodedValue = null;
try {
decodedValue = new String(Base64Utility.decode(parts[1]));
} catch (Base64Exception ex) {
requestContext.abortWith(createFaultResponse());
return;
}
String[] namePassword = decodedValue.split(":");
Employee emp = credentialService.getCredentialsByLoginAndPass(namePassword[0], namePassword[1], true);
if ( emp != null) {
ResteasyProviderFactory.pushContext(Employee.class, emp);
} else {
throw new NullPointerException("False Login");//requestContext.abortWith(Response.status(401).build());
}
}
#Path( "/people" )
public class PeopleRestService implements credentials {
#Inject private PeopleService peopleService;
#Inject private GenericUserRightsUtil genericUserRightsUtil;
#Produces( { "application/json" } )
#GET
public Collection<Person> getPeople(#Context Employee emp) {
Employee emp = (Employee)crc.getProperty("Employee");
return peopleService.getPeople( page, 5 );
}
}
On my understanding, you want an easy way to identify the user who is performing the request in your resource methods. Have you ever considered setting a SecurityContext with a Principal for the request?
In your filter, if the user credentials as valid, do the following
final SecurityContext currentSecurityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return username;
}
};
}
#Override
public boolean isUserInRole(String role) {
return true;
}
#Override
public boolean isSecure() {
return currentSecurityContext.isSecure();
}
#Override
public String getAuthenticationScheme() {
return "Basic";
}
});
Your resource method will be like:
#GET
#Path("{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response foo(#PathParam("id") Long id,
#Context SecurityContext securityContext) {
...
}
To get the Principal, use:
Principal principal = securityContext.getUserPrincipal();
String username = principal.getName();

Categories