JWT authentication with spring and angular with null header - java

I am trying to do JWT token authentication using spring boot and angular. After login bearer token is created but after that in JWTAuthorizationFilter i am getting null header and because of that it return anonymousUser. Please tell me why i am getting null header.
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
private CustomUserDetailService customUserDetailService;
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues())
.and().csrf().disable()
.authorizeRequests()
.antMatchers("/**").permitAll()
.antMatchers("/manage/**").hasRole("ADMIN")
.antMatchers("/").hasRole("USER")
.and()
.exceptionHandling()
.accessDeniedPage("/access-denied")
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager(), customUserDetailService));
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailService).passwordEncoder(new
BCryptPasswordEncoder());
}
}
JWTAuthenticationFilter.java
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
UserDetail user = new ObjectMapper().readValue(request.getInputStream(), UserDetail.class);
return this.authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
String username = ((org.springframework.security.core.userdetails.User) authResult.getPrincipal()).getUsername();
String token = Jwts
.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
String bearerToken = TOKEN_PREFIX + token;
System.out.println(bearerToken);
response.getWriter().write(bearerToken);
response.addHeader(HEADER_STRING, bearerToken);
}
}
JWTAuthorizationFilter.java
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
private final CustomUserDetailService customUserDetailService;
public JWTAuthorizationFilter(AuthenticationManager authenticationManager, CustomUserDetailService customUserDetailService) {
super(authenticationManager);
this.customUserDetailService = customUserDetailService;
}
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String header = request.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authenticationToken = getAuthenticationToken(request);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthenticationToken(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token == null) return null;
String username = Jwts.parser().setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody()
.getSubject();
UserDetails userDetails = customUserDetailService.loadUserByUsername(username);
return username != null ?
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())
: null;
}
}
CustomUserDetailService.java
#Component
public class CustomUserDetailService implements UserDetailsService {
private List<GrantedAuthority> role;
#Autowired
private UserDAO userDAO;
/*
* #Autowired public CustomUserDetailService(UserRepository userRepository) {
* this.userRepository = userRepository; }
*/
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = Optional.ofNullable(userDAO.getByEmail(username))
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
List<GrantedAuthority> authorityListAdmin = AuthorityUtils.createAuthorityList("ROLE_ADMIN");
List<GrantedAuthority> authorityListUser = AuthorityUtils.createAuthorityList("ROLE_USER");
if (user.getRole() == "admin") {
role = authorityListAdmin;
} else {
role = authorityListUser;
}
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), role);
}
}
Userinfo.java
private String email;
private String role;
private String password;
Controller
#RequestMapping(value = "/login")
public ModelAndView login(
#RequestParam(name = "error", required = false) String error,
#RequestParam(name = "logout", required = false) String logout,
HttpServletRequest request,
HttpServletResponse response) {
ModelAndView mv = new ModelAndView("login");
HttpSession session= request.getSession(false);
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
System.out.println("auth ===" + auth);
System.out.println("logout ===" + logout);
return mv;
}
This is the output on console:
Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJidW50QGdtYWlsLmNvbSIsImV4cCI6MTU5NjExMjcwM30.fBFMDO--8Q_56LT_qbioiT6p3BOxk3L9OrPVTw5EGbf7oJ0ky7W7PuahIYcdjYSL6-OsHY6qq8tPEetlJO7nEg
auth ===org.springframework.security.authentication.AnonymousAuthenticationToken#823df96a: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
Please tell me what i am missing here.

Your JWTAuthenticationFilter that extends UsernamePasswordAuthenticationFilter overrides successfulAuthentication method which by default calls this line:
SecurityContextHolder.getContext().setAuthentication(authResult);
Your implementation do not have this line so after processing of this filter you still do not have Authentication in Spring context. The next filter that is called is your JWTAuthorizationFilter which tries to read header from same request object as in previous filter. JWTAuthenticationFilter sets this header in response object not in request object. So basically you ends up without authentication because if (header == null || !header.startsWith(TOKEN_PREFIX)) is always true after login flow.

