Handling Authentication Failure with Springboot & Spring security - spring

In a Rest appplication developped with Spring, I use POJO classes, DTO and entity for users management. Here is an abstract of my entity class.
#Entity
#Table(name="users")
#Getter #Setter
#AllArgsConstructor #NoArgsConstructor
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
private String userKeyId;
#Column(nullable = false, length = 50)
private String firstName;
#Column(nullable = false, length = 50)
private String lastName;
#Column(nullable = false, length = 120, unique = true)
private String email;
#Column(nullable = false)
private String encryptedPassword;
#Column
private String emailVerificationToken;
#Column(name = "email_verification_status", columnDefinition = "BOOLEAN NOT NULL DEFAULT FALSE")
private Boolean emailVerificationStatus = false;
#Column(name="is_account_non_expired")
private Boolean isAccountNonExpired;
#Column(name="is_account_non_locked")
private Boolean isAccountNonLocked;
#Column(name="is_credentials_non_expired")
private Boolean isCredentialsNonExpired;
#Column(name="is_enabled")
private Boolean isEnabled;
#Column(name="is_logged_in")
private Boolean isLoggedIn;
#ManyToMany(cascade= { CascadeType.PERSIST }, fetch = FetchType.EAGER )
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns=#JoinColumn(name = "role_id", referencedColumnName = "id"))
private Collection<RoleEntity> roles;
#CreationTimestamp
#Temporal(TemporalType.DATE)
#Column(name="created_at")
private Date createdAt;
#UpdateTimestamp
#Temporal(TemporalType.DATE)
#Column(name="updated_at")
private Date updatedAt;
}
I have a UserServiceImpl class that implements UserDetails
I do have then to implement loadUserByUsername
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
UserEntity userEntity = userRepository.findByEmail(email);
if( userEntity == null) {
throw new UsernameNotFoundException("User email is not in the database");
} else {
validateLoginAttempt(userEntity);
log.info("Returning User : " + userEntity.getFirstName() + " " + userEntity.getLastName());
userEntity.setLastLoginDateDisplay(userEntity.getLastLoginDate());
userEntity.setLastLoginDate(new Date());
userRepository.save(userEntity);
return new UserPrincipal(userEntity);
}
}
If user exists I call a method to validate authentication.
private void validateLoginAttempt(UserEntity user) {
if(user.getIsAccountNonLocked()) {
if(loginAttemptService.hasExceededMaxAttempts(user.getEmail())) {
user.setIsAccountNonLocked(Boolean.FALSE);
} else {
user.setIsAccountNonLocked(Boolean.TRUE);
}
} else {
loginAttemptService.evictUserFromLoginAttemptCache(user.getEmail());
}
}
This method allows me to check if the user account is locked or not and if user tried to connect too many times.
My LoginAttemptServiceImpl is the following:
#Service
public class LoginAttemptServiceImpl implements LoginAttemptService {
public static final int MAXIMUM_AUTH_ATTEMPT = 5;
public static final int AUTH_ATTEMPT_INCREMENT = 1;
private LoadingCache<String, Integer> loginAttemptCache;
private String username;
public LoginAttemptServiceImpl() {
super();
loginAttemptCache = CacheBuilder.newBuilder()
.expireAfterWrite(15, TimeUnit.MINUTES)
.maximumSize(10000)
.build(new CacheLoader<>() {
#Override
public Integer load(String key) {
return 0;
}
});
}
#Override
public void evictUserFromLoginAttemptCache(String username) {
loginAttemptCache.invalidate(username);
}
#Override
public void addUserToLoginAttemptCache(String username) {
int attempts = 0;
try {
attempts = AUTH_ATTEMPT_INCREMENT + loginAttemptCache.get(username);
loginAttemptCache.put(username, attempts);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
#Override
public boolean hasExceededMaxAttempts(String username) {
try {
return loginAttemptCache.get(username) >= MAXIMUM_AUTH_ATTEMPT;
} catch (ExecutionException e) {
e.printStackTrace();
}
return false;
}
#Override
public int getLoginAttempts(String username) throws ExecutionException {
return loginAttemptCache.get(username);
}
}
I also implemented an event listener for authentication failure:
#Component
public class AuthenticationFailureListener {
private final LoginAttemptService loginAttemptService;
#Autowired
public AuthenticationFailureListener(LoginAttemptService loginAttemptService) {
this.loginAttemptService = loginAttemptService;
}
#EventListener
public void onAuthenticationFailure(AuthenticationFailureBadCredentialsEvent event) {
Object principal = event.getAuthentication().getPrincipal();
if (principal instanceof String) {
String username = (String) event.getAuthentication().getPrincipal();
loginAttemptService.addUserToLoginAttemptCache(username);
}
}
}
And finally my AuthenticationFilter allows me to manage successful and unsuccessful response:
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
String userName = ((UserPrincipal)authResult.getPrincipal()).getUsername();
// built the token
String token = Jwts.builder()
.setSubject(userName)
.setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.getTokenSecret())
.compact();
UserService userService = (UserService) SpringApplicationContext.getBean("userServiceImpl");
UserDto userDto = userService.getUser(userName);
response.addHeader(SecurityConstants.HEADER_STRING_USERID, userDto.getUserKeyId());
response.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
}
#SneakyThrows
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
// super.unsuccessfulAuthentication(request, response, failed);
int attempts;
if(loginAttemptService.hasExceededMaxAttempts(this.username)) {
attempts = loginAttemptService.getLoginAttempts(this.username);
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Attempt number " + attempts + ": Account is locked for 15 minutes");
} else {
attempts = loginAttemptService.getLoginAttempts(this.username);
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Attempt number " + attempts + ": " + (SecurityConstants.MAX_AUTH_ATTEMPTS - attempts) + " - before account is blocked");
}
}
Authentication works when it's successful... My issue concerns failure and i have 3 issues:
I would like to return an object in case of failure. the response.sendError should do the job but it doesn't. I also tried to return a Json response : https://www.baeldung.com/servlet-json-response
I use Guava cache but I also update database at the same time by setting isAccountNonLocked to false. I'd like to set the value to True once the cache is cleared.
I do not update the count of attempt in unsuccessfulAuthentication method. My response is always : Attempt number 0: 5 - before account is blocked
Thanks for help and for reading the whole text!

