Spring Security endpoint protection by dynamic roles - spring-boot

Have a doubt regarding protecting endpoints by user roles. I have a endpoint "/message" it is protected in either of two ways as shown below
1) In Controller as follows
#PreAuthorize("hasAuthority('USER')")
#RequestMapping(value = "/message", method = RequestMethod.GET)
public String message() {
return "Hello World!!!"
}
2) In configuration(WebSecurityConfigurereAdapter) file as follows
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().authenticated()
.antMatchers(HttpMethod.GET, "/message").access("hasAuthority('USER')");
}
As it can be seen role USER has been hard coded in both ways, how can this be achieved dynamically, one way is we can read from database in configuration file and build HttpSecurity, but this happens during start of the application, how can endpoints be protected by new roles created at runtime?

Best way to protect your endpoints that can be changed anytime:
In case if you want to grant/revoke roles/permission from a user, the best approach would be to play with privileges instead of roles. In that case, each user will have just one role. And each role may have a list of privileges, that can be added/removed from a role anytime using UI.
How to do?
public class Privilege{
#Id
private String id;
private String name;
//Constructors + Getters & Setters
}
public class Role{
#Id
private String id;
private String name;
private List<Privilege> privileges;
//Constructors + Getters & Setters
}
public class MyUser{
private Role role;
//All Required params
//Constructors + Getters & Setters
}
public class MyUserDetailsService implements UserDetailsService{
public User loadUserByUsername(final String userId) {
User user = //load your user
Role role = //load above user role
Collection<GrantedAuthority> grantedAuthorities.add(new SimpleGrantedAuthority(merchantRole.getName()));
for(Privilege privilege : role.getPrivileges()){
grantedAuthorities.add(new SimpleGrantedAuthority(privilege.getName()));
}
return new User(username, password, grantedAuthorities);
}
}
And finally your configuration file should look like this:
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().authenticated()
.antMatchers(HttpMethod.GET, "/message").access("hasAuthority('YOUR_PRIVILAGE')");
}
Note
Try to create privileges as indivisible as possible. Mean to say each privilege should be specific to each specific task. So that anytime any role can be created with any combination of privileges without changing antMatchers/security.
Example:
CREATE_USER_PRIVILEGE
UPDATE_USER_PRIVILEGE
DELETE_USER_PRIVILEGE
VIEW_USER_PRIVILEGE
etc.
ADMIN_ROLE = {CREATE_USER_PRIVILEGE, UPDATE_USER_PRIVILEGE, DELETE_USER_PRIVILEGE, VIEW_USER_PRIVILEGE}
ADMIN_ROLE = {VIEW_USER_PRIVILEGE}
etc.

Related

Extends Spring security user class