First thing, in the authentication filter token generated and set on the HttpServletResponse header not on the request object's header. Then next the authorization filter checking the request header for token, so there may be the issue of null happened.
Usually authentication and authorization will not be chained like this, but don't know regarding the actual use case you were trying to implement.

Related

Spring Boot + jjwt security return nothing on bad request

my problem is that I get nothing in response after sending requests with wrong login credentials or when accessing the endpoint without authentication.
This is how it looks when I send a good request:
Good Response screen
And this is how it looks when the request body is invalid:
Invalid credentials request
MyUserDetails class:
public class MyUserDetailService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User("foo", "foo", List.of());
}
}
Security configure class:
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailService myUserDetailService;
#Autowired
private JwtFilter jwtFilter;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailService);
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/auth").permitAll()
.anyRequest().authenticated()
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable();
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Jwt Filter class:
#Component
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JwtUtil jwtUtil;
#Autowired
private MyUserDetailService myUserDetailService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
String username = null;
String requestToken = null;
if(authHeader != null && authHeader.startsWith("Bearer ")) {
requestToken = authHeader.substring(7);
username = jwtUtil.getUsernameFromToken(requestToken);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = myUserDetailService.loadUserByUsername(username);
if(jwtUtil.validateToken(requestToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(request, response);
}
And finally my controller:
#PostMapping("/auth")
public AuthResponse auth(#RequestBody AuthRequest authRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
);
} catch (BadCredentialsException e) {
throw new Exception("Invalid Credentials", e);
}
final UserDetails userDetails = myUserDetailService.loadUserByUsername(authRequest.getUsername());
final String token = jwtUtil.generateToken(userDetails);
return new AuthResponse(token);
}
EDIT:
I forgot to paste dependancies.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'javax.xml.bind:jaxb-api:2.2.4'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
I also have AuthRequest and AuthResponse class which holds username, password, jwt token and all the constructors, getters and setters ( not worth to past it here imo)
You got to throw an exception while validating(following is what I use, just grab the concept, your classes and libraries might be different):
Jwts.parser().setSigningKey(your_jwt_secret).parseClaimsJws(token);
so if your token cannot get parsed throw this and send an error response:
httpServletResponse.setContentType("application/json");
httpServletResponse.getWriter().write("your_custom_error");
httpServletResponse.getWriter().flush();

Spring security custom UsernamePasswordAuthenticationFilter not replacing default UsernamePasswordAuthenticationFilter

