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, ie
@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
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.