Keycloak custom attribute not fetched in the claims - spring-boot

I am trying to fetch a custom attribute (phone_number) in the form of a claim from Keycloak. I am following the steps given here. Below are attached screenprints of the steps I have executed.
Adding attribute to user
Protocol mapper
Protocol mapper is available in Client scope -> evaluate for the above-mentioned client
I am now trying to access this attribute in a filter as follows.
public class FilterTest extends OncePerRequestFilter {
public static final String PHONE_NUMBER = "phone_number";
public FilterTest() {
}
#Override
protected void doFilterInternal(
final HttpServletRequest request,
final HttpServletResponse response,
final FilterChain filterChain) throws ServletException, IOException {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if( !AnonymousAuthenticationToken.class.isAssignableFrom(authentication.getClass()) ){
Principal principal = (Principal) authentication.getPrincipal();
if (principal instanceof KeycloakPrincipal) {
KeycloakPrincipal<KeycloakSecurityContext> kp = (KeycloakPrincipal<KeycloakSecurityContext>) principal;
AccessToken token = kp.getKeycloakSecurityContext().getToken();
Map<String, Object> otherClaims = token.getOtherClaims();
System.out.println("Phone number => "+otherClaims.get(PHONE_NUMBER); // null pointer
}
}
filterChain.doFilter(request, response);
}
}
Additional things that I have tried.
Clear the realm cache.
Add the built-in phone number protocol mapper.
The above two steps also didn't yield any results for me.
I am not sure what I am missing here. Any help is appreciated.

Keycloak is mapping some of the common fields from the custom attributes directly to the fields of IDToken class. In my case, phone number was a field in the IDToken class and I was trying to fetch it from otherClaims map. The following is the change in code snip that got me up and running.
AccessToken token = kp.getKeycloakSecurityContext().getToken();
phoneNumber = token.getPhoneNumber();

Related

How to send body data within a redirect using Spring Security AuthenticationFailureHandler?

I would like to provide detailed information about a failed login attempt.
Thymleaf shall display regarding information as well as prefill the login form with already given values like email or username. I don't want to send these information as url parameters. From my understanding I need a POST request to the login url.
How can I achieve this using an AuthenticationFailureHandler, and is this even doable without implementing the login process completely manually?
At the moment, all I have is this:
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException {
String email = request.getParameter("email");
String password = request.getParameter("password");
/* How to make the response POST including data for Thymeleaf? */
redirectStrategy.sendRedirect(request, response, "/login");
}
}
Many thanks for any information

Spring REST secure DELETE only the owned (the one created by app end-user, ONLY) resource

