I'm currently Implementing Token and Role Base Authentication Using Spring Boot JWT MySQL following the tutoriel [https://github.com/team-learn-programming-yourself/jwt-youtube][1] authentication with Spring Security for REST API using Spring Boot
it produces an error
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| jwtRequestFilter (field private com.example.jwt.service.JwtService com.example.jwt.configuration.JwtRequestFilter.jwtService)
↑ ↓
| jwtService (field private org.springframework.security.authentication.AuthenticationManager com.example.jwt.service.JwtService.authenticationManager)
↑ ↓
| webSecurityConfiguration (field private com.example.jwt.configuration.JwtRequestFilter com.example.jwt.configuration.WebSecurityConfiguration.jwtRequestFilter)
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
the code for the dependencies producing error are as below :
jwtRequestFilter
package com.example.jwt.configuration;
import com.example.jwt.service.JwtService;
import com.example.jwt.util.JwtUtil;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private JwtService jwtService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
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 {
System.out.println("JWT token does not start with Bearer");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = jwtService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
WebSecurityConfiguration
package com.example.jwt.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
private UserDetailsService jwtService;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.cors();
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/authenticate", "/registerNewUser").permitAll()
.antMatchers(HttpHeaders.ALLOW).permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(jwtService).passwordEncoder(passwordEncoder());
}
}
JwtService
package com.example.jwt.service;
import com.example.jwt.dao.UserDao;
import com.example.jwt.entity.JwtRequest;
import com.example.jwt.entity.JwtResponse;
import com.example.jwt.entity.User;
import com.example.jwt.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Set;
@Service
public class JwtService implements UserDetailsService {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDao userDao;
@Autowired
private AuthenticationManager authenticationManager;
public JwtResponse createJwtToken(JwtRequest jwtRequest) throws Exception {
String userName = jwtRequest.getUserName();
String userPassword = jwtRequest.getUserPassword();
authenticate(userName, userPassword);
UserDetails userDetails = loadUserByUsername(userName);
String newGeneratedToken = jwtUtil.generateToken(userDetails);
User user = userDao.findById(userName).get();
return new JwtResponse(user, newGeneratedToken);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.findById(username).get();
if (user != null) {
return new org.springframework.security.core.userdetails.User(
user.getUserName(),
user.getUserPassword(),
getAuthority(user)
);
} else {
throw new UsernameNotFoundException("User not found with username: " username);
}
}
private Set getAuthority(User user) {
Set<SimpleGrantedAuthority> authorities = new HashSet<>();
user.getRole().forEach(role -> {
authorities.add(new SimpleGrantedAuthority("ROLE_" role.getRoleName()));
});
return authorities;
}
private void authenticate(String userName, String userPassword) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userName, userPassword));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
}
CodePudding user response:
Remove this from
WebSecurityConfiguration:@Autowired private JwtRequestFilter jwtRequestFilter;The problem is that to be able to create the
jwtRequestFilter, you first need aJwtService, which is created inWebSecurityConfiguration.jwtRequestFilteris injected into this configuration for no reason (it isn't used to create another@Bean). It will automatically be created by virtue of being a@Component. By using@Autowirein the above manner you have made WebSecurityConfiguration depend on the filter and thus created this circular dependency.As per @M.Deinum's comment, your
JwtServicedepends onAuthenticationManager, which in turns requiresJwtServicewhile it's being built, specifically here:@Autowired public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder.userDetailsService(jwtService).passwordEncoder(passwordEncoder()); }JwtServiceshould only implementUserDetailsService.loadUserByUsername- it should not be responsible for anything else.createJwtToken(JwtRequest)andauthenticate(String, String)are the offenders here. You could just move them to another class, however, (and this could be 3.), depending on why and where and when you want to create such a token, responsibility for the authentication itself should remain with the AuthenticationManager, and token generation used for later password-less authorization could simply depend on a validUserargument, after that authentication has passed. There is no reason whycreateJwtTokenshould contain any call toauthenticateat all. A refined approach is presented here (no affiliation): https://www.toptal.com/spring/spring-security-tutorial
CodePudding user response:
Spring security has had full JWT support since 2018, so my recommendation is the following.
Remove all the custom code since writing custom security is bad practice. Use the built in JWT functionality that comes with spring security.
First define that you want to use the oauth2resource filter but you want the filter to handle jwts instead of a traditional oauth2 token.
@EnableWebSecurity
public class DirectlyConfiguredJwtDecoder extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
}
}
add a custom decoder, that will decode the jwt using the built in Nimbus library that comes with Spring Security. Its easy to configure using the builder pattern NimbusJWTDecoder
Just add it as a bean and spring will automatically pick it up and inject it into the built in jwt filter.
@Bean
public JwtDecoder jwtDecoder() {
// Use builder functions to configure how the JWT should be validated.
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
If you wish to configure scopes and map these as roles in your application you can just add a JwtAuthenticationConverter that will take the scopes and map them to roles with the ROLE_ prefix. If you don't add a converter your scopes will have the SCOPE_ prefix.
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
Please read up on how JWTs in spring security work, there is an entire chapter on how to simple and easy implement the handling of JWTs in spring security.
