I am trying to test my Spring Application which consists of JWT auth with Junit and Mockmvc. Signup test is working as expected. But when trying to test login, StackOverflowError is displayed. Inside the JWTAuthenticationFilter.java, data is being successfully received. But after that error is displayed. Please help me out. Thanks!
Error:
java.lang.StackOverflowError
at org.mockito.internal.invocation.MatchersBinder.bindMatchers(MatchersBinder.java:25)
at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:59)
at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:35)
at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:63)
at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:49)
at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptSuperCallable(MockMethodInterceptor.java:110)
at org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder$MockitoMock$310380589.encode(Unknown Source)
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.prepareTimingAttackProtection(DaoAuthenticationProvider.java:142)
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:106)
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:144)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:200)
at org.springframework.security.con
WebSecurity.java
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private UserDetailsServiceImpl userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurity(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManagerBean())
.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
}
UserControllerTest.java
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
#AutoConfigureJsonTesters
public class UserControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private JacksonTester<User> json;
#Autowired
private JacksonTester<CreateUserRequest> jsonN;
#MockBean
private UserRepository userRepository;
#MockBean
private BCryptPasswordEncoder encoder;
private CreateUserRequest r;
#Before
public void setup(){
r = new CreateUserRequest();
r.setUsername("ujjwal2102");
r.setPassword("ujjwal21");
r.setConfirmPassword("ujjwal21");
}
#Test
public void createUserTest() throws Exception{
signup();
}
#Test
public void loginUserTest() throws Exception{
signup();
login();
}
public void signup() throws Exception{
when(encoder.encode("ujjwal21")).thenReturn("ujjwal21");
mvc.perform(
post(new URI("/api/user/create"))
.content(jsonN.write(r).getJson())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id",is(0)))
.andExpect(jsonPath("$.username",is("ujjwal2102")));
}
public void login() throws Exception{
User user = new User();
user.setUsername("ujjwal2102");
user.setPassword("ujjwal21");
when(encoder.encode("ujjwal21")).thenReturn("ujjwal21");
mvc.perform(
post(new URI("/login"))
.content(json.write(user).getJson())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
}
}
User.java
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonProperty
private long id;
#Column(nullable = false, unique = true)
#JsonProperty
private String username;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#Column(nullable = false)
private String password;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "cart_id", referencedColumnName = "id")
#JsonIgnore
private Cart cart;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Cart getCart() {
return cart;
}
public void setCart(Cart cart) {
this.cart = cart;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
JWTAuthenticationFilter.java
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
User creds = new ObjectMapper()
.readValue(req.getInputStream(), User.class);
System.out.println("USERNAME-----" + creds.getUsername());
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
String token = JWT.create()
.withSubject(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.sign(HMAC512(SecurityConstants.SECRET.getBytes()));
System.out.println("TOKEN----" + token);
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
}
}
I noticed one mistake that may or may not solved your issue, but may compromise your authentification process.
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>()) // with autorities, the user is authenticated
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword()) // without autorities, the user is not authenticated
The constructor with autorities is usually used by the AuthentificationManager after authentification is successful. The constructor without is used by the filter to pass to the AuthentificationManager.
Spring Security Javadoc
Looking at the log, the issue looks to be in your Spring Security Configuration that you didn't provide. You may have done a recursive call inside an authentification provider.
Related
I have a spring boot project with a /api/users/register and /api/users/login endpoint, the register endpoint works fine and allows me to POST to create a new user (With validation etc) but the login endpoint gives me a 401 (Unauthorized) response when I try to login with correct details.
Also, I am trying to add new roles to users as they are created but since I'm using JWT, I am unsure how to go about doing this, every tutorial online has a different implementation and I'm struggling to understand this too.
If someone can provide me on some steps to do this I'd appreciate it. I know you need to add the roles as claims when generating the token itself, but where do I need to implement the actual logic of assigning a new role upon creation of an account.
User.java
#Entity
#Table(name="user")
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
#NotBlank(message = "Username is required")
private String username;
#NotBlank(message = "Password is required")
private String password;
#Transient
private String confirmPassword;
private boolean enabled;
#JsonFormat(pattern = "yyyy-mm-dd")
private Date createdAt;
// I want to load all the roles of users + the user itself once requested for
#ManyToMany(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name="user_roles",
joinColumns = {
#JoinColumn(name="user_id")
},
inverseJoinColumns = {
#JoinColumn(name="role_id")
})
private Set<Role> roles = new HashSet<>();
// no-arg and all arg constructor
// getters & setters
Role.java
#Entity
#Table(name="role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
// constructors
// getters & setters
** I do have repository/dao classes for both entities with a method to find user/role by name**
UserService.java
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Autowired
private RoleService roleService;
public User saveUser(User user) {
try {
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setUsername(user.getUsername());
user.setConfirmPassword("");
user.setEnabled(true);
return userRepository.save(user);
} catch(Exception e) {
throw new UsernameAlreadyExistsException("User with username " + user.getUsername() + " already exists!");
}
}
}
RoleService.java
#Service
public class RoleService {
#Autowired
private RoleRepository roleRepository;
public Role findByRoleName(String roleName) {
Role theRole = roleRepository.findByName(roleName);
return theRole;
}
}
UserDetailsServiceImpl.java
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("User not found");
}
return (UserDetails) user;
}
}
These are my JWT classes
**JwtAuthenticationFilter.java
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenProvider tokenProvider;
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJWTFromRequest(httpServletRequest);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
String username = tokenProvider.getUsernameFromJwt(jwt);
User userDetails = (User) userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, Collections.emptyList());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private String getJWTFromRequest(HttpServletRequest request) {
// Header Authorization: Bearer token
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}
JwtTokenProvider.java
#Component
public class JwtTokenProvider {
// Generate the token
public String generateToken(Authentication authentication) {
User user = (User) authentication.getPrincipal();
Date now = new Date(System.currentTimeMillis());
Date expiryDate = new Date(now.getTime() + 300_000);
String userId = Long.toString(user.getId());
// this is what holds the token
// add roles in claims
Map<String, Object> claims = new HashMap<>();
claims.put("id", (Long.toString(user.getId())));
claims.put("username", user.getUsername());
return Jwts.builder().setSubject(userId).setClaims(claims).setIssuedAt(now).setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, "SECRETSECRETSECRET").compact();
}
// Validate the token
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey("SECRETSECRETSECRET").parseClaimsJws(token);
return true;
} catch (SignatureException ex) {
System.out.println("Invalid JWT Signature");
} catch (MalformedJwtException ex) {
System.out.println("Invalid JWT Token");
} catch (ExpiredJwtException ex) {
System.out.println("Expired JWT Token");
} catch (UnsupportedJwtException ex) {
System.out.println("Unsupported JWT token");
} catch (IllegalArgumentException ex) {
System.out.println("JWT claims string is empty");
}
return false;
}
public String getUsernameFromJwt(String token) {
Claims claims = Jwts.parser().setSigningKey("SECRETSECRETSECRET").parseClaimsJws(token).getBody();
return claims.getSubject();
}
}
JwtAuthenticationEntryPoint.java
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
InvalidLoginResponse loginResponse = new InvalidLoginResponse();
// InvalidLoginResponse is a class with username and password fields and a no-arg
constructor initialiting them like this
// this.username = "Invalid Username";
// this.password = "Invalid Password";
String jsonLoginResponse = new Gson().toJson(loginResponse);
httpServletResponse.setContentType("application/json");
httpServletResponse.setStatus(401);
httpServletResponse.getWriter().print("ERROR FROM JwtAuthenticationEntryPoint: " + jsonLoginResponse);
}
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// Here i am permitting access to all URLs but getting 401 when posting to /login
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.anyRequest().permitAll();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
UserController.java
#RestController
#RequestMapping("/api/users")
#CrossOrigin
public class UserController {
private UserService userService;
private UserDetailsServiceImpl userDetailsService;
private UserValidator userValidator;
private ErrorValidationService errorValidationService;
private JwtTokenProvider tokenProvider;
private AuthenticationManager authenticationManager;
#Autowired
public UserController(UserService userService, UserDetailsServiceImpl userDetailsService, UserValidator userValidator, ErrorValidationService errorValidationService, JwtTokenProvider tokenProvider, AuthenticationManager authenticationManager) {
this.userService = userService;
this.userDetailsService = userDetailsService;
this.userValidator = userValidator;
this.errorValidationService = errorValidationService;
this.tokenProvider = tokenProvider;
this.authenticationManager = authenticationManager;
}
// I want to allow role based access to these URLs
#GetMapping("/all")
public String welcomeAll() {
return "Anyone can view this!";
}
#GetMapping("/admin")
public String adminPing(){
return "Only Admins Can view This";
}
#GetMapping("/user")
public String userPing(){
return "Any User Can view This";
}
#PostMapping("/register")
public ResponseEntity<?> register(#Valid #RequestBody User user, BindingResult result) {
userValidator.validate(user, result);
ResponseEntity<?> errorMap = errorValidationService.validationService(result);
if(errorMap != null) return errorMap;
User newUser = userService.saveUser(user);
return new ResponseEntity<User>(newUser, HttpStatus.CREATED);
}
#PostMapping("/login")
public ResponseEntity<?> generateToken(#Valid #RequestBody LoginRequest loginRequest, BindingResult result) throws Exception {
System.out.println("Entering /login");
ResponseEntity<?> errorMap = errorValidationService.validationService(result);
if(errorMap != null) return errorMap;
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = "Bearer " + tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtLoginSuccessResponse(true, jwt));
}
}
When trying to register in postman
/api/users/register
When trying to login in postman
/api/users/register
As you added OncePerRequestFilter for all the incoming request. So when you call /api/users/login it will check for JWT token and unable to find it in header so it is throwing 401 (Unauthorized). To exclude /api/users/login end point from OncePerRequestFilter you need to override shouldNotFilter(HttpServletRequest request)
public class LoginFilter extends OncePerRequestFilter {
private List<String> excludeUrlPatterns = new ArrayList<String>();
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException
{
//Business logic
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException
{
// add excludeUrlPatterns on which one to exclude here
}
}
For more info visit :OncePerRequestFilter
After configuring a custom user details service that uses an embedded H2 database to retrieve user credentials, I keep receiving 401 Unauthorized errors when completing a post command even though the username and password are available. I don't know if this makes any difference, but the console still prints out the generated security password (although the auto generated credentials return the same 401 error). Please take a look at my code below and let me know any suggestions or fixes available.
User model...
#Entity
public class ApplicationUser {
#Id
private Long id;
private String username, password, role;
public ApplicationUser(String username, String role, String password) {
this.username=username;
this.password=password;
this.role=role;
}
public ApplicationUser() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
#Override
public String toString() {
return "ApplicationUser{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", role='" + role + '\'' +
'}';
}
}
User repository class...
#Repository
public class AppUserRepository {
#Autowired
JdbcTemplate jdbcTemplate;
static class ApplicationUserRowMapper implements RowMapper<ApplicationUser> {
#Override
public ApplicationUser mapRow(ResultSet rs, int rowNum) throws SQLException {
ApplicationUser applicationUser = new ApplicationUser();
applicationUser.setUsername(rs.getString("username"));
applicationUser.setPassword(rs.getString("password"));
applicationUser.setRole(rs.getString("userrole"));
return applicationUser;
}
}
public List<ApplicationUser> findAll() {
return jdbcTemplate.query("select u.username, u.password, ur.userrole from ApplicationUsers u, ApplicationUsers_Role ur where u.username = ur.username",
new ApplicationUserRowMapper());
}
public ApplicationUser findByUsername(String username) {
return jdbcTemplate.queryForObject("select u.username, u.password, ur.userrole from ApplicationUsers u, ApplicationUsers_Role ur where u.username = ? and u.username = ur.username",
new Object[] {username}, new ApplicationUserRowMapper());
}
}
Security config class...
#Configuration
#EnableWebSecurity
#ComponentScan(basePackages = "security_package")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CustomUserDetailsService customUserDetailsService;
#Autowired
CustomAuthenticationProvider customAuthenticationProvider;
#Bean
public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
#Autowired
public void configureGlobal (AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").usernameParameter("username").passwordParameter("password");
}
}
Custom authentication provider...
#Service
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
AppUserRepository userRepository;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
ApplicationUser user = userRepository.findByUsername(name);
if(user == null) {
throw new BadCredentialsException("Authentication failed.");
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority(user.getRole()));
return new UsernamePasswordAuthenticationToken(name, password, grantedAuthorities);
}
#Override
public boolean supports(Class<?> aClass) {
return aClass.equals(UsernamePasswordAuthenticationToken.class);
}
}
Main app class...
#SpringBootApplication(exclude={
HazelcastAutoConfiguration.class,
DataSourceAutoConfiguration.class,
SecurityAutoConfiguration.class
})
#EnableScheduling
#Slf4j
public class AwsorchestratorApplication implements CommandLineRunner{
#Override
public void run(String... arg0) throws Exception {
if (arg0.length > 0 && arg0[0].equals("exitcode")) {
throw new ExitException();
}
}
public static void main(String[] args) throws Exception {
if ( System.getProperty("spring.profiles.active") == null )
{
System.setProperty("spring.profiles.active","local");
}
new SpringApplication(AwsorchestratorApplication.class).run(args);
}
class ExitException extends RuntimeException implements ExitCodeGenerator {
private static final long serialVersionUID = 1L;
#Override
public int getExitCode() {
return 10;
}
}
}
UPDATE ONE::...
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private AppUserRepository userRepo;
#Override
public UserDetails loadUserByUsername(String username) {
ApplicationUser user = userRepo.findByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("User '" + username + "' not found.");
}
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(user.getRole());
return new User(user.getUsername(), user.getPassword(), Collections.singletonList(grantedAuthority));
}
}
I was able to correct this issue by removing my custom authentication class (although I don't think this was actually part of the problem), and editing my web security config to match the below.
#Configuration
#EnableWebSecurity
#ComponentScan(basePackages = package_location)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public CustomUserDetailsService detailsService;
#Bean
public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
#Autowired
public void configureGlobal (AuthenticationManagerBuilder auth) throws Exception {
//the password encoder here is only to support a mix of encoding - that change can be removed
auth.userDetailsService(detailsService).passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/awsorchestrator/**")
.hasAnyRole("ADMIN", "USER")
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.and()
.csrf()
.disable();
}
}
The important part that directly correlated to my problem was the passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()) portion. I also had to change the password stored in the database to be prefixed with {bcrypt} and now I am able to login with no issues.
Thanks R.G for the tips.
The successfulAuthentication function in JWTAuthenticationFilter.java gives a nullpointerexception. Do you see why this would be an issue? Is it a problem with using the same bean for autowiring?
This is my current project structure:
-com
-register
-RegisterController.java
-security
-JWTAuthenticationFilter.java
-JWTAuthorizationFilter.java
-SecurityConstants.java
-WebSecurity.java
-user
-User.java
-UserRepository.java
-UserService.java
-Application.java
Application.java
#Configuration
#SpringBootApplication
public class Application {
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
UserRepository.java
#Repository
public interface UserRepository extends MongoRepository<User, String> {
User findByUsername(String name);
User findByEmail(String Email);
User findBy_id(ObjectId id);
}
UserService.java
#Service
public class UserService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = this.userRepository.findByUsername(username);
if(user == null)
return null;
List<SimpleGrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority("user"));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}
public User getUserByUsername(String username) {
return this.userRepository.findByUsername(username);
}
public User getUserBy_id(ObjectId _id) {
return userRepository.findBy_id(_id);
}
public void saveUser(User newUser){
userRepository.save(newUser);
}
}
User.java
#Document
public final class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private ObjectId _id;
private String email;
private String username;
private String password;
private AccountProperties accountProperties;
private Address address;
private List<Pet> pets = new ArrayList<>();
private String phoneNumber;
public User() {}
public User(#JsonProperty("email") String email, #JsonProperty("username") String username,
#JsonProperty("password") String password) {
this.email = email;
this.username = username;
this.password = password;
}
public String get_id() { return _id.toHexString();}
getters and setters() ...
}
JWTAuthenticationFilter.java
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Autowired
private UserRepository userRepo;
private AuthenticationManager authenticationManager;
JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
User creds = new ObjectMapper()
.readValue(req.getInputStream(), User.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
String username = ((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername();
String token = JWT.create()
.withSubject(username)
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(HMAC512(SECRET.getBytes()));
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
User u = uRepo.findByUsername("admin");
res.getWriter().write(
"{\"" + SecurityConstants.HEADER_STRING + "\":\"" + SecurityConstants.TOKEN_PREFIX+token + "\"," +
"\"" + "ObjectID" + "\":\"" + u.get_id() + "\"}"
);
}
}
JWTAuthorizationFilter.java
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
}
WebSecurity.java
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userDetailsService;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS );
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(this.userDetailsService).passwordEncoder(this.bCryptPasswordEncoder);}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
SecurityConstants.java
public class SecurityConstants {
public static final String SECRET = "SecretKeyToGenJWTs";
public static final long EXPIRATION_TIME = 864_000_000; // 10 days
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
public static final String SIGN_UP_URL = "/users/sign-up";
}
RegisterController.java
#RestController
#RequestMapping("/users")
public class RegisterController {
#Autowired
private UserService userService;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#PostMapping("/sign-up")
public void signUp(#RequestBody User user) {
if (user.getPassword() == null || user.getUsername() == null)
return;
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userService.saveUser(user);
}
}
Not sure if this is the root cause of your issue, but I have never seen #Configuration in the main application. I would try to move that to a separate config class and see if that helps
Annotate the JWTAuthenticationFilter with #Component or add #Bean in configuration file . Looks like the object is not created
The problem is that you doesn't define JWTAuthenticationFilter as Bean, so spring doesn't inject dependencies in it.
You can get beans in filter manualy. From the GenericFilterBean javadoc:
This generic filter base class has no dependency on the Spring org.springframework.context.ApplicationContext concept. Filters usually don't load their own context but rather access service beans from the Spring root application context, accessible via the filter's ServletContext (see org.springframework.web.context.support.WebApplicationContextUtils).
Or you can make it bean. But if you are using Spring Boot consider that:
The fact that all filters internal to Spring Security are unknown to the container is important, especially in a Spring Boot application, where all #Beans of type Filter are registered automatically with the container by default. So if you want to add a custom filter to the security chain, you need to either not make it a #Bean or wrap it in a FilterRegistrationBean that explicitly disables the container registration.
As the title says, I'm using Spring Security with JWT (using hibernate and bCrypt) to sign up users and let them login. I've followed this tutorial to make this work in my project. When doing exactly the same as the tutorial (using the in-memory-database) everythings seems to work fine. But when intergrating the code in my own project the authentication keeps failing, giving a "bad credentials" exception.
Main:
#SpringBootApplication
#EnableConfigurationProperties
public class ApiApplication {
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
My websecurity config looks like this:
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurity(UserDetailsService userDetailsService,
BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
The authentication filter, whenever I try to send a post with the username and password, the unsuccessfulAuthentication runs, even with correct credentials.
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
User creds = new ObjectMapper()
.readValue(req.getInputStream(), User.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getUsername(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
System.out.println("This method never runs...");
Claims claims = Jwts.claims()
.setSubject(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME));
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, SECRET.getBytes())
.compact();
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
super.unsuccessfulAuthentication(request, response, failed);
System.out.println("FAILED");
failed.printStackTrace(); // bad creds
}
}
UserDetailServiceImpl:
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
private UserRepository repository;
public UserDetailsServiceImpl(UserRepository applicationUserRepository) {
this.repository = applicationUserRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = repository.findByUsername(username);
if(user == null){
System.out.println("User is null");
throw new UsernameNotFoundException(username);
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), emptyList());
}
}
The user entity:
#Data
#Entity
#Table(name = "user_entity")
public class User implements Serializable {
public User() { }
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "user_id", unique = true)
private long userId;
#NotEmpty
#Column(name = "username", unique = true)
private String username;
#NotEmpty
#Column(name = "user_rol")
#JsonProperty("userRol")
private String userRol;
#NotEmpty
#Column(name = "password")
private String password;
}
In my user controller I encrypt the password like this:
#PostMapping("/sign-up")
public User signUp(#RequestBody User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setUserRol("ADMIN");
return userService.signUpUser(user);
}
Everythings seems to be ok, Spring succesfully puts a new user in the database (with an encrypted password), whenever I do a login attempt it also succesfully gets that user, but authentication still fails (with correct credentials ofcourse). So my guess there is something wrong with the bcypt password encoder...
Another thing I wonder; where does the /login route come from? Is this a default route within Spring Security? (I've never declared it)
Thanks for the help guys!
Maybe it is due a typo in your attemptAuthentication() method.
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
User creds = new ObjectMapper()
.readValue(req.getInputStream(), User.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getUsername(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
In the constructor of UsernamePasswordAuthenticationToken the second argument should be creds.getPassword() and not creds.getUsername().
Good day,
I am using spring security to restrict user, and i am using mongodb.
I have created UserDetail and userDetail Services.
her is my webSecurity config.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public UserDetailsService mongoUserDetails() {
return new CustomUserDetailsService();
}
#Bean
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(mongoUserDetails());
authProvider.setPasswordEncoder(new BCryptPasswordEncoder());
return authProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home", "/static/unsecure/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
I am storing permissions in database like this.
"permissions" : [
"/reports/report1",
"/reports/report2"
]
and here is userDetail service.
public class MongoUserDetails implements UserDetails {
private final String username;
private final String password;
private final List<GrantedAuthority> grantedAuthorities;
private final boolean enabled;
public MongoUserDetails(String username, String password, String[] authorities, boolean isEnabled) {
this.username = username;
this.password = password;
this.grantedAuthorities = AuthorityUtils.createAuthorityList(authorities);
this.enabled = isEnabled;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return username;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return enabled;
}
}
in private final List grantedAuthorities; I have successfully stored authorites. which are "/reports/report1", "/reports/report2"
When i run my web application it is redirecting my to login page and after valid credentials it redirects my to /hello page.
Now i am creating controllers and actions like
#Controller
#ResponseBody
public class MyController {
#GetMapping("/user/greeting")
public String greeting(HttpServletRequest request){
return "welcome secure";
}
#GetMapping("/login1")
public String greeting1(HttpServletRequest request){
}
}
so since List grantedAuthorities; does not contains this route it should not allowed the user for this action
how can i achieve this. assuming all authorities are dynamic.
thanks.
My example:
Authentication service:
#Service
public class AuthService implements UserDetailsService {
#Autowired
private UserJpaRepository userJpaRepository;
#Autowired
private UserProfileJPARepository profileJPARepository;
#Autowired
private UserProfileContainer profileContainer;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = (User) userJpaRepository.findByLogin(username);
GrantedAuthority authority = new SimpleGrantedAuthority(user.getRole().name());
UserDetails userDetails = (UserDetails) new SecureUser(
user.getId(),
user.getFirstName(),
user.getLastName(),
user.getEmail(),
user.getMobileNumber(),
user.getLogin(),
user.getPassword(),
user.isActive(),
true,
true,
true,
Arrays.asList(authority)
);
user.setLastLogonTime(new Timestamp((new Date()).getTime()));
userJpaRepository.saveAndFlush(user);
profileContainer.setUser(user);
return userDetails;
}
}
Session scoped bean:
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserProfileContainer {
private User user = null;
public UserProfileContainer() {
}
public void setUser(User user) {
this.user = user;
}
public User getUser() {
return user;
}
}
And in User class you cal store a List<String> allowedURLS = new ArrayList<>();
AFTER EDIT:
I've wrote this example. In this case I have some SecureUser.class which extends org.springframework.security.core.userdetails.User and in this class I have Set<String> allowedPathSet actually HashSet. And here is the custom filter:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomerFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
// nothing here...
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession(true);
SecurityContextImpl sci = (SecurityContextImpl) session.getAttribute("SPRING_SECURITY_CONTEXT");
if (sci != null) {
SecureUser user = (SecureUser) sci.getAuthentication().getPrincipal();
String url = req.getRequestURI();
if (!user.path.contains("url")) ((HttpServletResponse) response).sendRedirect("http://redirect-URL-Here/");
}
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
}
This example isn't a silver bullet but the main idea is present here and it work.