I try to find the best solution in how safety (by the owner only) DELETE a REST resource.
GOAL:
The resource could be deleted only by the owner/creator of that resource (means the one it created that resource).
Premises:
Each time an application end-user creates a client account he receives back a JWT token.
To be able to access a REST resource the client should provide a valid JWT.
The validation of the JWT is done for each incoming calls through a customer filter:
#Component public class JwtRequestFilter extends OncePerRequestFilter{
#Autowired
private ClientAuthService clientAuthService;
#Autowired
private JwtUtil jwtUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeaderDate = request.getHeader("Date");
if (authorizationHeaderDate != null){
if (DateTimeUtil.isLaterInMinThenNow(
LocalDateTime.parse(authorizationHeaderDate,
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), 2)) {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.clientAuthService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
= new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
}
}
chain.doFilter(request, response);
}
}
The current implementation of the DELETE REST end-point is:
#DeleteMapping("/clients/{id}")
public ResponseEntity<Client> deleteClientById(#PathVariable(required = true) Long id){
return ResponseEntity.ok(clientService.deleteClientById(id));
}
Letting like each end-user having a valid JWT could delete another end-user client account.
For a hacker is easy to get a JWT, intuit a client ID and delete, one-by-one, all clients accounts
The question is: How can I prevent such a security issue?
You want to use Spring's expression based access control:
https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html
You can annotate your REST endpoint method or service method and use EL expressions to authorize your user. Here's an example from Spring's documentation that you can adapt:
#PreAuthorize("#n == authentication.name")
Contact findContactByName(#Param("n") String name);
Now - you didn't ask, but you should consider conforming to the REST convention of using the HTTP verb that matches what your action does (i.e. use DELETE HTTP actions for requests that delete resources):
Do not a REST service that uses GET HTTP methods to delete resources - to anyone that knows anything about REST this is not going to make sense:
#GetMapping("/clients/{id}")
It should be
#DeleteMapping("/clients/{id}")

How can I test my JwtAuthentication class using JUnit & Mockito? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I can test my JwtTokenAuthenticationFilter class. How can I write the test cases of this class using Mockito & JUnit? I can Only test this class.
I don't understand how I can mock the class.
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
public JwtTokenAuthenticationFilter(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
System.out.println("Code is reachable");
// 1. get the authentication header. Tokens are supposed to be passed in the authentication header
String header = request.getHeader(jwtConfig.getHeader());
// 2. validate the header and check the prefix
if (header == null || !header.startsWith(jwtConfig.getPrefix())) {
chain.doFilter(request, response);
return;// If not valid, go to the next filter.
}
// If there is no token provided and hence the user won't be authenticated.
// It's Ok. Maybe the user accessing a public path or asking for a token.
// All secured paths that needs a token are already defined and secured in config class.
// And If user tried to access without access token, then he won't be authenticated and an exception will be thrown.
// 3. Get the token
String token = header.replace("Bearer","");
try { // exceptions might be thrown in creating the claims if for example the token is expired
// 4. Validate the token
Claims claims = Jwts.parser()
.setSigningKey(jwtConfig.getSecret().getBytes())
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
if (username != null) {
#SuppressWarnings("unchecked")
List<String> authorities = (List<String>) claims.get(ApplicationConstant.tokenAuthorities);
List<GrantedAuthority> grantAuthorities = new ArrayList<GrantedAuthority>();
// 5. Create auth object
// UsernamePasswordAuthenticationToken:A built-in object, used by spring to represent the current authenticated / being authenticated user.
// It needs a list of authorities, which has type of GrantedAuthority interface, where SimpleGrantedAuthority is an implementation of that interface
for (String authName : authorities) {
grantAuthorities.add(new SimpleGrantedAuthority(authName));
}
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
username, null, grantAuthorities);
// 6. Authenticate the user
// Now, user is authenticated
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
// In case of failure. Make sure it's clear; so guarantee user won't be authenticated
SecurityContextHolder.clearContext();
}
// go to the next filter in the filter chain
chain.doFilter(request, response);
}
}
Spring gives you some mocks:
MockHttpServletRequest: mocked implementation of HttpServletRequest
MockHttpServletResponse: mocked implementation of HttpServletResponse
MockFilterChain: mocked implementation of FilterChain
MockFilterConfig: mocked implementation if FilterConfig
See the org.springframework.mock.web package for other mocks.
And here's some code that can help you to get started:
#RunWith(SpringRunner.class)
public class JwtTokenAuthenticationFilterTest {
#Before
public void before() {
SecurityContextHolder.clearContext();
}
#After
public void after() {
SecurityContextHolder.clearContext();
}
#Test
#SneakyThrows
public void doFilterInternal_shouldPopulateSecurityContext_whenTokenIsValid() {
String token = issueTokenForUser("john.doe");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = new MockFilterChain();
FilterConfig filterConfig = new MockFilterConfig();
JwtTokenAuthenticationFilter filter = new JwtTokenAuthenticationFilter();
filter.init(filterConfig);
filter.doFilter(request, response, filterChain);
filter.destroy();
assertThat(SecurityContextHolder.getContext().getAuthentication())
.satisfies(authentication -> {
assertThat(authentication).isNotNull();
assertThat(authentication.getName()).isEqualTo("john.doe");
});
}
private String issueTokenForUser(String username) {
return "xxxxx.yyyyy.zzzzz"; // Implement as per your needs
}
}
The above code uses AssertJ for assertions.

