Skip to content

Commit 1b85f7e

Browse files
Fix authentication manager recursion bug
1 parent bf0ca56 commit 1b85f7e

File tree

18 files changed

+180
-83
lines changed

18 files changed

+180
-83
lines changed

pom.xml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,17 @@
5454
<!--<artifactId>flyway-core</artifactId>-->
5555
<!--<version>4.2.0</version>-->
5656
<!--</dependency>-->
57-
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-search-orm -->
57+
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
58+
<dependency>
59+
<groupId>org.springframework.boot</groupId>
60+
<artifactId>spring-boot-starter-web</artifactId>
61+
<version>2.0.0.RELEASE</version>
62+
</dependency>
63+
<dependency>
64+
<groupId>org.hibernate.validator</groupId>
65+
<artifactId>hibernate-validator</artifactId>
66+
<version>6.0.8.Final</version>
67+
</dependency>
5868
<dependency>
5969
<groupId>org.hibernate</groupId>
6070
<artifactId>hibernate-search-orm</artifactId>

src/main/java/com/springboilerplate/springboilerplate/SpringBoilerplateApplication.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55

66
@SpringBootApplication
77
public class SpringBoilerplateApplication {
8-
98
public static void main(String[] args) {
109
SpringApplication.run(SpringBoilerplateApplication.class, args);
1110
}
11+
12+
1213
}

src/main/java/com/springboilerplate/springboilerplate/app/auth/AuthController.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,24 @@
33
import com.springboilerplate.springboilerplate.app.user.User;
44
import com.springboilerplate.springboilerplate.security.CustomUserService;
55
import com.springboilerplate.springboilerplate.security.JwtTokenUtil;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
68
import org.springframework.beans.factory.annotation.Autowired;
79
import org.springframework.beans.factory.annotation.Value;
810
import org.springframework.http.ResponseEntity;
911
import org.springframework.security.authentication.AuthenticationManager;
10-
import org.springframework.security.authentication.BadCredentialsException;
11-
import org.springframework.security.authentication.DisabledException;
1212
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
13+
import org.springframework.security.core.AuthenticationException;
1314
import org.springframework.security.core.userdetails.UserDetails;
14-
import org.springframework.web.bind.annotation.RequestBody;
15-
import org.springframework.web.bind.annotation.RequestMapping;
16-
import org.springframework.web.bind.annotation.RequestMethod;
17-
import org.springframework.web.bind.annotation.RestController;
15+
import org.springframework.web.bind.annotation.*;
1816

1917
import javax.servlet.http.HttpServletRequest;
2018
import javax.validation.constraints.NotNull;
2119

2220
@RestController
2321
public class AuthController {
2422

23+
Logger logger = LoggerFactory.getLogger(AuthController.class);
2524
@Value("${jwt.header}")
2625
private String tokenHeader;
2726
@Autowired
@@ -32,22 +31,26 @@ public class AuthController {
3231
private CustomUserService customUserService;
3332

3433

35-
@RequestMapping(value = "user", method = RequestMethod.GET)
34+
@GetMapping(value = "user")
3635
public User getAuthenticatedUser(HttpServletRequest request) {
3736
String token = request.getHeader(tokenHeader).substring(7);
37+
logger.info("Retrieved token: '{}'", token);
3838
String username = jwtTokenUtil.getEmailFromToken(token);
39+
logger.info("Retrieved user from token: '{}'", username);
3940
return customUserService.loadUserByUsername(username);
4041
}
4142

42-
@RequestMapping(value = "${jwt.route.authentication.path}", method = RequestMethod.POST)
43+
@PostMapping(value = "${jwt.route.authentication.path}")
4344
public ResponseEntity<?> createAuthenticationToken(@RequestBody AccountCredentials accountCredentials) {
44-
authenticate(accountCredentials);
45-
final UserDetails userDetails = customUserService.loadUserByUsername(accountCredentials.getEmail());
45+
authenticateUser(accountCredentials);
46+
final User userDetails = customUserService.loadUserByUsername(accountCredentials.getEmail());
47+
logger.info("Loaded user details: '{}' '{}'", userDetails);
4648
final String token = jwtTokenUtil.generateToken(userDetails);
49+
logger.info("Generated token: '{}'", token);
4750
return ResponseEntity.ok(new JwtAuthenticationResponse(token));
4851
}
4952

50-
@RequestMapping(value = "${jwt.route.authentication.refresh}", method = RequestMethod.GET)
53+
@GetMapping(value = "${jwt.route.authentication.refresh}")
5154
public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request) {
5255
String authToken = request.getHeader(tokenHeader);
5356
final String token = authToken.substring(7);
@@ -61,8 +64,10 @@ public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest req
6164
}
6265
}
6366