I'm Working on a Spring security project . I try to extends the security.core.userdetails.User class to add more details while registering the users.
User Extended class
public class UserDetails extends User {
private int id;
private String Country;
public UserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities, int id,
String country) {
super(username, password, authorities);
this.id = id;
Country = country;
}
public UserDetails(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities,
int id, String country) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.id = id;
Country = country;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCountry() {
return Country;
}
public void setCountry(String country) {
Country = country;
}
I have also added Id and country in my entity class(model class).
But when i try to register the user .
It give an error.org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [insert into users (username, password, enabled) values (?,?,?)]; Field 'id' doesn't have a default value; nested exception is java.sql.SQLException: Field 'id' doesn't have a default value
(The value of id and country is hard coded)
Controller class
try {
List<GrantedAuthority> authority = new ArrayList<>();
authority.add(new SimpleGrantedAuthority(form.getRole()));
String encodedPassword = passwordEncoder.encode(form.getPassword());
UserDetails details = new UserDetails(form.getUsername(), encodedPassword, authority, 10 ,"India");
System.out.println(details.getId()+" "+details.getCountry() +" "+details.getUsername());
System.out.println(details);
detailsManager.createUser(details);
}
OUPUT
10 India alpha#gmail.com
com.example.demo.model.UserDetails [Username=alpha#gmail.com, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_ADMIN]]
I don't know why its is calling the parent class constructor.
The SQL is incorrect. Spring Security's INSERT by default populates the username, password, and enabled columns. However, the users table you created requires an id column as well. Since the query doesn't specify the value, it fails.
You could try extending JdbcUserDetailsManager's various methods to be aware of your id field as well. You'd need to at least extend createUser so it adds the id to the INSERT statement and findUserByUsername so it constructs your custom object.
A better way, though, would be to use Spring Data. This allows your domain object to be independent of Spring Security. Also, Spring Data has much broader SQL support.
It might be helpful to call your class something different than a Spring Security interface. So, let's imagine that your custom class (the one with the id) is called YourUser (instead of UserDetails). Now, you can wire a Spring Data-based UserDetailsService to Spring Security like so:
#Service
public class YourUserRepositoryUserDetailsService implements UserDetailsService {
private final YourUserRepository users; // your Spring Data repository
// ... constructor
#Override
public UserDetails findUserByUsername(String username) {
YourUser user = this.users.findByUsername(username)
.orElseThrow(() -> new UserNotFoundException("not found"));
return new UserDetailsAdapter(user);
}
private static class UserDetailsAdapter extends YourUser implements UserDetails {
UserDetailsAdapter(YourUser user) {
super(user); // copy constructor
}
// ... implement UserDetails methods
}
}
This UserDetailsService replaces the JdbcUserDetailsManager that you are publishing.

What is the CLI command to view inside of a set data type in redis

I user a CRUDRepository in my spring data redis project to persist a redis hash in my redis cluster. i have rest api written to persist and get thte values of the data. this works fine.
however my entity annotated with RedisHash is being saved as a set / and i am not able to look inside the value using redis cli.
how do i look inside a set data type(without popping) in redis cli
i looked at redis commands page https://redis.io/commands#set
i only get operations which can pop value . i neeed to simply peek
EDIT:
to make things clearer, i am using spring crudrepo to save the user entity into redis data store. the user entity gets saved as a set data type.
when i query back the user details, i can see entire details of the user
{
userName: "somak",
userSurName: "dattta",
age: 23,
zipCode: "ah56h"
}
i essentially want to do the same using redis cli... but all i get is
127.0.0.1:6379> smembers user
1) "somak"
how do i look inside the somak object.
#RestController
#RequestMapping("/immem/core/user")
public class UserController {
#Autowired
private UserRepository userRepository;
#RequestMapping(path = "/save", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(HttpStatus.OK)
public void saveUserDetails() {
User user = new User();
user.setAge(23);
user.setUserName("somak");
user.setUserSurName("dattta");
user.setZipCode("ah56h");
userRepository.save(user);
}
#RequestMapping(path="/get/{username}", method = RequestMethod.GET, produces = "application/json")
public User getUserDetails(#PathVariable("username") String userName) {
return userRepository.findById(userName).get();
}
}
#Repository
public interface UserRepository extends CrudRepository<User, String>{
}
#RedisHash("user")
public class User {
private #Id String userName;
private #Indexed String userSurName;
private #Indexed int age;
private String zipCode;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserSurName() {
return userSurName;
}
public void setUserSurName(String userSurName) {
this.userSurName = userSurName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
}
I don't understant your descr with your problem, but I understand your title.
In redis set, the member is always string type.
I hope you can offer more info about UserRepository.save:
User user = new User();
user.setAge(23);
user.setUserName("somak");
user.setUserSurName("dattta");
user.setZipCode("ah56h");
userRepository.save(user);
And you can check your redis data and check data type when rest api invoked.

Java 8 default method throws PropertyNotFoundException [duplicate]

I am trying to setup my own JSF tag libary. So I created a composite component with an backing interfaces as a blueprint to build a backing bean for this component.
public interface CompLogin {
String getUsername();
void setUsername(String username);
String getPassword();
void setPassword(String password);
String validateLogin();
default String getPasswordWatermark() {
return "Passwort";
}
default String getUsernameWatermark() {
return "Loginname:";
}
default String getLoginButtonValue() {
return "Login";
}
}
So I have Password, Username and an Validate method for a Login site. Additionally O want serve some default namings for Inputtext watermarks and the Button. If the implementing person want to change it, he could.
I implemented this interface inside a Backing bean of a real application using my own JSF tag.
#Named
#RequestScoped
public class Login implements Serializable, CompLogin {
private String username;
private String password;
#Override
public String getUsername() {
return username;
}
#Override
public void setUsername(String username) {
this.username = username;
}
#Override
public String getPassword() {
return password;
}
#Override
public void setPassword(String password) {
this.password = password;
}
#Override
public String validateLogin() {
System.out.println(username + " " + password);
return null;
}
}
I tought it could work this way. But I get the error: 'Caused by: javax.el.PropertyNotFoundException: The class 'Login' does not have the property 'usernameWatermark'.'
It seems like the default implementation of the interface is not inherited in my implementing bean. Why and what could I do to serve default implementations for my components?
Edit:
I tried the following to ommit a missunderstanding of the interface default method conecpt. I took my interface and der implementing class in a normal java project tried to access the getLoginButtonValue thru the Login class.
public class Main {
public static void main(String[] args) {
Login log = new Login();
System.out.println(log.getLoginButtonValue());
}
}
Works very well. The correct String got printed out. There is no need to implement the default methods to access them. So where is the problem? Maybe there is something like cdi, bean-resolver or somthing else not aware of this java 8 concept?
Working with Apache EL this works by calling the default method by its full name. Try to use it this way in your code:
#{login.getUsernameWatermark()}
The problem is likely caused by EL relying on reflection to find the appropriate accessor methods, but doing it in a way that fails for default methods.
Consider implementing a custom ELResolver similar to what they did here.

How to prevent NULL entry in UPDATE operation using CRUD Repository?

Consider the following Events:-
CREATE User [id=1, name=Ram, city=Delhi, email=abc#gmail.com, phone=12345]
UPDATE User [id=1, city=Mumbai, phone=56789]
For CREATE operation, this is what is stored in the database:-
[id=1, name=Ram, city=Delhi, email=abc#gmail.com, phone=12345]
However, when I perform the UPDATE operation, fields which are not updated becomes null
[id=1, name=null, city=Mumbai, email=null, phone=56789]
Now, my question is that how do I prevent name and email from becoming NULL ??
Here is the code snippet :-
Entity Class:-
#Entity
public class User {
#Id
private Integer id;
private String name;
private String city;
private String email;
private String phone;
//Setters and Getters
}
Repository:-
import org.springframework.data.repository.CrudRepository;
import com.therealdanvega.domain.User;
public interface UserRepository extends CrudRepository<User, Integer>{
}
Application Class :-
#SpringBootApplication
public class JsontodbApplication implements CommandLineRunner {
#Autowired
private UserRepository userRepo;
public static void main(String[] args) {
SpringApplication.run(JsontodbApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
/* CREATE USER */
userRepo.save(createUser());
/* UPDATE USER */
userRepo.save(updateUser());
System.out.println(userRepo.findOne(1));
}
public User createUser() {
User user = new User();
user.setId(1);
user.setName("Ram");
user.setCity("Delhi");
user.setEmail("abc#gmail.com");
user.setPhone("12345");
return user;
}
public User updateUser() {
User user = new User();
user.setId(1);
user.setCity("Mumbai");
user.setPhone("56789");
return user;
}
}
Your code doesn't work like you think it does. For Spring it is actually something like that :
Take new object User with id = 1 , name = Ram ... and put it into that base.
Take new object User with id = 1 , city = Mumbai ... and put it into that base.
Spring Repository works like that, if you save object with NULL id, it will add it. If you save object with given id it will update EVERY value that has changed. So for spring you actually update every field, some for new value and most of them for NULLs. MORE INFO
You should get your object which is return by first save function, and update it again. Like that :
#SpringBootApplication
public class JsontodbApplication implements CommandLineRunner {
#Autowired
private UserRepository userRepo;
public static void main(String[] args) {
SpringApplication.run(JsontodbApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
/* CREATE USER */
User user = userRepo.save(createUser(new User()));
/* UPDATE USER */
userRepo.save(updateUser(user));
System.out.println(userRepo.findOne(1));
}
public User createUser(User user) {
user.setName("Ram");
user.setCity("Delhi");
user.setEmail("abc#gmail.com");
user.setPhone("12345");
return user;
}
public User updateUser(User user) {
user.setCity("Mumbai");
user.setPhone("56789");
return user;
}
}

how to construct #service in Springboots using a payload variable

I'm quite new in spring boots so I hope this is not a silly question
I have a #Service that needs to initiate a class attribute, this attribute needs a information that comes from the RestPayload in the Controller. I'm not finding the most recommend way to do that.
#RestController
public class UserController {
#Autowired
private UserService userService;
#RequestMapping("/searchUser")
public List<UserWrapper> searchUser(#RequestBody UserWrapper userWrapper) {
List<UserWrapper> returnUserWrapper = userService.findByName(userWrapper);
return returnUserWrapper;
}
}
And the service layer, I would like to be something like:
#Service
public class UserService {
private LdapTemplate ldapTemplate;
public static final String BASE_DN = "xxxxxxx";
#Value( value = "${sample.ldap.url}" )
private String ldapUrl;
#Value( value = "${sample.ldap.base}" )
private String ldapBase;
public UserService() {
}
public UserService(String dn, String password) {
LdapContextSource ctxSrc = new LdapContextSource();
System.out.println(this.ldapUrl);
ctxSrc.setUrl(ldapUrl);
ctxSrc.setBase(ldapBase);
ctxSrc.setUserDn(dn);
ctxSrc.setPassword(password);
ctxSrc.afterPropertiesSet(); // this method should be called.\
this.ldapTemplate = new LdapTemplate(ctxSrc);
}
The String dn and String password will come in the REST Payload but the other properties comes from a properties file.
Hope someone can guide me with best practices
for ldap authentication you should have a look on spring-security:
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#ldap
on the other hand you can access almost any request parameter by just injecting it via a annotation like in these examples:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-requestheader

Resources