How to design a good JWT authentication filter

I am new to JWT. There isn't much information available in the web, since I came here as a last resort. I already developed a spring boot application using spring security using spring session. Now instead of spring session we are moving to JWT. I found few links and now I can able to authenticate a user and generate token. Now the difficult part is, I want to create a filter which will be authenticate every request to the server,
How will the filter validate the token? (Just validating the signature is enough?)
If someone else stolen the token and make rest call, how will I verify that.
How will I by-pass the login request in the filter? Since it doesn't have authorization header.
Here is a filter that can do what you need :
public class JWTFilter extends GenericFilterBean {
private static final Logger LOGGER = LoggerFactory.getLogger(JWTFilter.class);
private final TokenProvider tokenProvider;
public JWTFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException,
ServletException {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = this.resolveToken(httpServletRequest);
if (StringUtils.hasText(jwt)) {
if (this.tokenProvider.validateToken(jwt)) {
Authentication authentication = this.tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(servletRequest, servletResponse);
this.resetAuthenticationAfterRequest();
} catch (ExpiredJwtException eje) {
LOGGER.info("Security exception for user {} - {}", eje.getClaims().getSubject(), eje.getMessage());
((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
LOGGER.debug("Exception " + eje.getMessage(), eje);
}
}
private void resetAuthenticationAfterRequest() {
SecurityContextHolder.getContext().setAuthentication(null);
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(SecurityConfiguration.AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
String jwt = bearerToken.substring(7, bearerToken.length());
return jwt;
}
return null;
}
}
And the inclusion of the filter in the filter chain :
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
public final static String AUTHORIZATION_HEADER = "Authorization";
#Autowired
private TokenProvider tokenProvider;
#Autowired
private AuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.authenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JWTFilter customFilter = new JWTFilter(this.tokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
// #formatter:off
http.authorizeRequests().antMatchers("/css/**").permitAll()
.antMatchers("/images/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/authenticate").permitAll()
.anyRequest().fullyAuthenticated()
.and().formLogin().loginPage("/login").failureUrl("/login?error").permitAll()
.and().logout().permitAll();
// #formatter:on
http.csrf().disable();
}
}
The TokenProvider class :
public class TokenProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TokenProvider.class);
private static final String AUTHORITIES_KEY = "auth";
#Value("${spring.security.authentication.jwt.validity}")
private long tokenValidityInMilliSeconds;
#Value("${spring.security.authentication.jwt.secret}")
private String secretKey;
public String createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream().map(authority -> authority.getAuthority()).collect(Collectors.joining(","));
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime expirationDateTime = now.plus(this.tokenValidityInMilliSeconds, ChronoUnit.MILLIS);
Date issueDate = Date.from(now.toInstant());
Date expirationDate = Date.from(expirationDateTime.toInstant());
return Jwts.builder().setSubject(authentication.getName()).claim(AUTHORITIES_KEY, authorities)
.signWith(SignatureAlgorithm.HS512, this.secretKey).setIssuedAt(issueDate).setExpiration(expirationDate).compact();
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(token).getBody();
Collection<? extends GrantedAuthority> authorities = Arrays.asList(claims.get(AUTHORITIES_KEY).toString().split(",")).stream()
.map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
LOGGER.info("Invalid JWT signature: " + e.getMessage());
LOGGER.debug("Exception " + e.getMessage(), e);
return false;
}
}
}
Now to answer your questions :
Done in this filter
Protect your HTTP request, use HTTPS
Just permit all on the /login URI (/authenticate in my code)
I will focus in the general tips on JWT, without regarding code implemementation (see other answers)
How will the filter validate the token? (Just validating the signature is enough?)
RFC7519 specifies how to validate a JWT (see 7.2. Validating a JWT), basically a syntactic validation and signature verification.
If JWT is being used in an authentication flow, we can look at the validation proposed by OpenID connect specification 3.1.3.4 ID Token Validation. Summarizing:
iss contains the issuer identifier (and aud contains client_id if using oauth)
current time between iat and exp
Validate the signature of the token using the secret key
sub identifies a valid user
If someone else stolen the token and make rest call, how will I verify that.
Possesion of a JWT is the proof of authentication. An attacker who stoles a token can impersonate the user. So keep tokens secure
Encrypt communication channel using TLS
Use a secure storage for your tokens. If using a web front-end consider to add extra security measures to protect localStorage/cookies against XSS or CSRF attacks
set short expiration time on authentication tokens and require credentials if token is expired
How will I by-pass the login request in the filter? Since it doesn't have authorization header.
The login form does not require a JWT token because you are going to validate the user credential. Keep the form out of the scope of the filter. Issue the JWT after successful authentication and apply the authentication filter to the rest of services
Then the filter should intercept all requests except the login form, and check:
if user authenticated? If not throw 401-Unauthorized
if user authorized to requested resource? If not throw 403-Forbidden
Access allowed. Put user data in the context of request( e.g. using a ThreadLocal)
Take a look at this project it is very good implemented and has the needed documentation.
1. It the above project this is the only thing you need to validate the token and it is enough. Where token is the value of the Bearer into the request header.
try {
final Claims claims = Jwts.parser().setSigningKey("secretkey")
.parseClaimsJws(token).getBody();
request.setAttribute("claims", claims);
}
catch (final SignatureException e) {
throw new ServletException("Invalid token.");
}
2. Stealing the token is not so easy but in my experience you can protect yourself by creating a Spring session manually for every successfull log in. Also mapping the session unique ID and the Bearer value(the token) into a Map (creating a Bean for example with API scope).
#Component
public class SessionMapBean {
private Map<String, String> jwtSessionMap;
private Map<String, Boolean> sessionsForInvalidation;
public SessionMapBean() {
this.jwtSessionMap = new HashMap<String, String>();
this.sessionsForInvalidation = new HashMap<String, Boolean>();
}
public Map<String, String> getJwtSessionMap() {
return jwtSessionMap;
}
public void setJwtSessionMap(Map<String, String> jwtSessionMap) {
this.jwtSessionMap = jwtSessionMap;
}
public Map<String, Boolean> getSessionsForInvalidation() {
return sessionsForInvalidation;
}
public void setSessionsForInvalidation(Map<String, Boolean> sessionsForInvalidation) {
this.sessionsForInvalidation = sessionsForInvalidation;
}
}
This SessionMapBean will be available for all sessions. Now on every request you will not only verify the token but also you will check if he mathces the session (checking the request session id does matches the one stored into the SessionMapBean). Of course session ID can be also stolen so you need to secure the communication. Most common ways of stealing the session ID is Session Sniffing (or the Men in the middle) and Cross-site script attack. I will not go in more details about them you can read how to protect yourself from that kind of attacks.
3. You can see it into the project I linked. Most simply the filter will validated all /api/* and you will login into a /user/login for example.

How to get request attributes in authentication-success-handler

I am trying to do few things in authentication-success-handler and I need to access few values which was part of initial request data being posted to Spring security.
I am posting following information when user trying to do login
j_username
j_password
storeCode
Spring security is able to authenticate user successfully and is calling "authentication-success-handler".
public class WebshopAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler
{
public WebshopAuthenticationSuccessHandler() {
}
#Override
public void onAuthenticationSuccess(final HttpServletRequest request,
final HttpServletResponse response, final Authentication authentication)
throws IOException, ServletException {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
request.getAttribute( "storeCode" );
attr.getRequest().getAttribute( "storeCode" );
}
}
But in all way, I am not able to get value of storeCode and its coming as null.
Not sure what I am doing wrong.
I am assuming that Spring is creating a new instance of Request and response while calling onAuthenticationSuccess, but how can I pass/ retrieve values which passed passed from the login page?
If the data is from an HTTP POST request, you should be using getParameter, not getAttribute. Attributes are server-side state only, not submitted by the client.

Resources