Regarding issue number 1, you can use a similar approach as the one mentioned in the link you posted, but use response.getWriter().write(String) and Jackson's ObjectMapper, like this:
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ObjectMapper mapper = new ObjectMapper();
response.getWriter().write(mapper.writeValueAsString( /*Your custom POJO here */ ));

For issue 2: I found a trick that solves it. Instead of updating the database at the same time i clear the cache, I make the update at login validation...
private void validateLoginAttempt(UserEntity user) {
if(user.getIsAccountNonLocked()) {
if(loginAttemptService.hasExceededMaxAttempts(user.getEmail())) {
user.setIsAccountNonLocked(Boolean.FALSE);
} else {
user.setIsAccountNonLocked(Boolean.TRUE);
}
} else {
if(!loginAttemptService.hasExceededMaxAttempts(user.getEmail())) {
user.setIsAccountNonLocked(Boolean.TRUE);
}
loginAttemptService.evictUserFromLoginAttemptCache(user.getEmail());
}
}

For issue 3:
In my WebSecurity class which extends WebSecurityConfigurerAdapter, I implemented a bean in order to inject it in my AuthenticationFilter.
Here is my bean:
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Here is my AuthenticationFilter class. I initially added this class as component (bad idea which generated error messages).
// #Component
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final LoginAttemptService loginAttemptService;
private String username;
public AuthenticationFilter(AuthenticationManager authenticationManager, LoginAttemptService loginAttemptService) {
this.authenticationManager = authenticationManager;
this.loginAttemptService = loginAttemptService;
}
....

Related

Spring Security basic auth for sign in

