How to get JWT authentication to work on spring boot 3 - spring-boot

I've been trying to setup spring security on spring boot 3.0.2 to use JWT, but so far, it hasn't worked.
Whenever I start my spring boot API app, none of my endpoints are exposed except /actuator. And I keep getting a default password given to me.
Here's my security config class:
#Configuration
#EnableMethodSecurity
#RequiredArgsConstructor
public class ApiSecurityConfig {
private final AuthenticationTokenFilter authenticationTokenFilter;
private final UserDetailsServiceImpl userDetailsService;
private final CustomAuthenticationEntryPoint entryPoint;
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling(e -> e.authenticationEntryPoint(entryPoint))
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/merchant/auth/**").permitAll()
.requestMatchers("/swagger-ui.html").permitAll()
.anyRequest().authenticated())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.authenticationProvider(authenticationProvider());
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
EDIT: Here's my authentication token filter:
#Slf4j
#Configuration
public class AuthenticationTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String jwt = "";
String headerAuthorization = request.getHeader("Authorization");
if (StringUtils.hasText(headerAuthorization) && headerAuthorization.startsWith("Bearer ")) {
jwt = headerAuthorization.substring(7, headerAuthorization.length());
}
try {
if (!jwt.isEmpty() && jwtUtils.validateJwt(jwt)) {
String username = jwtUtils.getUsernameFromJwt(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"status\": \"false\", \"message\": \"Access denied\", \"data\": \"Invalid token\"}");
response.getWriter().flush();
return;
}
} catch (InvalidKeySpecException e) {
log.error("Invalid key spec exception thrown:", e);
} catch (NoSuchAlgorithmException e) {
log.error("No such algorithm exception thrown:", e);
}
}
}

Thanks to to Tangrunze I was able to see that I missed out the filterChain.doFilter(request, response);
But also, I discovered that I had a componentScan annotation in my main class because I was trying to pick up some components in a different maven project.
Apparently, spring security auto configuration kicks in if you scan other base packages before the resident package where your config is in.

Related

Error occured when Spring Security CustomLoginFilter is applied! somebody help me

I want to apply CustomLoginProcessingFilter in my application but i can't figure out how it works!
I'm using Spring boot 2.7.2, the lastest version when i started studying this.
here's my code
Another custom providers or custom detail services work so well.
But, once i enroll new bean fore login processing filter, AjaxLoginProcessingFilter, they tell me that i need to specify authentitcationManager!
so, i added at filterChain method this in SecurityConfig.java, but it doesn't work.
enter image description here
/**
----------------- SecurityConfig -------------------------
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#RequiredArgsConstructor
public class SecurityConfig {
private final CustomAuthenticationSuccessHandler authenticationSuccessHandler;
private final CustomAuthenticationFailureHandler authenticationFailureHandler;
private final FormAuthenticationDetailsSource authenticationDetailsSource;
private final AjaxLoginProcessingFilter ajaxLoginProcessingFilter;
#Bean
AuthenticationManager authenticationManager(AuthenticationManagerBuilder builder) throws Exception {
return builder.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(registry -> {
registry.antMatchers("/","/users","user/login/**","/login*").permitAll()
.antMatchers("/mypage").hasRole("USER")
.antMatchers("/messages").hasRole("MANAGER")
.antMatchers("/config").hasRole("ADMIN")
.anyRequest().authenticated();
}).formLogin(login -> {
login.loginPage("/login")
.loginProcessingUrl("/login_proc")
.defaultSuccessUrl("/")
.authenticationDetailsSource(authenticationDetailsSource)
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.permitAll();
}).exceptionHandling(exception -> {
exception.accessDeniedHandler(accessDeniedHandler());
})
.addFilterBefore(ajaxLoginProcessingFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
#Bean
public AccessDeniedHandler accessDeniedHandler(){
CustomAccessDeniedHandler accessDeniedHandler = new CustomAccessDeniedHandler();
accessDeniedHandler.setErrorPage("/denied");
return accessDeniedHandler;
}
#Bean
public WebSecurityCustomizer webSecurityCustomizer() throws Exception {
return (web) -> web.ignoring().antMatchers("/resources/**");
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/*Explanation:
In the old version you inject AuthenticationManagerBuilder, set userDetailsService, passwordEncoder and build it.
But authenticationManager is already created in this step.
It is created the way we wanted (with userDetailsService and the passwordEncoder).
https://stackoverflow.com/questions/72381114/spring-security-upgrading-the-deprecated-websecurityconfigureradapter-in-spring
*/
#Bean
CustomUserDetailsService customUserDetailsService() {
return new CustomUserDetailsService();
}
#Bean
public AuthenticationProvider authenticationProvider() {
return new CustomAuthenticationProvider();
}
}
-++------------------ AjaxLoginProcessingFilter ---------------------
#Component("loginProcessingFilter")
public class AjaxLoginProcessingFilter extends AbstractAuthenticationProcessingFilter {
private ObjectMapper objectMapper = new ObjectMapper();
public AjaxLoginProcessingFilter() {
super(new AntPathRequestMatcher("/api/login"));
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
if(isAjax(request)){
throw new IllegalStateException("Authentication is not supported");
}
AccountDto accountDto = objectMapper.readValue(request.getReader(), AccountDto.class);
if(StringUtils.isEmpty(accountDto.getUsername()) || StringUtils.isEmpty(accountDto.getPassword())){
throw new IllegalArgumentException("Username or password is not empty");
}
AjaxAuthenticationToken authenticationToken = new AjaxAuthenticationToken(accountDto.getUsername(), accountDto.getPassword());
return getAuthenticationManager().authenticate(authenticationToken);
}
private boolean isAjax(HttpServletRequest request) throws IOException {
if("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))){
return true;
}
return false;
}
}