I'm trying to implement my own UsernamePasswordAuthenthicationFilter that authenticates every request from my frontend using firebase auth.
public class FireBaseAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
.
.
//Assigning roles happens here
List<GrantedAuthority> authorities = new ArrayList<>();
if (user.getCustomClaims() != null) {
if (user.getCustomClaims().get("boss").equals("true")) {
authorities.add(new SimpleGrantedAuthority("boss"));
}
if (user.getCustomClaims().get("admin").equals("true")) {
authorities.add(new SimpleGrantedAuthority("admin"));
}
if (user.getCustomClaims().get("office").equals("true")) {
authorities.add(new SimpleGrantedAuthority("office"));
}
if (user.getCustomClaims().get("warehouse").equals("true")) {
authorities.add(new SimpleGrantedAuthority("warehouse"));
}
if (user.getCustomClaims().get("store").equals("true")) {
authorities.add(new SimpleGrantedAuthority("store"));
}
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getUid(), authorities));
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
user.getEmail(),
user.getUid(),
authorities
);
}
filterChain.doFilter(request, response);
}
}
Then i try and replace the default auth in my security config:
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().disable().csrf().disable().httpBasic().disable().formLogin().disable()
.addFilter(new FireBaseAuthenticationFilter())
.sessionManagement().sessionCreationPolicy(STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated();
}
}
But for some reason my custom filter is never called during runtime? What am I missing here?
Example :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().and().authorizeRequests()
.antMatchers(HttpMethod.POST, ChallengeConstant.AUTHORIZE_ENDPOINT).permitAll()
.antMatchers(HttpMethod.POST, ChallengeConstant.TOKEN_ENDPOINT).permitAll()
.antMatchers(HttpMethod.GET, "/*").permitAll()
.antMatchers(HttpMethod.GET, "/assets/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JWTFilter(userService, objectMapper), UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
If you want to validate token :
#AllArgsConstructor
public class JWTFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JWTFilter.class);
private final UserService userService;
private final ObjectMapper objectMapper;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = httpServletRequest.getHeader(ChallengeConstant.AUTH_HEADER);
if (token != null) {
LOGGER.info("The request is authenticated. Performing Token validity");
String userName;
try {
userName = JWT.require(Algorithm.HMAC512(ChallengeConstant.DUMMY_SIGN.getBytes()))
.build()
.verify(token.replace(ChallengeConstant.TOKEN_PREFIX, ""))
.getSubject();
} catch (JWTVerificationException ex) {
LOGGER.warn(String.format("Token is not valid. Token: %s", token), ex);
generateErrorResponse(httpServletResponse, ExceptionResponse.UNAUTHORIZED);
return;
}
LOGGER.info("Token is valid for username: {}", userName);
try {
UserEntity userEntity = userService.findUserByName(userName);
List<GrantedAuthority> authList = userEntity.getAuthorizations()
.stream()
.map(authorizationEntity -> new SimpleGrantedAuthority(authorizationEntity.getAuth()))
.collect(Collectors.toList());
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(userEntity.getUserName(), userEntity.getPassword(), authList));
LOGGER.debug("User has been found by given username: {} with authorities: {}", userName, authList.toString());
} catch (NotFoundException ex) {
LOGGER.warn("User couldn't be found with given username: {}", userName);
generateErrorResponse(httpServletResponse, ExceptionResponse.NOT_FOUND);
return;
}
}
LOGGER.info("The request is NOT authenticated. It will continue to request chain.");
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private void generateErrorResponse(HttpServletResponse httpServletResponse, ExceptionResponse exceptionResponse) throws IOException {
LOGGER.trace("Generating http error response");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpServletResponse.setStatus(exceptionResponse.getStatus().value());
ErrorResource errorResource = new ErrorResource(exceptionResponse.getCode(),
exceptionResponse.getMessage());
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(errorResource));
LOGGER.trace("Error response is {}", errorResource.toString());
httpServletResponse.getWriter().flush();
}
I think you can validate in Filter and return error response if it is not valid.
Your custom implementation extends the UsernamePasswordAuthenticationFilter (which in its turn extends the AbstractAuthenticationProcessingFilter). The UsernamePasswordAuthenticationFilter, by default, is used for .formLogin authentication, handling the default AntRequestMatcher "/login". If you use a different protected endpoint, the filter's attemptAuthentication() method never gets action. So, if you want to use a different matcher (a different protected endpoint), you have to override the default AntRequestMatcher. For instance, you can do so within your custom filter constructor, by using something like that:
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/signin", "GET"));

Spring Cloud Netflix Zuul routing with account service