I am new to springboot security.And i just find that it is quite hard to follow the latest version since some of the method has already outdated.So i just follow on some of the tutorials to work on the jwt and authentication with mysql.I have problem on the use of the basic auth for generating the token when it is integrated to my own model.Here are my code.
Tokenservice:
#Service
public class TokenService {
private final JwtEncoder encoder;
private PasswordEncoder passwordEncoder;
public TokenService(JwtEncoder jwtEncoder, PasswordEncoder passwordEncoder) {
this.encoder = jwtEncoder;
this.passwordEncoder = passwordEncoder;
}
#Autowired
private JpaUserrepository jpaUserrepository;
private Authentication auth;
public String generateToken (Authentication authentication){
Instant now=Instant.now();
String scope=authentication.getAuthorities().stream() //Stream<capture of ? extends GrantedAuthority >
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer(authentication.getName())
.issuedAt(now)
.expiresAt(now.plus(1, ChronoUnit.HOURS))
.claim("scope",scope).build();
return this.encoder.encode(JwtEncoderParameters.from((claims))).getTokenValue();
}
public String signup(JpaUser jpaUser) {
System.out.println(jpaUserrepository.findByUsername(jpaUser.getUsername()));
if(jpaUserrepository.findByUsername(jpaUser.getUsername()).isPresent())
{return "repeated username";}
jpaUserrepository.save(new JpaUser(jpaUser.getUsername(),passwordEncoder.encode(jpaUser.getPassword()),jpaUser.getEmail(),jpaUser.getRoles()));
return "sign up success";
}
}
I want to sign up which can return the token,but the required type of generatedtoken method is authentication (Basic Auth) but i have no idea to convert my Jpauser model to authentication interphase.
Here is Jpauser model:
#Entity
#Table(name = "JpaUser")
public class JpaUser {
#Id #GeneratedValue
private Long id;
private String username;
private String password;
private String email;
private String roles;
public JpaUser() {}
public JpaUser(String username, String password,String email, String roles) {
this.username = username;
this.password = password;
this.email=email;
this.roles = roles;
}
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 String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public void setPassword(String password) {
this.password = password;
}
public String getRoles() {
return roles;
}
public void setRoles(String roles) {
this.roles = roles;
}
#Override
public String toString() {
return "SecurityUser{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
", roles='" + roles + '\'' +
'}';
}
}
My securityconfig:
#Bean
public SecurityFilterChain securityfilterchain(HttpSecurity http) throws Exception {
return http
//if used csrf.ignoringAntMatchers -->prevent the attack from cross-origin
//.csrf(csrf -> csrf.disable())
.csrf(csrf -> csrf.ignoringAntMatchers("/shownews/**","/getnews/**","/token","/signup"))
.authorizeRequests(auth-> auth
.antMatchers("/shownews/**").permitAll()
.antMatchers("/getnews/**").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
//Link to the jpauserservice to check whether the authentication valid (similar to authentication manager)
.userDetailsService(myUserDetailsService)
.headers(headers -> headers.frameOptions().sameOrigin())
.httpBasic(Customizer.withDefaults())
.formLogin().and()
.build();
}
#Bean
JwtDecoder jwtDecoder(){
return NimbusJwtDecoder.withPublicKey(rsakeys.publicKey()).build();
}
In fact, i am quite loss when i learn this topic since i think the doc is quite hard to understand .I am just wondering if there any materials that are more easy to understand amd modifymy code?

Why all properties of a Model passed from AOP to controller with other arguments are null

AOP
#Around(
"execution(* net.inter.warp.bridge.controller.*.*(.., net.inter.warp.bridge.model.User)) && " +
"args(.., authenticatedUser)"
)
public Object withAuthenticatedUser(ProceedingJoinPoint joinPoint, User authenticatedUser) throws Throwable {
System.out.println(joinPoint + " -> " + authenticatedUser);
User user = null;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null)
user = (User) userService.loadUserByUsername(authentication.getName());
else
throw new UnauthorizedException("err 1");
if (user == null)
throw new UnauthorizedException("err 2");
return joinPoint.proceed(new Object[]{user});
}
Controller (all properties of authenticatedUser are null)
package net.inter.warp.bridge.controller;
#GetMapping("/boxes/{id}")
public ResponseEntity<Box> getBoxById(#PathVariable(value = "id") Long boxId, User authenticatedUser)
throws NoDynamicTableFoundException, ResourceNotFoundException {}
Controller (This works as there is no more parameters except for authenticatedUser)
package net.inter.warp.bridge.controller;
#GetMapping("/boxes/{id}")
public ResponseEntity<Box> getBoxById(User authenticatedUser)
throws NoDynamicTableFoundException, ResourceNotFoundException {}
AOP seems to hate other paramethers... authenticatedUser is not null, every property of authenticatedUser is null.
Model (I am not sure this issue is related to this)
#Entity
#Table(name="users")
#ToString
public class User extends AuthEntity implements UserDetails
{
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
String[] userRoles = this.roles.stream().map((role) -> role.getName()).toArray(String[]::new);
Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(userRoles);
return authorities;
}
#Override
public String getUsername() {
return this.email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
#Column(nullable=false)
#NotNull(message = "")
private String name;
#Column(nullable=false, unique=true)
#Email
//#NotBlank(message = "")
private String email;
#Column
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#JsonIgnore
private String password;
#Column(length = 20, columnDefinition ="bigint")
//#NotNull(message = "")
private Long organization_id;
#ManyToOne(optional=false)
#JoinColumn(name = "organization_id",referencedColumnName="id", insertable=false, updatable=false)
//#JsonIgnore
private Organization organization;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.MERGE)
//#Fetch(org.hibernate.annotations.FetchMode.SELECT)
#JoinTable(
name="user_role",
joinColumns={#JoinColumn(name="user_id")},
inverseJoinColumns={#JoinColumn(name="role_id")})
private List<Role> roles;
/*
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.MERGE)
//#Fetch(org.hibernate.annotations.FetchMode.SELECT)
#Fetch(value = FetchMode.SUBSELECT)
#JoinTable(
name="hyperbridge_resource.user_workspace",
joinColumns={#JoinColumn(name="user_id")},
inverseJoinColumns={#JoinColumn(name="workspace_id")})
private List<Workspace> workspaces;
*/
#Column(length = 1, columnDefinition ="char")
private String active;
#Column(name = "reset_token")
#JsonIgnore
private String resetToken;
#Column(name = "reset_token_time")
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime resetTokenTime;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Long getOrganization_id() {
return organization_id;
}
public void setOrganization_id(Long organization_id) {
this.organization_id = organization_id;
}
public Organization getOrganization() {
return organization;
}
public void setOrganization(Organization organization) {
this.organization = organization;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
/* public List<Workspace> getWorkspaces() {
return workspaces;
}
public void setWorkspaces(List<Workspace> workspaces) {
this.workspaces = workspaces;
}*/
public String getActive() {
return active;
}
public void setActive(String active) {
this.active = active;
}
public String getResetToken() {
return resetToken;
}
public void setResetToken(String resetToken) {
this.resetToken = resetToken;
}
public LocalDateTime getResetTokenTime() {
return resetTokenTime;
}
public void setResetTokenTime(LocalDateTime resetTokenTime) {
this.resetTokenTime = resetTokenTime;
}
}
Try this, doc:
#Around(
"execution(* net.inter.warp.bridge.controller.*.*(..) && " +
"args(authenticatedUser,..)"

JpaRepository findBy boolean

I am implementing an API to return all flagged users (boolean true). I am novice on Spring Boot and I am wondering what would be the best way to implement that method on the UserDAO
Does that DAO method makes sense, I want to return a LIST a Users with FLAG set to true.
User
#Entity
#Table(name = "user", schema = "public")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "userid", nullable = false)
private Long userId;
#Column(name = "flag")
private Boolean flag;
public Boolean getFlag() {
return flag;
}
public void setFlag(Boolean flag) {
this.flag = flag;
}
DAO
#Repository
public interface UserDao extends JpaRepository<User, Long> {
List<User> findByRoleId(Integer roleId);
Service
public Execution<User> getFlaggedUsers() {
Execution<User> res;
try {
List<User> users = userDao.findUsersByFlag();
res = new Execution<>(ResultEnum.SUCCESS, users);
} catch (Exception e) {
res = new Execution<>(ResultEnum.INNER_ERROR);
}
return res;
}
Controller
#GetMapping("/flagged_users")
public Map<String, Object> getFlaggedUsers() throws InternalException {
Map<String, Object> resultMap = new HashMap<>();
try {
Execution<User> res = userService.getFlaggedUsers();
resultMap.put(Constants.USER.getStatusCode(), res.getObjects());
} catch (Exception e) {
throw new InternalException(e.getMessage());
}
return resultMap;
}
Your DAO method should be
List<User> findByFlag(boolean flag) where you can pass required flag.
Or
List<User> findByFlagTrue() which will returns all the users where flag = true.

Spring Boot Security 403 "Access Denied"

I am making a RESTFul API (not web-app) and adding Spring Security but unable to do it successfully.
After going through a lot of articles and posts here on stackoverflow, I am finally posting my question. Kindly go through it and let me know what I am missing or configuring wrongly?
Base Entity
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
abstract class BaseEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "ID", nullable = false, updatable = false)
private Long ID;
#CreatedBy
#Column(name = "CreatedBy", nullable = false, updatable = false)
private String createdBy;
#CreatedDate
#Column(name = "CreatedDate", nullable = false, updatable = false)
private LocalDateTime createdDate;
#LastModifiedBy
#Column(name = "ModifiedBy")
private String modifiedBy;
#LastModifiedDate
#Column(name = "ModifiedDate")
private LocalDateTime modifiedDate;
...getters setters
}
Role Entity
#Entity
#Table(name = "ROLE")
public class Role extends BaseEntity {
#Column(name = "Name")
private String name;
...getters setters
}
User Entity
#Entity
#Table(name = "USER")
public class User extends BaseEntity {
#Column(name = "EmiratesID", unique = true, nullable = false, updatable = false)
private String emiratesID;
#Column(name = "FirstName")
private String firstName;
#Column(name = "LastName")
private String lastName;
#Column(name = "StaffID", unique = true, nullable = false, updatable = false)
private String staffID;
#Column(name = "Email", unique = true, nullable = false)
private String email;
#Column(name = "Password", nullable = false)
private String password;
#ManyToOne(optional = false, cascade = CascadeType.MERGE)
#JoinColumn(name = "ROLE_ID")
private Role role;
...getters setters
public UserDetails currentUserDetails() {
return CurrentUserDetails.create(this);
}
}
SecurtiyConfig Class
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final DataSource dataSource;
private final UserDetailsServiceImplementation userDetailsService;
#Autowired
public SecurityConfig(final DataSource dataSource, final UserDetailsServiceImplementation userDetailsService) {
this.dataSource = dataSource;
this.userDetailsService = userDetailsService;
}
#Bean
BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.antMatchers("/console/**").permitAll()
.antMatchers("/", "/greetUser", "/register", "/login").permitAll()
.antMatchers("/user/**").hasAnyAuthority(ROLES.USER.getValue(), ROLES.ADMIN.getValue())
.antMatchers("/admin/**").hasAuthority(ROLES.ADMIN.getValue()).anyRequest().authenticated();
httpSecurity.csrf().disable();
// required to make H2 console work with Spring Security
httpSecurity.headers().frameOptions().disable();
}
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
public void configure(WebSecurity webSecurity) {
webSecurity.ignoring().antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**");
}
CurrentUserDetails
public class CurrentUserDetails implements UserDetails {
private String ROLE_PREFIX = "ROLE_";
private Long userID;
private String emiratesID;
private String firstName;
private String lastName;
private String staffID;
private String email;
private String password;
private Role role;
public CurrentUserDetails(Long ID, String emiratesID, String firstName,
String lastName, String staffID, String email,
String password, Role role) {
super();
this.userID = ID;
this.emiratesID = emiratesID;
this.firstName = firstName;
this.lastName = lastName;
this.staffID = staffID;
this.email = email;
this.password = password;
this.role = role;
}
public Long getUserID() {
return userID;
}
public String getEmiratesID() {
return emiratesID;
}
public String getEmail() {
return this.email;
}
public Role getRole() {
return this.role;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> grantedAuthority = new ArrayList<>();
grantedAuthority.add(new SimpleGrantedAuthority(ROLE_PREFIX + role.getName()));
return grantedAuthority;
}
#Override
public String getPassword() {
return this.password;
}
#Override
public String getUsername() {
return this.email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
/**
* Helper method to add all details of Current User into Security User Object
* #param user User
* #return UserDetails
*/
public static UserDetails create(User user) {
return new CurrentUserDetails(user.getID(), user.getEmiratesID(),
user.getFirstName(), user.getLastName(),
user.getStaffID(), user.getEmail(),
user.getPassword(), user.getRole());
}
}
UserDetailsService
#Component/#Service
public class UserDetailsServiceImplementation implements UserDetailsService {
private static final Logger userDetailsServiceImplementationLogger = LogManager.getLogger(UserDetailsServiceImplementation.class);
private final UserRepository userRepository;
#Autowired
public UserDetailsServiceImplementation(final UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StringUtils.isEmpty(username)) {
userDetailsServiceImplementationLogger.error("UserDetailsServiceImplementation.loadUserByUsername() :: FAILED");
throw new UsernameNotFoundException("UserName is not passed");
}
User userFound = userRepository.findByEmail(username);
if (userFound == null) {
userDetailsServiceImplementationLogger.error("No user found with given username = {}", username);
throw new UsernameNotFoundException("No user found with given username");
}
return userFound.currentUserDetails();
}
}
UserController Class
#RestController
#RequestMapping(value = "/user")
public class UserController {
private static Logger userControllerLogger = LogManager.getLogger(UserController.class);
#Autowired
private PropertiesConfig propertiesConfig;
#Autowired
private UserManager userManager;
#RequestMapping(value = "/listAll", method = RequestMethod.GET)
public ResponseEntity<Map<String, Object>> getUsersList() {
userControllerLogger.info("UserController.getUsersList()[/listAll] :: method call ---- STARTS");
LinkedHashMap<String, Object> result = userManager.findAllUsers();
userControllerLogger.info("UserController.getUsersList()[/listAll] :: method call ---- ENDS");
return new ResponseEntity<>(result, HttpStatus.OK);
}
}
AdminContrller Class
#RestController
#RequestMapping(value = "/admin")
public class AdminController {
private static final Logger adminControllerLogger = LogManager.getLogger(AdminController.class);
private final PropertiesConfig propertiesConfig;
private final UserManager userManager;
#Autowired
public AdminController(final PropertiesConfig propertiesConfig, final UserManager userManager) {
this.propertiesConfig = propertiesConfig;
this.userManager = userManager;
}
#RequestMapping(value = "/home", method = {RequestMethod.GET})
public ResponseEntity<String> adminPortal(#RequestBody String adminName) {
adminControllerLogger.info("AdminController.adminPortal()[/home] :: method call ---- STARTS");
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UserDTO adminUser = userManager.findUserByEmail(auth.getName());
if (adminUser == null) {
throw new UsernameNotFoundException(propertiesConfig.getProperty(ApplicationProperties.Messages.NO_USER_FOUND.getValue()));
}
adminControllerLogger.info("AdminController.adminPortal()[/home] :: method call ---- ENDS");
return new ResponseEntity<>(ApplicationConstants.GeneralConstants.WELCOME.getValue() + adminUser.getStaffID(), HttpStatus.OK);
}
}
data.sql
Tried with both values ROLE_USER/ADMIN and USER/ADMIN
INSERT INTO ROLE(ID, CreatedBy, CreatedDate, ModifiedBy, ModifiedDate, Name) VALUES (-100, 'Muhammad Faisal Hyder', now(), '', null, 'ROLE_ADMIN'/'ADMIN')
INSERT INTO ROLE(ID, CreatedBy, CreatedDate, ModifiedBy, ModifiedDate, Name) VALUES (-101, 'Muhammad Faisal Hyder', now(), '', null, 'ROLE_USER'/'USER')
INSERT INTO USER(ID, CreatedBy, CreatedDate, ModifiedBy, ModifiedDate, EmiratesID, FirstName, LastName, Email, StaffID, Password, ROLE_ID) VALUES (-1, 'Muhammad Faisal Hyder', now(), '', null, 'ABCDEF12345', 'Muhammad Faisal', 'Hyder', 'faisal.hyder#gmail.com', 'S776781', '$2a$10$qr.SAgYewyCOh6gFGutaWOQcCYMFqSSpbVZo.oqsc428xpwoliu7C', -100)
INSERT INTO USER(ID, CreatedBy, CreatedDate, ModifiedBy, ModifiedDate, EmiratesID, FirstName, LastName, Email, StaffID, Password, ROLE_ID) VALUES (-2, 'Muhammad Faisal Hyder', now(), '', null, 'BCDEFG12345', 'John', 'Smith', 'John.Smith#gmail.com', 'S776741', '$2a$10$j9IjidIgwDfNGjNi8UhxAeLuoO8qgr/UH9W9.LmWJd/ohynhI7UJO', -101)
I have attached all possible classes I think which are necessary. Kindly let me know what can be the issue.
Articles I went through;
SO-1, SO-2, SO-3, SO-4, Article-1, Article-2
Resolved
#dur thanks to you for pointing it out and others as well for their helpful insights.
1- Use ROLE_ in db entries.
2- Once prefix is added in db then no need to explicitly add this in
#Override
public Collection<? extends GrantedAuthority> getAuthorities(){...}
3- .and().httpBasic(); was missing from SpringSecurity configuration.
4- This is very detailed, might be helpful to others as well.
The problem I'm seeing is that you're granting access for authority ADMIN but you're not adding this authority to the CurrentUserDetails, you're just adding their role. You should add the authority as well, i.e.
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> grantedAuthority = new ArrayList<>();
grantedAuthority.add(new SimpleGrantedAuthority(ROLE_PREFIX + role.getName()));
// add authority in addition to role (no role prefix)
grantedAuthority.add(new SimpleGrantedAuthority(role.getName()));
return grantedAuthority;
}
As #dur pointed out in comments, I am adding answer to my question.
1- Use ROLE_ in db entries.
2- Once prefix is added in db then no need to explicitly add this in
#Override
public Collection<? extends GrantedAuthority> getAuthorities(){...}
3- .and().httpBasic(); was missing from SpringSecurity configuration.
Since this post is very detailed, might be helpful to others as well. For corrected answer kindly refer to my git repo