Migration to Ldap based authentication from Inmemory authentication

I have to implement a LDAP based authentication and return a JWT token in response which will be used in subsequent request.
I followed this guide InMemory Authentication with JWT to implement InMemory based Authentication. I know and tried standalone code for LDAP authentication and it's working.
While integrating ldap authentication in the code explained in the above link's example,I am getting stuck and not able to move forward.
I am not getting, How to define loadByUsername method of UserDetailsService class in case of ldap authentication.
Looking for some direction/guidance on this to proceed.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class JWTWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtUnAuthorizedResponseAuthenticationEntryPoint jwtUnAuthorizedResponseAuthenticationEntryPoint;
#Autowired
private JwtTokenAuthorizationOncePerRequestFilter jwtAuthenticationTokenFilter;
#Autowired
private Environment env;
#Value("${jwt.get.token.uri}")
private String authenticationPath;
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new LdapAuthenticationProvider(env)).eraseCredentials(false);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(jwtUnAuthorizedResponseAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated();
httpSecurity
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity
.headers()
.frameOptions().sameOrigin() //H2 Console Needs this setting
.cacheControl(); //disable caching
}
#Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity
.ignoring()
.antMatchers(
HttpMethod.POST,
authenticationPath
)
.antMatchers(HttpMethod.OPTIONS, "/**")
.and()
.ignoring()
.antMatchers(
HttpMethod.GET,
"/" //Other Stuff You want to Ignore
)
.and()
.ignoring()
.antMatchers("/h2-console/**/**");//Should not be in Production!
}
#Bean
#Override
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
}
#Component
public class LdapAuthenticationProvider implements AuthenticationProvider
{
private Environment environment;
public LdapAuthenticationProvider(Environment environment) {
this.environment = environment;
}
private LdapContextSource contextSource;
private LdapTemplate ldapTemplate;
private void initContext(Authentication authentication)
{ contextSource = new LdapContextSource();
contextSource.setUrl(environment.getProperty("ldap.server.url"));
//contextSource.setAnonymousReadOnly(true);
contextSource.setUserDn("domain\\uid");
contextSource.setBase("DC=global,DC=comp,DC=org");
contextSource.setPassword("pwd");
contextSource.afterPropertiesSet();
ldapTemplate = new LdapTemplate(contextSource);
ldapTemplate.setIgnorePartialResultException(true);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
initContext(authentication);
Filter filter = new EqualsFilter("sAMAccountName", authentication.getName());
Boolean authenticate = ldapTemplate.authenticate(LdapUtils.emptyLdapName(), filter.encode(), authentication.getCredentials().toString());
if (authenticate)
{
UserDetails userDetails = new User(authentication.getName(), authentication.getCredentials().toString()
, new ArrayList<>());
Authentication auth = new UsernamePasswordAuthenticationToken(userDetails,
authentication.getCredentials().toString(), new ArrayList<>());
return auth;
}
else
{
return null;
}
}
#Override
public boolean supports(Class<?> authentication)
{
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
#Component
public class JwtTokenAuthorizationOncePerRequestFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
private UserDetailsService JwtLdapUserDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Value("${jwt.http.request.header}")
private String tokenHeader;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
logger.debug("Authentication Request For '{}'", request.getRequestURL());
final String requestTokenHeader = request.getHeader(this.tokenHeader);
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
logger.error("JWT_TOKEN_UNABLE_TO_GET_USERNAME", e);
} catch (ExpiredJwtException e) {
logger.warn("JWT_TOKEN_EXPIRED", e);
}
} else {
logger.warn("JWT_TOKEN_DOES_NOT_START_WITH_BEARER_STRING");
}
logger.debug("JWT_TOKEN_USERNAME_VALUE '{}'", username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.JwtLdapUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
Above are some code files (modified for ldap) in the application. Complete code (base of my changes - InMemory based auth+Jwt) is available in the link mentioned above.
Thanks,

Invalid cors request in spring boot

I am running a spring boot application in conjunction with graphql and jwt token. Currently when I am trying to hit one of the endpoints I am getting 'Invalid Cors Request'. Below is the code of config and filter file.Config file:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Autowired
private JwtFilter jwtFilter;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userService);
}
#Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception{
http.csrf().disable().cors().disable().authorizeRequests().antMatchers("/**", "/graphql", "/graphiql", "/graphql/**", "/graphiql/**")
.permitAll().anyRequest().authenticated()
.and().exceptionHandling().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Filter file:
#Component
#Log4j2
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JwtUtil jwtUtil;
#Autowired
private UserService userService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
if(authorizationHeader==null)
throw new ServletException();
String token = null;
String userName = null;
if (authorizationHeader.startsWith("Bearer ")) {
token = authorizationHeader.substring(7);
userName = jwtUtil.extractUsername(token);
}
else
throw new ServletException();
if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(userName);
if (jwtUtil.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
else
throw new ServletException();
}
else
throw new ServletException();
filterChain.doFilter(request, response);
}
}
The rest is a simple graphql project with kickstart implementation of graphql. I only have a couple of queries and I am trying to hit it via Altair extension. Please let me know if any more information is required. Thanks in advance.