I am developing a microservices web application.
I have this microservices:
frontEnd service (all html files)
Account service (with Spring Security)
ZuulGateway service
Demo service (a simple rest controller that return a string)
EurekaServer service
My account service works. The login phase works and the JWT creation works. I save it in the header of the response:
response.addHeader("Authorization", "Bearer " + token);
This is the Ajax code in the frontEnd:
/* sign in submit function */
$("#submit").click(function(e) {
e.preventDefault();
$.ajax({ /* Ajax call to AccountMicroservice for login */
url : 'http://localhost:8762/token/generate-token',
type : "POST",
data : {
username : $("#username").val(),
password : $("#password").val()
},
success : function(data) {
console.log(data.token);
window.location.href = 'http://localhost:8762/test';
},
error : function(result) {
alert("Sign in failed!");
console.log(result);
}
});
});
The problems arose with the addition of Spring Cloud Netflix Zuul. The gateway server port is 8762.
Each request to 'http://localhost:8762/token/generate-token' return a 401 error.
These are the Zuul gateway service. I omitted the import.
application.properties:
server.port=8762
spring.application.name=gateway-service
eureka.client.serviceUrl.defaultZone=${EUREKA_SERVER_URL:http://localhost:8761/eureka}
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
zuul.routes.test-service.path=/test/**
zuul.routes.test-service.service-id=test-service
zuul.routes.account-service.path=/token/**
zuul.routes.account-service.service-id=account-service
zuul.routes.auth-service.sensitive-headers=Cookie,Set-Cookie
webSecurityConfig:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.addFilterAfter(new JwtTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
// allow all who are accessing "auth" service
.antMatchers(HttpMethod.POST, "/token/**", "/signup").permitAll()
// Any other request must be authenticated
.anyRequest().authenticated();
}
//The CORS filter bean - Configures allowed CORS any (source) to any
//(api route and method) endpoint
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin(CorsConfiguration.ALL);
//config.addAllowedHeaders(Collections.singletonList(CorsConfiguration.ALL));
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return source;
}
}
JwtTokenUtil:
#Component
public class JwtTokenUtil implements Serializable {
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
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(SIGNING_KEY)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(Authentication authentication) {
final String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(SignatureAlgorithm.HS256, SIGNING_KEY)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000))
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (
username.equals(userDetails.getUsername())
&& !isTokenExpired(token));
}
UsernamePasswordAuthenticationToken getAuthentication(final String token, final Authentication existingAuth, final UserDetails userDetails) {
final JwtParser jwtParser = Jwts.parser().setSigningKey(SIGNING_KEY);
final Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
final Claims claims = claimsJws.getBody();
final Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
}
}
JwtTokenAuthenticationFilter:
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// 1. get the authentication header. Tokens are supposed to be passed in the authentication header
String header = request.getHeader(HEADER_STRING);
String username = null;
String authToken = null;
// 2. validate the header and check the prefix
if (header != null && header.startsWith(TOKEN_PREFIX)) {
// 3. Get the token
authToken = header.replace(TOKEN_PREFIX,"");
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
System.out.println("\n\n\n\n\n\n\n\n\n\n" + username + "\n\n\n\n\n\n\n\n\n\n");
} catch (IllegalArgumentException e) {
logger.error("c'รจ stato un errore durante il reperimento dello username dal token", e);
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 4. Validate the token
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
//UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
UsernamePasswordAuthenticationToken authentication = jwtTokenUtil.getAuthentication(authToken, SecurityContextHolder.getContext().getAuthentication(), userDetails);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
logger.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
// go to the next filter in the filter chain
chain.doFilter(request, response);
}
}
JwtAuthenticationEntryPoint:
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
String json = String.format("{\"message\": \"%s\"}", authException.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
}
}
constants:
public class Constants {
public static final long ACCESS_TOKEN_VALIDITY_SECONDS = 5*60*60;
public static final String SIGNING_KEY = "devglan123r";
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
public static final String AUTHORITIES_KEY = "scopes";
}

spring security permitAll() not working for JWT Authentication filter

The issue is with the app uses custom JWT authentication filter which extends UsernamePasswordAuthenticationFilter which accepts user credentials and generates a long-lived JWT in return.
The issue seems to be with permitAll() which should bypass custom Authorization filter.However in debug mode I could see call to custom JwtAuthorizationFilter first instead of custom JwtAuthenticationFilter Filter which eventually results with 403 forbidden Access denied response.
Note the .antMatchers(HttpMethod.POST, "/login").permitAll() line. /login endpoint should be accessible without JWT since the JWT has not yet been generated when the user has not yet logged in.
Below is my code
JwtAuthenticationFilter.java
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
//
private AuthenticationManager authenticationManager;
private final static UrlPathHelper urlPathHelper = new UrlPathHelper();
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
setFilterProcessesUrl("/login");
}
/**
* Trigger when we issue POST request to login / we also need to pass in
* {"username: " username, "password": password} in the request body
*/
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
// Grab credentials and map them to login viewmodel
LoginViewModel credentials = null;
try {
credentials = new ObjectMapper().readValue(request.getInputStream(), LoginViewModel.class);
} catch (IOException e) {
e.printStackTrace();
}
// Create login token
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
credentials.getUsername(), credentials.getPassword(), new ArrayList<>());
// Authenciate user
Authentication auth = authenticationManager.authenticate(authenticationToken);
return auth;
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// Grab principal
UserPrincipal principal = (UserPrincipal) authResult.getPrincipal();
// Create JWT Token
String token = JWT.create().withSubject(principal.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + JwtProperties.EXPIRATION_TIME))
.sign(HMAC512(JwtProperties.SECRET.getBytes()));
// add token in response
response.addHeader(JwtProperties.HEADER_STRING, JwtProperties.TOKEN_PREFIX + token);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
logger.debug("failed authentication while attempting to access "
+ urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
// Add more descriptive message
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed");
}
}
JwtAuthorizationFilter.java
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private UserRepository userRepository;
public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserRepository userRepository) {
super(authenticationManager);
this.userRepository = userRepository;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//Read the Authorization header, where the JWT token should be
String header = request.getHeader(JwtProperties.HEADER_STRING);
//If header does not contain BEARER or is null delegate to Spring impl and exit
if (header == null || !header.startsWith(JwtProperties.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// If header is present, try grab user principal from db and perform authorization
Authentication authentication = getUsernamePasswordAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Continue filter execution
chain.doFilter(request, response);
}
private Authentication getUsernamePasswordAuthentication(HttpServletRequest request){
String token = request.getHeader(JwtProperties.HEADER_STRING)
.replace(JwtProperties.TOKEN_PREFIX, "");
if(token !=null){
//parse the token validate it
String userName = JWT.require(Algorithm.HMAC512(JwtProperties.SECRET.getBytes()))
.build()
.verify(token)
.getSubject();
// Search in the DB if we find the user by token subject(username)
// If so, then grab user details and create auth token using username, pass, authorities/roles
if(userName != null){
User user = userRepository.findByUsername(userName);
UserPrincipal principal = new UserPrincipal(user);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, null, principal.getAuthorities());
return authenticationToken;
}
return null;
}
return null;
}
}
SecurityConfiguration.java
Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private UserPrincipalDetailsService userPrincipalDetailsService;
private UserRepository userRepository;
public SecurityConfiguration(UserPrincipalDetailsService userPrincipalDetailsService,
UserRepository userRepository) {
this.userPrincipalDetailsService = userPrincipalDetailsService;
this.userRepository = userRepository;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// remove csrf state in session because in jwt do not need them
.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers("/api/public/management/*").hasRole("MANAGER").antMatchers("/api/public/admin/*")
.hasRole("ADMIN").anyRequest().authenticated().and()
// add jwt filters (1. authentication, 2. authorization_)
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager(), this.userRepository));
// configure access rules
}
#Bean
DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService((UserDetailsService) this.userPrincipalDetailsService);
return daoAuthenticationProvider;
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Request,Response
Can someone suggest whats wrong here..Appreciate your help..Thanks in advance..!!!
It seems that your path is wrong. When you look at your body you can see that the path shows following: /login%0A. This seems that you have an extra character at the end of your URL. Just try to rewrite the URL in Postman.
please consider to use shouldNotFilter method from BasicAuthenticationFilter. It extends OncePerRequestFilter so you can use it in filtering class as below:
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
// code here
}