Spring + JPA "Lock wait timeout exceeded; try restarting transaction"

I'm new to Spring and JPA and I encountered the problem specified in the title. To simplify the problem, I have two classes: User and FeedItem. User can have more FeedItems but the relationship is bi-directional (FeedItem knows with which User it's associated). They're both persisted in the database using JPA+Hibernate:
#Entity
#Table
public class User
{
#Id
#GeneratedValue
#Column(name = "id", nullable = false, length = 8)
private int id;
#Column(nullable = false, length = 32, unique = true)
private String alias;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<FeedItem> feedItems = new ArrayList<FeedItem>();
public User()
{
}
public User(String alias)
{
this.alias = alias;
}
... getters/setters...
}
#Entity
#Table
public class FeedItem
{
#Id
#GeneratedValue
#Column(name = "id", nullable = false, length = 16)
private int id;
#Column(nullable = false, length = 64)
private String title;
#ManyToOne
#JoinColumn(name = "userId", nullable = false)
private User user;
public FeedItem()
{
}
public FeedItem(String title, User user)
{
this.title = title;
this.user = user;
}
... getters/setters...
}
The DAOs:
#Repository
public class UserJpaDao implements UserDao
{
private EntityManager em;
#Transactional
public User save(User user)
{
return this.em.merge(user);
}
#Transactional
public void delete(User user)
{
this.em.remove(user);
}
#Transactional(readOnly = true)
public User findById(int id)
{
return this.em.find(User.class, id);
}
#PersistenceContext
void setEntityManager(EntityManager entityManager)
{
this.em = entityManager;
}
}
#Repository
public class FeedItemJpaDao implements FeedItemDao
{
private EntityManager em;
#Transactional
public FeedItem save(FeedItem feedItem)
{
return this.em.merge(feedItem);
}
#Transactional
public void delete(FeedItem feedItem)
{
this.em.remove(feedItem);
}
#Transactional
public FeedItem findById(int id)
{
return this.em.find(FeedItem.class, id);
}
#PersistenceContext
void setEntityManager(EntityManager entityManager)
{
this.em = entityManager;
}
}
This is the test giving the error:
#RunWith(SpringJUnit4ClassRunner.class)
public class FeedItemJpaDaoTest
{
#Autowired
private DriverManagerDataSource dataSource;
#Autowired
private FeedItemJpaDao feedItemDao;
#Autowired
private UserJpaDao userDao;
#Before
#Transactional
public void setUp() throws Exception
{
DatabaseOperation.CLEAN_INSERT.execute(getConnection(), getDataSet());
}
#After
#Transactional
public void tearDown() throws Exception
{
DatabaseOperation.DELETE_ALL.execute(getConnection(), getDataSet());
}
#Test
#Transactional
public void testSave() throws Exception
{
User user = userDao.findById(3);
FeedItem feedItem = new FeedItem("Achievement unlocked!", user);
feedItem = feedItemDao.save(feedItem);
assertEquals(feedItem, feedItemDao.findById(feedItem.getId()));
}
private IDatabaseConnection getConnection() throws Exception
{
return new DatabaseConnection(dataSource.getConnection());
}
private IDataSet getDataSet() throws Exception
{
return new FlatXmlDataSetBuilder().build(new File("src/test/resources/dataset.xml"));
}
}
I don't understand why the error is happening -- any suggestion is appreciated!
Thank you.
EDIT: Seems like the problem is due to DbUnit: if I comment out the tearDown() method, the error doesn't occour
Solved the problem following this: http://tadaya.wordpress.com/2008/04/27/transaction-aware-datasource-use-dbunit-hibernate-in-spring/

Resources