Log user information on failed attempt

I want to be able to access my user details when login fail so that I can count the number of times of failed attempt a user has. How can i access the userdetails and save it to my database on failure? I read somewhere that AuthenticationFailureHandler might a be a solution to this but from what I see, it works only with formLogin?
I have a signin endpoint
#CrossOrigin
#RequestMapping(value = "/signin", method = RequestMethod.POST)
#ApiOperation(value = "Sign in endpoint", notes = "You have to provide a valid login request")
public ResponseEntity<?> authenticateUser(#ApiParam(value = "The login request", required = true)
#Valid #RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
// Set authentication so that when /authenticate, we can retrieve the authenticated user
. . .
This is my authentrypointjwt when authenticate fails.
#Component
public class AuthEntryPointJwt implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
logger.info("> AuthEntryPointJwt");
logger.error("Unauthorized error: {}", authException.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
}
}
This is my websecurity
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);
#Autowired
UserDetailsServiceImpl userDetailsService;
#Autowired
private AuthEntryPointJwt unauthorizedHandler;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
LOGGER.info("> AuthenticationManagerBuilder authenticationJwtTokenFilter");
return new AuthTokenFilter();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
LOGGER.info("> AuthenticationManagerBuilder authenticationManagerBuilder");
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
LOGGER.info("> AuthenticationManagerBuilder authenticationManagerBean");
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/test/**").authenticated()
.antMatchers("/signup").hasAuthority("SUPER_ADMIN");
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("HEAD",
"GET", "POST", "PUT", "DELETE", "PATCH"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("*"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Yes. AuthenticationFailureHandler will only be called if you customise the filter for authentication by extending AbstractAuthenticationProcessingFilter like what formLogin does.
But it seems that you are now implementing a customised authentication way using spring-mvc-rest , and you can do the following which equivalents to what AbstractAuthenticationProcessingFilter does to invoke AuthenticationFailureHandler :
#RestController
public void AuthenticateController {
#Autowired AuthenticationFailureHandler failureHandler;
#RequestMapping(value = "/signin", method = RequestMethod.POST)
public ResponseEntity<?> authenticateUser(LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) {
try {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
}catch (AuthenticationException failed) {
failureHandler.onAuthenticationFailure(request, response, failed);
}
}
}
P.S. As you are customising authentication using spring-mvc-rest rather than following the spring security infrastructure to implement it based on the Servlet Filter , I assume that you also need to configure spring security to totally ignore AuthenticateController and no other spring security feature will be applied to it . I normally will follow the spring security infrastructure which customize the authentication process based on Servlet Filter as it is more compatible with the spring security ecosystem.

Spring boot 2+ Could not Autowire. There is more than one bean of 'UserDetailsService'

Hello Everyone i'm new in spring security and jwt. I'm implementing Jwt in my spring boot project to secure user login and i'm using spring boot 2.1.5
and i don't know much about new bean restriction in spring boot 2+ .
I need some help .. here i'm trying to #Autowired UserDetailsService and code run fine ..and result is also fine.. but intellij shows error at
#Autowired UserDetailsService jwtUserDetailsService
saying ... Could not autowire. There is more than one bean of UserDetailsService type.
Can anyone explain me what what happens wrong here why i can't autowired and why and what are the Autowired restriction in spring boot 2+ ?
And thanks in advance
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private JwtFilter jwtFilter;
#Autowired
private UserDetailsService jwtUserDetailsService; // here i got error only
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/api/user/add", "/generate").permitAll().anyRequest().authenticated().and() .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
my customUserDetailService is
#Service
public class JwtUserDetailService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user != null) {
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), new ArrayList<>());
} else {
throw new UsernameNotFoundException("Username does't exists");
}
}
}
My JwtController class which expose restend point to generate jwt token
#CrossOrigin
#RestController
public class JwtController {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtUserDetailService jwtUserDetailService;
#PostMapping(value = "/generate")
public ResponseEntity<?> generateToken(#RequestBody JwtRequest jwtRequest) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(jwtRequest.getUsername(),
jwtRequest.getPassword()));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVAILD_CREDENTIALS", e);
}
final UserDetails userDetails = jwtUserDetailService.loadUserByUsername(jwtRequest.getUsername());
final String token = jwtUtils.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
}
My JwtFilter Class
#Component
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JwtUserDetailService jwtUserDetailService;
#Autowired
private JwtUtils jwtUtils;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
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 = jwtUtils.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailService.loadUserByUsername(username);
if (jwtUtils.validate(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
Other thing as just normal like entity, repository, and some secured restend points
UserDetailsService was provided by spring.
To Autowire you need to configure it with.
#Bean
public UserDetailsService getUserDetails(){
return new JwtUserDetailService(); // Implementation class
}
If you are not interested in Bean Configuration.
you can autowire JwtUserDetailService directly.
#Autowired
private JwtUserDetailService jwtUserDetailsService;
I got the same error in another context. The reason was the Idea donĀ“t know which bean of type 'UserDetailsService' to use.
My solution is through annotation Qualifier:
#Qualifier("beanNameWhichYouWantUse")
#Autowired
private UserDetailsService jwtUserDetailsService;
If use Idea: give mouse point on the error, select from context menu:
"More actions" -> "Add qualifier"
and finally select the bean
You can put this code in application.properties:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration

Resources