64-
private void authenticate(@NotNull AccountCredentials accountCredentials) {
67+
private void authenticateUser(@NotNull AccountCredentials accountCredentials) throws AuthenticationException{
6568
String email = accountCredentials.getEmail(), password = accountCredentials.getPassword();
69+
logger.info("Authenticating with the following email and password: '{}' '{}'", email, password);
6670
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(email, password));
71+
logger.info("Authenticated with email: '{}' '{}'", email, password);
6772
}
6873
}

src/main/java/com/springboilerplate/springboilerplate/app/user/User.java

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import com.fasterxml.jackson.annotation.JsonFormat;
44
import com.fasterxml.jackson.annotation.JsonIgnore;
5+
import com.springboilerplate.springboilerplate.app.role.Role;
56
import com.springboilerplate.springboilerplate.app.userRole.UserRole;
7+
import org.hibernate.Hibernate;
68
import org.hibernate.annotations.Where;
79
import org.hibernate.search.annotations.*;
810
import org.hibernate.search.annotations.Index;
@@ -22,7 +24,7 @@
2224
import java.util.stream.Collectors;
2325

2426
@Entity
25-
@Table(name="user")
27+
@Table(name="users")
2628
@Indexed
2729
@Where(clause = "deleted = false")
2830
public class User implements UserDetails{
@@ -117,7 +119,8 @@ public void setEmail(String email) {
117119
this.email = email;
118120
}
119121

120-
@OneToMany(mappedBy="user", orphanRemoval = true)
122+
123+
@OneToMany(mappedBy="user", orphanRemoval = true, fetch = FetchType.EAGER)
121124
public List<UserRole> getUserRoles() {
122125
return userRoles;
123126
}
@@ -165,6 +168,10 @@ public boolean isDeleted() {
165168
return deleted;
166169
}
167170

171+
public void setDeleted(boolean deleted) {
172+
this.deleted = deleted;
173+
}
174+
168175
@Column(name = "last_login")
169176
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
170177
public LocalDateTime getLastLogin() {
@@ -175,18 +182,24 @@ public void setLastLogin(LocalDateTime lastLogin) {
175182
this.lastLogin = lastLogin;
176183
}
177184

178-
public void setDeleted(boolean deleted) {
179-
this.deleted = deleted;
185+
@Column(name = "last_password_reset_data")
186+
@JsonIgnore
187+
public Date getLastPasswordResetDate() {
188+
return lastPasswordResetDate;
189+
}
180190

191+
public void setLastPasswordResetDate(Date lastPasswordResetDate) {
192+
this.lastPasswordResetDate = lastPasswordResetDate;
181193
}
182194

183195
@Override
184196
@JsonIgnore
185197
@Transient
186198
public Collection<? extends GrantedAuthority> getAuthorities() {
187-
return userRoles.stream()
188-
.map(userRole -> (new SimpleGrantedAuthority(userRole.getRole().getName().name())))
189-
.collect(Collectors.toList());
199+
//Hibernate initialize because role on userRole is lazily loaded.
200+
userRoles.forEach(userRole -> Hibernate.initialize(userRole.getRole()));
201+
return userRoles.stream().map(userRole -> new SimpleGrantedAuthority(
202+
userRole.getRole().getName().name())).collect(Collectors.toList());
190203
}
191204

192205
@Override
@@ -216,14 +229,4 @@ public boolean isAccountNonLocked() {
216229
public boolean isCredentialsNonExpired() {
217230
return true;
218231
}
219-
220-
@Column(name = "last_password_reset_data")
221-
@JsonIgnore
222-
public Date getLastPasswordResetDate() {
223-
return lastPasswordResetDate;
224-
}
225-
226-
public void setLastPasswordResetDate(Date lastPasswordResetDate) {
227-
this.lastPasswordResetDate = lastPasswordResetDate;
228-
}
229232
}

src/main/java/com/springboilerplate/springboilerplate/app/user/UserController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@ public ResponseEntity<?> searchUser(@RequestParam("keyword") String keyword) thr
4747

4848
@GetMapping(path = "/hello")
4949
public String getHello(){
50-
return "hello";
50+
return "hey!";
5151
}
5252
}

src/main/java/com/springboilerplate/springboilerplate/app/user/UserServiceImpl.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.springboilerplate.springboilerplate.app.userRole.UserRoleRepository;
99
import com.springboilerplate.springboilerplate.exceptions.RoleDoesNotExistException;
1010
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.security.crypto.password.PasswordEncoder;
1112
import org.springframework.stereotype.Service;
1213

1314
import java.util.Optional;
@@ -17,21 +18,24 @@
1718
public class UserServiceImpl implements UserService{
1819
private RoleRepository roleRepository;
1920
private UserRepository userRepository;
20-
private UserRoleRepository userRoleRepository;
2121
private UserDtoMapper userDtoMapper;
22+
private PasswordEncoder passwordEncoder;
2223

2324
@Autowired
2425
public UserServiceImpl(RoleRepository roleRepository,
25-
UserRepository userRepository, UserRoleRepository userRoleRepository, UserDtoMapper userDtoMapper) {
26+
UserRepository userRepository,
27+
UserDtoMapper userDtoMapper,
28+
PasswordEncoder passwordEncoder) {
2629
this.roleRepository = roleRepository;
2730
this.userRepository = userRepository;
28-
this.userRoleRepository = userRoleRepository;
2931
this.userDtoMapper = userDtoMapper;
32+
this.passwordEncoder = passwordEncoder;
3033
}
3134

3235
@Override
3336
public User saveUser(UserDto userDto, RoleType roleType) {
3437
User user = userDtoMapper.toUser(userDto);
38+
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
3539
return setUserRole(user, roleType);
3640
}
3741

src/main/java/com/springboilerplate/springboilerplate/app/userRole/UserRole.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ public class UserRole {
2020

2121
@NotNull
2222
@JoinColumn(name="user_id")
23-
@ManyToOne
23+
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.DETACH, CascadeType.REFRESH, CascadeType.MERGE})
2424
private User user;
2525

2626
@NotNull
2727
@JoinColumn(name="role_id")
28-
@ManyToOne
28+
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.DETACH, CascadeType.REFRESH, CascadeType.MERGE})
2929
private Role role;
3030

