How can I solve JWT error in my spring code?
ERROR security.jwt.AuthEntryPointJwt - Unauthorized error: Full authentication is required to acccess this resource
ERROR security.jwt.AuthTokenFilter - Cannot set user authentication {}
There is some error in my WebSecurityConfig.java file showing WebSecurityConfigurerAdapter is deprecated due which my authenticationManagerBean() is also deprecated.
Authcontroler.java Class
#CrossOrigin("*")
#RestController
#RequestMapping("/api/auth")
public class AuthController {
#Autowired
private UserRepo userRepo;
#Autowired
private RoleRepo roleRepo;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private JwtUtils jwtUtils;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private EmailService emailService;
// POST request for adding user
#PostMapping("/register")
public ResponseEntity<?> registerUser(#Valid #RequestBody SignupRequest signupRequest) {
if (userRepo.existsByEmail(signupRequest.getEmail())) {
return ResponseEntity
.badRequest()
.body(new MessageResponse("Error: Email is already in use!"));
}
User user = new User(signupRequest.getEmail(),
signupRequest.getName(),
passwordEncoder.encode(signupRequest.getPassword()),
signupRequest.getAddress());
//signupRequest.setRole(new ArraySet<>(Set.of("ROLE_USER","")));
signupRequest.setRole("ROLE_ADMIN");
String strRoles = signupRequest.getRole();
Set<Role> roles = new HashSet<>();
if (strRoles==null) {
Role userRole = roleRepo.findByRoleName("ROLE_ADMIN")
.orElseThrow(()-> new RuntimeException("Error: role not found"));
roles.add(userRole);
}
else {
// strRoles.forEach(e->{
// switch (e) {
// case "admin":
// Role roleAdmin = roleRepo.findByRoleName("ROLE_ADMIN")
// .orElseThrow(()-> new RuntimeException("Error: role not found"));
// roles.add(roleAdmin);
// break;
//
// default:
// Role roleUser = roleRepo.findByRoleName("ROLE_USER")
// .orElseThrow(()-> new RuntimeException("Error: role not found"));
// roles.add(roleUser);
// break;
}
user.setRoles(roles);
userRepo.save(user);
emailService.sendTextEmail(user.getEmail());
return ResponseEntity
.status(201)
.body(new MessageResponse("User created successfully"));
}
// POST request for validating user
#PostMapping("/authenticate")
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getEmail(),
loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtUtils.generateToken(authentication);
UserDetailsImpl userDetailsImpl = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetailsImpl.getAuthorities()
.stream()
.map(i->i.getAuthority())
.collect(Collectors.toList());
return ResponseEntity.ok(new JwtResponse(jwt,
userDetailsImpl.getId(),
userDetailsImpl.getEmail(),
roles));
}
#GetMapping("/")
public ResponseEntity<?> getUser() {
UserDetailsImpl userDetailsImpl = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication()
.getPrincipal();
Long id = userDetailsImpl.getId();
Optional<User> optional = userRepo.findById(id);
return ResponseEntity.ok(optional.get());
}
}
AuthEntryPointJwt.java Class
#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.error("Unauthorized error: {}", authException.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
final Map<String, Object> body = new HashMap<>();
body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
body.put("error", "Unauthorized");
body.put("message", authException.getMessage());
body.put("path", request.getServletPath());
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), body);
}
}
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsServiceImpl userDetailsService;
#Autowired
private AuthEntryPointJwt unauthorizedHandeler;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// TODO Auto-generated method stub
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
// TODO Auto-generated method stub
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http.cors().and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandeler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests().antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/food/**").permitAll()
.antMatchers("/api/cart/**").permitAll()
.antMatchers("/api/order/**").permitAll()
.antMatchers("/api/payment/**").permitAll()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/api/users/**")
.permitAll().anyRequest().authenticated();
http.headers().frameOptions().disable();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
// super.configure(http);
}
}
AuthTokenFilter.java Class
public class AuthTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private UserDetailsServiceImpl userDetailsServiceImpl;
private static final Logger LOGGER = LoggerFactory.getLogger(AuthTokenFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// TODO Auto-generated method stub
try {
String jwt = parseJwt(request);
if (jwt!=null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUsernameFromJwtToken(jwt);
UserDetails userDetails = userDetailsServiceImpl.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
} catch (Exception e) {
// TODO Auto-generated catch block
LOGGER.error("Cannot set user authentication {}", e);
}
filterChain.doFilter(request, response);
}
private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7, headerAuth.length());
}
return null;
}
}
Thankyou
You need a configuration file that allows users to talk to a login endpoint. Within this endpoint you may authenticate a user to get past the authentication barrier.
Example configuration within spring boot security:
#Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/register").permitAll() // <- allow requests to this reqister endpoint
.anyRequest().authenticated(); // <- all other requests needs full authentication;
http.addFilterAfter(
new AuthTokenFilter(), BasicAuthenticationFilter.class); // <- add your JWT authentication filter into the chain
}
}
Another important step is to integrate your Filter into the filter chain of incomming requests.
You may have a look at this example: Spring Security / Getting Started. Another good resource is always Baeldung Config / Baeldung Filter
Within this configuration you can also integrate your AuthEntryPointJwt.
Related
I'm currently building a standard JwtAuthorizationFilter. I extend the OncePerRequestFilter class for this. Furthermore I have a JwtUtils class, which contains all JWT methods. For example, one method validates the JWT bearer token. However, I keep getting the error that this method (and all others) cannot be invoked because this.jwtUtils is null.
So bassicly I am trying to autowire the JwtUtils classe. But Spring is not giving any instance. Instead it is giving null
Thats my error message
Cannot invoke ... jwt.JwtUtils.validateJwtToken(String)" because "this.jwtUtils" is null
JwtAuthorizationFilter class (the error is throwing here)
#Slf4j
public class JwtAuthorizationFilter extends OncePerRequestFilter {
#Autowired private JwtUtils jwtUtils;
#Autowired private UserDetailsServiceImpl userDetailsService;
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = JwtUtils.resolveToken(request);
System.out.println("before if " + jwt);
if (jwtUtils.validateJwtToken(jwt)) { // ERROR !
String username = jwtUtils.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
JwtAuthorizationFilter.log.error("Cannot set user authentication: {}", e.getMessage());
}
filterChain.doFilter(request, response);
}
}
JwtUtils class
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
#Slf4j
#Component
public class JwtUtils {
#Value("${ggg.app.jwtSecret}")
private String jwtSecret;
#Value("${ggg.app.jwtExpirationMs}")
private long jwtExpirationMs;
#Value("${ggg.app.jwtRefreshExpirationMs}")
private long jwtRefreshExpirationMs;
static String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) return bearerToken.substring(7);
return null;
}
public String generateJwtAccessToken(String username) {
return generateTokenFromUsername(username, jwtExpirationMs);
}
public String generateJwtRefreshToken(String username) {
return generateTokenFromUsername(username, jwtRefreshExpirationMs);
}
private String generateTokenFromUsername(String username, Long expiration) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + expiration))
.signWith(secretKey())
.compact();
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(secretKey())
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public boolean validateJwtToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(secretKey()).build().parseClaimsJws(token);
return true;
} catch (SignatureException e) {
JwtUtils.log.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
JwtUtils.log.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
JwtUtils.log.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
JwtUtils.log.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
JwtUtils.log.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
private SecretKey secretKey() {
return Keys.hmacShaKeyFor(jwtSecret.getBytes());
}
}
WebSecurityConfig
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired private UserDetailsServiceImpl userDetailsService;
#Bean
private static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
#Bean
private static JwtAuthorizationFilter authenticationJwtTokenFilter() {
return new JwtAuthorizationFilter();
}
// #Override
// public void configure(AuthenticationManagerBuilder authenticationManagerBuilder)
// throws Exception {
// authenticationManagerBuilder
// .userDetailsService(userDetailsService)
// .passwordEncoder(WebSecurityConfig.passwordEncoder());
// }
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// Disable CSRF (cross site request forgery)
http.cors()
.and()
.csrf()
.disable()
// No session will be created or used by spring security
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/v1/auth/**", "/confirm-account")
.permitAll()
.anyRequest()
.authenticated();
// Apply JWT
http.addFilterBefore(
WebSecurityConfig.authenticationJwtTokenFilter(),
UsernamePasswordAuthenticationFilter.class);
}
}
Problem solver ?
#Bean
public JwtAuthorizationFilter authenticationJwtTokenFilter() {
return new JwtAuthorizationFilter();
}
I found out that the private static JwtAuthorizationFilter
authenticationJwtTokenFilter() method is calling the issue. If i change that to a public method it is working, although passwordEncoder is privat static and is used in my auth service . Unfortunately it is my plugin which is causing that problem. It changes the code automatically. Does somebody know what to do ?
AuthService class
#Service
public class AuthService implements AuthServiceRepository {
#Autowired private RoleRepository roleRepository;
#Autowired private UserRepository userRepository;
#Autowired private PasswordEncoder encoder;
#Autowired private JwtUtils jwtUtils;
#Autowired private AuthenticationManager authenticationManager;
#Autowired private UserService userService;
#Autowired private EmailService emailService;
#Autowired private ConfirmationTokenRepository confirmationTokenRepository;
#Override
public JwtResponse signUpUser(SignUpRequest signUpRequest, String siteURL)
throws MessagingException, UnsupportedEncodingException {
String accessToken = jwtUtils.generateJwtAccessToken(signUpRequest.getUsername());
String refreshToken = jwtUtils.generateJwtRefreshToken(signUpRequest.getUsername());
Set<Role> roles = SignUpRequest.getRoles(signUpRequest, roleRepository);
AppUser user =
new AppUser(
signUpRequest.getUsername(),
signUpRequest.getEmail(),
encoder.encode(signUpRequest.getPassword()));
user.setRoles(roles);
user.setEnabled(false);
AppUser newUser = userService.saveUser(user);
ConfirmationToken confirmationToken = new ConfirmationToken(user);
confirmationTokenRepository.save(confirmationToken);
emailService.sendVerificationEmail(user, siteURL, confirmationToken.getConfirmationToken());
List<String> userRoles =
newUser.getRoles().stream().map(role -> role.getName().name()).collect(Collectors.toList());
return new JwtResponse(
accessToken,
refreshToken,
user.getId(),
signUpRequest.getUsername(),
signUpRequest.getEmail(),
userRoles);
} ... }
That's because JwtUtils and WebSecurityConfig are not registered as Spring Beans. Try to add a #Component annotation to the classes.
More about dependency injection in Spring Boot: https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.spring-beans-and-dependency-injection
Any ideas what is the issue? This happened after upgrading Gradle, Spring framework and all dependencies to the latest version. Tried by using #Lazy on Beans but still getting an error. Please check the uploaded images for a better understanding. Thanks in advance.Error message
JwtRequestFilter file
#Autowired
private JwtUserDetailsService jwtUserDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#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;
// JWT Token is in the form "Bearer token". Remove Bearer word and get only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.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");
}
//Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// After setting the Authentication in the context, we specify
// that the current user is authenticated. So it passes the Spring Security Configurations successfully.
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
JwtUserDetailsService file
#Autowired
private UserRepository userRepository;
#Autowired
private RoleRepository roleRepository;
#Autowired
private PasswordEncoder bcryptEncoder;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> user = userRepository.findByUsername(username);
if (!user.isPresent()) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
return new org.springframework.security.core.userdetails.User(
user.get().getUsername(),
user.get().getPassword(),
getAuthorities(user.get())
);
}
public User getUser(){
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication()
.getPrincipal();
String username = userDetails.getUsername();
return userRepository.findByUsername(username).get();
}
private Set<GrantedAuthority> getAuthorities(User user) {
Set<GrantedAuthority> authorities = new HashSet<>();
user.getAuthorities().forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role.getName()));
});
return authorities;
}
WebSecurityConfig.java file
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
private CorsFilter corsFilter;
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/api/user/login");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.and()
.authorizeRequests()
.antMatchers("/admin/user/login", "/api/user/login", "/management/users/register", "/v2/api-docs", "/swagger-ui.html").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/admin/**").hasAuthority("Admin")
.antMatchers("/api/**").hasAuthority("User")
// all other requests need to be authenticated
.anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
#Bean
CorsFilter getCorsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addExposedHeader("Authorization, Content-Type, Error");
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
I'm guessing that your problem is due to your use of property injection.
Use constructor injection instead (everywhere), and your problem will likely go away.
I am currently working on a spring boot project that has multiple security (authentication) configurations combined:
a rest api with http basic auth
a rest api with jwt auth.
a web (form login) with 2fa auth.
The problem I am experiencing is, that the configurations cannot be entirely seperated. More specific: The authenticiation providers are accumulated (in the provider manager), which prevents my preferred setup from working correctly.
What happens is this: In config (3) i have a custom 2fa authentication provider which checks if the credentials (both password and 2FA code!) are entered correctly. If the password is correct, but the 2fa code is not, it exits (catches) with a (authentication or bad credentials) exception. However, as it exits the 2fa authentication provider, it goes back to the provider manager. The latter has another (DAO) auth provider at hand, and checks the credentials again: but this time: only password! not the 2fa code!). As the password is ok, it authorized the request.
So for short: I am experiencing a problem where the first authentication provider does NOT authorize, but the second does (because that one does not take the 2fa code into account)!
I have been at this for days, and i cannot seem to get this right.
As an alternative i have now opted for a solution using a 2fa custom filter. But is not my preferred solution, as it gives me some frontend problems (i first have to authorize the username/password, and only after that i can check the 2fa code).
Is there a solution using my 2fa auth provider? I would sort of wish that the auth providers would not get accumulated, so that when the first auth. provider exits with bad credentials, the auth procedure ends with a 'bad credentials'.
My config class:
WebSecurityConfig.java
#EnableWebSecurity
#Configuration
#AllArgsConstructor
public class WebSecurityConfig_backup extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
private HttpBasicAuthenticationExceptionHandler authenticationExceptionHandler;
private JwtAuthenticationProvider jwtAuthenticationProvider;
private MfaAuthenticationDetailsSource mfaAuthenticationDetailsSource;
private MfaAuthenticationProvider2 mfaAuthenticationProvider;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Configuration
#Order(1)
public class ApiV1WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v1/**")
.httpBasic()
.authenticationEntryPoint(authenticationExceptionHandler)
.and()
.csrf().disable()
.authorizeRequests(authorize -> {
authorize
.mvcMatchers(HttpMethod.GET,
"/api/v1/transaction/**").hasAnyRole("BANK", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/api/v1/transaction/**").hasAnyRole("BANK", "ADMIN");
//.anyRequest().denyAll();
})
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth .userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
#Configuration
#Order(2)
public class ApiV2WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v2/**")
.addFilter(bearerTokenAuthenticationFilter())
.addFilter(credentialsAuthenticationFilter())
.csrf().disable()
.authorizeRequests(authorize -> {
authorize
.mvcMatchers(HttpMethod.GET,
"/api/v2/transaction/**").hasAnyRole("BANK", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/api/v2/transaction/**").hasAnyRole("BANK", "ADMIN");
//.anyRequest().denyAll();
})
.authorizeRequests()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
private BearerTokenAuthenticationFilter bearerTokenAuthenticationFilter() throws Exception {
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(authenticationManager());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
private CredentialsAuthenticationFilter credentialsAuthenticationFilter() throws Exception {
CredentialsAuthenticationFilter filter = new CredentialsAuthenticationFilter(authenticationManager());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
private AuthenticationFailureHandler authenticationFailureHandler() {
return (request, response, ex) -> {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON.toString());
response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
ResponseWriterUtil.writeErrorResponse(response, ex.getMessage());
};
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jwtAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
#Configuration
#Order(3)
public class FormWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//authorisation
http//.addFilterBefore(googleTfaFilter, SessionManagementFilter.class)
.authorizeRequests(authorize -> {
authorize
.mvcMatchers("/", "/login", "/logout", "/registrationPage", "/register").permitAll()
.mvcMatchers(HttpMethod.GET,
"/others1", "/others2").hasAnyRole("USER", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/others1", "/others2").hasAnyRole("USER", "ADMIN");
})
.formLogin()
.loginPage("/login").permitAll()
.usernameParameter("email")
.loginProcessingUrl("/authenticate")
.defaultSuccessUrl("/")
.failureUrl("/login?error")
.authenticationDetailsSource(mfaAuthenticationDetailsSource)
.and()
.logout()
.deleteCookies("JSESSIONID")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.logoutSuccessUrl("/login?logout").permitAll()
.and()
.sessionManagement()
.sessionFixation().migrateSession()
.and()
.headers().frameOptions().sameOrigin()
.and()
.csrf();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(mfaAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
}
auth provider for config 2:
#Slf4j
#RequiredArgsConstructor
#Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(BearerTokenAuthenticationToken.class);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
BearerTokenAuthenticationToken bearerToken = (BearerTokenAuthenticationToken) authentication;
Authentication auth = null;
try {
//validate the token
Jwts.parser().setSigningKey(JWT_TOKEN_SECRET).parseClaimsJws(bearerToken.getToken());
JwtTokenUtil jwtTokenUtil = new JwtTokenUtil(bearerToken.getToken());
String username = jwtTokenUtil.getUsernameFromToken();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
auth = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
log.debug("Authentication token: " + auth);
} catch (IllegalArgumentException e) {
throw new UserServiceAuthenticationException("Invalid token");
} catch (ExpiredJwtException e) {
throw new UserServiceAuthenticationException("Token expired");
} catch (SignatureException e) {
throw new UserServiceAuthenticationException("Invalid signature");
}
return auth;
}
}
Auth provider for config (3)
#Slf4j
#AllArgsConstructor
#Component
public class MfaAuthenticationProvider2 implements AuthenticationProvider {
private UserRepo userRepository;
private GoogleAuthenticator googleAuthenticator;
private PasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
String verficationCode = ((MfaAuthenticationDetails) authentication.getDetails()).getUserMFaCode();
User user = userRepository.findByEmail(authentication.getName()).stream().findFirst().orElse(null);
if(user == null || !passwordEncoder.matches(password, user.getPassword())){
throw new BadCredentialsException("Invalid username or password");
}
try {
if(!googleAuthenticator.authorizeUser(user.getUsername(), Integer.parseInt(verficationCode))){
throw new BadCredentialsException("Invalid verification code.");
}
} catch (Exception e) {
throw new BadCredentialsException("Authentication failed. Please try again.");
}
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
My current alternative solution for config 3:
#Slf4j
#Component
public class GoogleTfaFilter2 extends OncePerRequestFilter {
private final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private final GoogleTfaFailureHandler googleTfaFailureHandler = new GoogleTfaFailureHandler();
private final RequestMatcher urlIs2fa = new AntPathRequestMatcher("/verify2fa");
private final RequestMatcher urlIs2fa2 = new AntPathRequestMatcher("/register2fa");
private final RequestMatcher urlResource = new AntPathRequestMatcher("/resources/**");
private final RequestMatcher api = new AntPathRequestMatcher("/api/**");
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
StaticResourceRequest.StaticResourceRequestMatcher staticResourceRequestMatcher =
PathRequest.toStaticResources().atCommonLocations();
if (urlIs2fa.matches(request) || urlResource.matches(request) || urlIs2fa2.matches(request)||
staticResourceRequestMatcher.matcher(request).isMatch() || api.matches(request)) {
filterChain.doFilter(request, response);
return;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)){
log.debug("Processing 2FA Filter");
if (authentication.getPrincipal() != null && authentication.getPrincipal() instanceof User) {
User user = (User) authentication.getPrincipal();
if (!user.getMfaPassed()) {
log.debug("2FA Required");
request.getRequestDispatcher("/verify2fa").forward(request, response);
return;
}
}
}
filterChain.doFilter(request, response);
}
}
I am using Spring Security and using Username Password Authentication Filter. I want to know if it is possible to show the authentication endpoint in Swagger. This endpoint is generated automatically by the filter (as far I understood).
I really want to make this endpoint appear in Swagger-ui, otherwise I will need to login in Postman and then use Swagger with the Jwt Token, which is little strange.
This is the UsernamePasswordAuthenticationFilter:
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) {
try {
LoginDTO userEntity = new ObjectMapper().readValue(req.getInputStream(), LoginDTO.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(userEntity.getEmail(),
userEntity.getPassword(), new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
Authentication auth) throws IOException {
Token token = new Token(JwtUtils.createAccessToken(ZonedDateTime.now().plusMinutes(10), TenantContext.getCurrentUserUniqueIdentifier(), TenantContext.getCurrentTenantId()),
JwtUtils.createRefreshToken(ZonedDateTime.now().plusMonths(3)));
PrintWriter out = res.getWriter();
res.setContentType("application/json");
res.setCharacterEncoding("UTF-8");
out.print(new ObjectMapper().writeValueAsString(token));
out.flush();
}
}
This is my class extending from WebSecurityAdapter. As you can see, I set the url as /v1/login.
#EnableWebSecurity
#AllArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final UserEntityDetailsService userEntityDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, "/v1/account").permitAll()
.antMatchers(HttpMethod.GET, "/v1/healthcheck").permitAll()
.antMatchers("/v1/recoverpassword/**").permitAll()
.antMatchers("/swagger-ui/**", "/v2/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(getAuthenticationFilter())
.addFilter(new AuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userEntityDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
public AuthenticationFilter getAuthenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(authenticationManager());
filter.setFilterProcessesUrl("/v1/login");
return filter;
}
}
This is my SpringFox config:
#Configuration
#EnableSwagger2
public class SpringFoxConfig {
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Projeto List")
.description("All endpoints of Projeto List Api")
.build();
}
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("br.com.projetolist.resource"))
.paths(PathSelectors.ant("/**"))
.build()
.securitySchemes(Arrays.asList(apiKey()));
}
/**
* SwaggerUI information
*/
#Bean
UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder()
.deepLinking(true)
.displayOperationId(false)
.defaultModelsExpandDepth(1)
.defaultModelExpandDepth(1)
.defaultModelRendering(ModelRendering.MODEL)
.displayRequestDuration(false)
.docExpansion(DocExpansion.NONE)
.filter(false)
.maxDisplayedTags(null)
.operationsSorter(OperationsSorter.ALPHA)
.showExtensions(false)
.tagsSorter(TagsSorter.ALPHA)
.validatorUrl(null)
.build();
}
private ApiKey apiKey() {
return new ApiKey("jwtToken", "Authorization", "header");
}
}
You can just add a similar method to one of your controllers. In your case, it will be the POST method with URL "/v1/login".
#PostMapping("/v1/login")
#ResponseStatus(OK)
fun login(#RequestBody userCreds: UserCredentials) {}
I'm using LDAP authentication provider (active directory) + JWT authorization filter.
I have my custom user object implementing UserDetails, also my user service extends UserDetailsService. But when I do:
Usuario principal = (Usuario) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Principal is just a string (the username), not my user object.
This is my configuration:
SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final LdapProperties ldapProperties;
private final LdapUserMapper ldapUserMapper;
private final UserService userService;
public SecurityConfig(LdapProperties ldapProperties, LdapUserMapper ldapUserMapper, UserService userService) {
this.ldapProperties = ldapProperties;
this.ldapUserMapper = ldapUserMapper;
this.userService = userService;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// Entry points
http.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers(HttpMethod.GET, "/v2/api-docs",
"/configuration/ui",
"/swagger-resources",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/swagger-resources/**",
"/swagger-ui.html").permitAll()
//TODO review
.anyRequest().authenticated();
// JwtWebSecurityConfigurer... TODO ?
// Filters
http.addFilter(new AuthenticationFilter(authenticationManager())); // ldap
http.addFilter(new AuthorizationFilter(authenticationManager())); // jwt
http.cors();
http.csrf().disable();
// No session will be created or used by spring security
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Need to provide Authorization header
http.httpBasic();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(ldapAuthenticationProvider());
auth.userDetailsService(userService);
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues();
corsConfiguration.setAllowedMethods(Arrays.asList(CorsConfiguration.ALL));
//TODO configure properly
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
#Bean
public AbstractLdapAuthenticationProvider ldapAuthenticationProvider() {
String urls = "";
for (String url : ldapProperties.getUrls()) {
urls += url + " ";
}
urls = urls.trim();
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
ldapProperties.getBaseEnvironment().get("domain"),
urls,
ldapProperties.getBase()
);
provider.setUserDetailsContextMapper(ldapUserMapper);
provider.setConvertSubErrorCodesToExceptions(true);
// comment to connect as anonymous
provider.authenticate(
new UsernamePasswordAuthenticationToken(ldapProperties.getUsername(), ldapProperties.getPassword())
);
return provider;
}
}
LdapUserMapper:
#Component
public class LdapUserMapper implements UserDetailsContextMapper {
private final UserService userService;
#Autowired
public LdapUserMapper(UserService userService) {
this.userService = userService;
}
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Usuario result = (Usuario) userService.loadUserByUsername(username);
//TODO compare roles ? set bloqueado ? ...
return result;
}
#Override
public void mapUserToContext(UserDetails userDetails, DirContextAdapter dirContextAdapter) {
}
}
It was my authorization filter.
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String token = req.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.replace("Bearer ", "");
Authentication authentication = getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(final String token) {
try {
DecodedJWT decodedToken = JWT.require(Algorithm.HMAC512(jwtSecret)).build().verify(token);
String username = decodedToken.getSubject();
return new UsernamePasswordAuthenticationToken(username , null, new ArrayList<>());
} catch (JWTVerificationException ex) {
log.error(ex.getMessage());
}
return null;
}
I wasn't aware that I only was passing the username as the Principal object. So I changed it to this:
Usuario usuario = (Usuario) userService.loadUserByUsername(decodedToken.getSubject());
I also removed
auth.userDetailsService(userService);
From AuthenticationManagerBuilder config. That was wrong. My auth provider is LDAP, not the DB.