Spring Boot Security Context return null when using a JWT token

I have created a REST API that require a authentication with JWT.
My implementation is very similar with the code found on https://auth0.com/blog/securing-spring-boot-with-jwts/
When I try to return the current user, I always receive a null return.
My code:
Websecurity:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
// login
.antMatchers(HttpMethod.POST, "/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilterBefore(new JWTLoginFilter(
"/login", authenticationManager(), logService), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
JWTAuthenticationFilter:
public class JWTAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest req,
ServletResponse res,
FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest)req);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(req, res);
}
}
I don't included all the code of JWT authentication, because JWT is working ok, user access too.
I believe the problem is in the filter or some configuration.
Then, I made a facade to get the current user on a service or controller, with the following code (method 4 on http://www.baeldung.com/get-user-in-spring-security):
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
but this don't worked.
- SecurityContextHolder.getContext() returned org.springframework.security.core.context.SecurityContextImpl#ffffffff: Null authentication.
- SecurityContextHolder.getContext().getAuthentication() returned null object.
Update (and solution):
In my controller, if I use this code:
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
I can get the current user, but, in my service, the exact same code don't work.
But then, I remember that SecurityContext is "lost" on another thread (source: https://docs.spring.io/spring-security/site/docs/current/reference/html/concurrency.html), and my service is async
#Async
public CompletableFuture<Optional<ViewUserDto>> findByLogin(String login) throws InterruptedException {
...
}
So, using the code found here: https://stackoverflow.com/a/40347437/4794469, everything works correctly.
I don't known if this can bring any side effects for my code yet (all unit tests worked)
I have worked in an application that has a similar authorization flow as yours:
WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationProvider provider;
#Autowired
private TokenAuthenticationService tokenService;
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.authenticationProvider(provider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().disable();
http.csrf().disable();
http.authorizeRequests().antMatchers(HttpMethod.POST, "/v1/users", "/v1/oauth/token").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new OAuthTokenFilter("/v1/oauth/token", authenticationManager(), tokenService), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new AuthorizationFilter(tokenService), UsernamePasswordAuthenticationFilter.class);
}
}
AbstractAuthenticationProcessingFilter
public class OAuthTokenFilter extends AbstractAuthenticationProcessingFilter {
private final ObjectMapper MAPPER = new ObjectMapper();
private TokenAuthenticationService service;
public OAuthTokenFilter(String url, AuthenticationManager manager, TokenAuthenticationService service) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(manager);
this.service = service;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
Login login = MAPPER.readValue(request.getInputStream(), Login.class);
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(login.getUsername(), login, Arrays.asList());
return getAuthenticationManager().authenticate(token);
}
#Override
protected void successfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authentication) throws IOException, ServletException {
User credentials = (User) authentication.getPrincipal();
String token = service.jwt(credentials);
String json = MAPPER.writeValueAsString(new AuthorizationToken(token, "Bearer"));
response.addHeader("Content-Type", "application/json");
response.getWriter().write(json);
response.flushBuffer();
}
}
GenericFilterBean
public class AuthorizationFilter extends GenericFilterBean {
private TokenAuthenticationService service;
public AuthorizationFilter(TokenAuthenticationService service) {
this.service = service;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
Authentication authentication = service.getAuthentication((HttpServletRequest)request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
}
TokenAuthenticationService
#Service
public class TokenAuthenticationService {
public static final String JWT_SECRET_ENV = "JWT_SECRET";
public static final String ISSUER = "my issuer";
public static final String ROLE_CLAIM = "role";
public static final String THIRDY_PARTY_ID_CLAIM = "thirdy_party_id";
public static final String TOKEN_PREFIX = "Bearer";
public static final String HEADER = "Authorization";
#Autowired
private Environment environment;
public Authentication getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER);
String secret = environment.getProperty(JWT_SECRET_ENV);
if (token != null) {
try {
String bearer = token.replace(TOKEN_PREFIX, "").trim();
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(ISSUER)
.build();
DecodedJWT jwt = verifier.verify(bearer);
User user = new User();
user.setId(jwt.getSubject());
user.setThirdPartyId(jwt.getClaim(THIRDY_PARTY_ID_CLAIM).asString());
user.setRole(jwt.getClaim(ROLE_CLAIM).asString());
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole());
return new UsernamePasswordAuthenticationToken(user, null, authorities);
} catch (Exception e){
e.printStackTrace(System.out);
}
}
return null;
}
}
And then, the controller:
#RestController
public class UserController {
#ResponseBody
#GetMapping("/v1/users/{id}")
#PreAuthorize("hasAuthority('USER')")
public User get(#PathVariable("id") String id, Authentication authentication) {
User user = (User) authentication.getPrincipal();
return user;
}
}
I faced similar issue when i was enabling JWT on my web app.
You need: "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files".
Please download the this package from the below URL and replace US_export_policy.jar, local_policy.jar (\jre\lib\security)
If it is still not working, then you need to replace the above jar files in the location \lib\security
http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

Categories

Resources