3131
public UserRole() {

src/main/java/com/springboilerplate/springboilerplate/config/WebSecurityConfig.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
2727
private JwtAuthenticationEntryPoint unauthorizedHandler;
2828
@Autowired
2929
private JwtTokenUtil jwtTokenUtil;
30-
3130
@Autowired
32-
private CustomUserDetailsService customUserDetailsService;
31+
private JwtUserDetailsService customUserService;
3332

3433
@Value("${jwt.header}")
3534
private String tokenHeader;
@@ -39,13 +38,13 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
3938

4039
@Autowired
4140
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
42-
auth.userDetailsService(customUserDetailsService)
41+
auth.userDetailsService(customUserService)
4342
.passwordEncoder(passwordEncoder());
4443
}
4544

4645
@Bean
47-
@Autowired
48-
public AuthenticationManager authenticationManager() throws Exception {
46+
@Override
47+
public AuthenticationManager authenticationManagerBean() throws Exception {
4948
return super.authenticationManagerBean();
5049
}
5150

@@ -66,7 +65,9 @@ protected void configure(HttpSecurity httpSecurity) throws Exception {
6665
// Un-secure H2 Database
6766
.antMatchers("/h2-console/**/**").permitAll()
6867
.antMatchers("/auth/**").permitAll()
68+
.antMatchers("/v1/users/**").permitAll()
6969
.antMatchers("/actuator/**").permitAll()
70+
.antMatchers(HttpMethod.POST, "/v1/users/register").permitAll()
7071
.anyRequest().authenticated();
7172

7273
// Custom JWT based security filter

src/main/java/com/springboilerplate/springboilerplate/constants/EnvironmentConstants.java

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/main/java/com/springboilerplate/springboilerplate/exceptions/CentralizedExceptionHandler.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,11 @@ public ResponseEntity<Object> handleSendingTokenException(SendingTokenException
7474
ApiError apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage(), error);
7575
return new ResponseEntity<>(apiError, apiError.getStatus());
7676
}
77+
78+
@ExceptionHandler({RoleDoesNotExistException.class})
79+
public ResponseEntity<Object> handleRoleDoesNotExistException(RoleDoesNotExistException ex, WebRequest webRequest){
80+
String error = ex.getMessage();
81+
ApiError apiError = new ApiError(HttpStatus.NOT_FOUND, ex.getMessage(), error);
82+
return new ResponseEntity<>(apiError, apiError.getStatus());
83+
}
7784
}

0 commit comments

Comments
 (0)