TokenStore MongoDB Spring OAuth2 - java

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.

Related

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

how to handle exception thrown from org.springframework.security.core.userdetails.User for invalid credentials

I want to trigger an event if there is an invalid credential,in my code it goes to orelsethrow block(trying to achieve account lock).Is it possible to catch the exception thrown from "org.springframework.security.core.userdetails.User(lowercaseLogin, user.getPassword(),grantedAuthorities)" so that I can trigger an event which handles account lock
I have created a custom event handler(AuthenticationFailureEventListener din't work) to lock account after 3 or 5 attempts.I am using jhipster UAA
Optional<User> userFromDatabase = userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin);
return userFromDatabase.map(user -> {
if (!user.getActivated()) {
log.info("User " + login + " was not activated");
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
}
List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getName())).collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(lowercaseLogin, user.getPassword(),
grantedAuthorities);
})
.orElseThrow(
() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the " + "database"));
------- Account Lock Class
#Service
public class AccountLockService {
private final int MAX_ATTEMPT = 3;
private LoadingCache<String, Integer> attemptsCache;
public AccountLockService() {
super();
attemptsCache = CacheBuilder.newBuilder().
expireAfterWrite(1, TimeUnit.MINUTES).build(new CacheLoader<String, Integer>() {
public Integer load(String key) {
return 0;
}
});
}
public void loginFailed(String key) {
int attempts = 0;
try {
attempts = attemptsCache.get(key);
} catch (ExecutionException e) {
attempts = 0;
}
attempts++;
attemptsCache.put(key, attempts);
}
public boolean isBlocked(String key) {
try {
return attemptsCache.get(key) >= MAX_ATTEMPT;
} catch (ExecutionException e) {
return false;
}
}
}
----Custom Listener
#Component
public class CustomCreatedEventListener {
#Autowired
private AccountLockService accountLockService;
#Autowired
private HttpServletRequest request;
public CustomCreatedEventListener(AccountLockService accountLockService, HttpServletRequest request) {
this.accountLockService = accountLockService;
this.request = request;
}
#EventListener
public void accountLock(Authentication auth) {
String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader == null) {
xfHeader = request.getRemoteAddr();
}
xfHeader = xfHeader.split(",")[0];
accountLockService.loginFailed(xfHeader);
}
}

Getting null in Environment variable

#Configuration
public class CustomRemoteTokenService implements ResourceServerTokenServices {
private static final Logger logger = LoggerFactory.getLogger(CustomRemoteTokenService.class);
#Resource
Environment environment;
private RestOperations restTemplate;
private String checkTokenEndpointUrl;
private String clientId;
private String clientSecret;
private String tokenName = "token";
private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
#Autowired
public CustomRemoteTokenService() {
restTemplate = new RestTemplate();
((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
#Override
// Ignore 400
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getRawStatusCode() != 400
&& response.getRawStatusCode() != 403 /* && response.getRawStatusCode() != 401 */) {
super.handleError(response);
}
}
});
}
public void setRestTemplate(RestOperations restTemplate) {
this.restTemplate = restTemplate;
}
public void setCheckTokenEndpointUrl(String checkTokenEndpointUrl) {
this.checkTokenEndpointUrl = checkTokenEndpointUrl;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) {
this.tokenConverter = accessTokenConverter;
}
public void setTokenName(String tokenName) {
this.tokenName = tokenName;
}
#Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException, GenericException {
/*
* This code needs to be more dynamic. Every time an API is added we have to add
* its entry in the if check for now. Should be changed later.
*/
HttpServletRequest request = Context.getCurrentInstance().getRequest();
MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
formData.add(tokenName, accessToken);
formData.add("api", environment.getProperty("resource.api"));
/* formData.add("api", "5b64018880999103244f1fdd");*/
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
Map<String, Object> map = null;
try {
map = postForMap(checkTokenEndpointUrl, formData, headers);
} catch (ResourceAccessException e) {
logger.error("Socket Exception occured at " + System.currentTimeMillis() + "for client_id : " + clientId);
GenericException ge = new GenericException(
"Could not validate your access token. If this occurs too often please contact MapmyIndia support at apisupport#mapmyindia.com");
ge.setHttpErrorCode(504);
ge.setOauthError("Access Token validation failed");
throw ge;
}
if (map.containsKey("error")) {
logger.error("check_token returned error: " + map.get("error") + " for client id : " + clientId);
String temp = map.get("error").toString();
GenericException ge = new GenericException(map.get("error_description").toString());
ge.setHttpErrorCode(Integer.parseInt(map.get("responsecode").toString()));
ge.setOauthError(temp);
switch (temp) {
case "invalid_token":
throw new InvalidTokenException(accessToken);
default:
throw ge;
}
}
Assert.state(map.containsKey("client_id"), "Client id must be present in response from auth server");
return tokenConverter.extractAuthentication(map);
}
#Override
public OAuth2AccessToken readAccessToken(String accessToken) {
throw new UnsupportedOperationException("Not supported: read access token");
}
private String getAuthorizationHeader(String clientId, String clientSecret) {
String creds = String.format("%s:%s", clientId, clientSecret);
try {
return "Basic " + new String(Base64.encode(creds.getBytes("UTF-8")));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Could not convert String");
}
}
private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers)
throws RestClientException {
if (headers.getContentType() == null) {
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
}
#SuppressWarnings("rawtypes")
Map map = restTemplate.exchange(path, HttpMethod.POST,
new HttpEntity<MultiValueMap<String, String>>(formData, headers), Map.class).getBody();
#SuppressWarnings("unchecked")
Map<String, Object> result = map;
return result;
}
}
I autowired Environment and getting null when I do environment.getProperty("resource.api");
It is always returning null but in another classes I autowire Environment and successfully retrieve the value from properties.
You have to take this steps :
1.Register a Properties
You need to register you properties file by #PropertySource("classpath:foo.properties") as :
#Configuration
#PropertySource("classpath:foo.properties")
public class CustomRemoteTokenService implements ResourceServerTokenServices {
//...
}
2.Injecting Properties
To obtain the value of a property with the Environment API:
#Autowired
private Environment env;

Categories