Control JSON data with Spring Security - json

I have a class named Order shown below.
class Order{
private int id;
private String name;
private String amount;
//getters and setters
}
Using Spring security, I need to be able to control the data that is being returned as a response from Spring Controller. For say, Admin can view all the data of Order, but customer can see only name and amount. How can I filter the JSON data with Spring Security.So, final output for admin should be
[{id:1,name:order1,amount:100}, {id:2,name:order2,amount:200}]
and output for customer should be
[{name:order1,amount:100}, {name:order2,amount:200}].
Is there any way to do this

you could hack it a bit with Spring Data and Spring Security:
public interface FooRepository extends CrudRepository<Foo, Long> {
#Query(
value = "select id, amount, case when ?#{hasRole('admin')} then name else null end as name from foo where id=?1",
nativeQuery = true
)
Foo findOne(Long id);
}
You'd need to add an EvaluationContextExtensionSupport bean. This lets you use Spring Security expressions in Spring Data queries:
#Component
public class MyEvaluationContextExtensionSupport extends EvaluationContextExtensionSupport{
#Override
public String getExtensionId() {
return "security";
}
#Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {};
}
}
Or you could try Projections with Spring Data REST
//untested:
#Projection(name = "detailed", types = Foo.class)
public interface FooDetailProjection {
#Value("?#{ hasRole('admin')? target.name: null}")
public String getName();
}
Or consider using Column Security directly in your database.

Related

Spring Web Socket Session throws Jackson Exception

In chat application, there are many rooms(Map type), which consist of Strings, boolean, and List<WebSocketSession>.
I think the problem is List<WebSocketSession> can't be written to JSON.
#RequestMapping(value = "/api/v1/lobby/roomList", method = RequestMethod.GET)
public ResponseEntity<Object> getRooms(
HttpServletRequest request,
HttpServletResponse response) {
logger.debug("RoomCtrl - getRooms");
Map<Integer, Room> rooms = roomService.getRooms();
Map<String, Object> returnMap = new HashMap<>();
returnMap.put("rooms", rooms);
return new ResponseEntity<>(
returnMap,
HttpStatus.OK);
}
This is my method to get rooms from roomService. What I have to do to receive that response correctly?
For giving more information to you, I post Room Class.
public class Room {
private String host, title;
private List<WebSocketSession> members = new ArrayList<>();
private boolean status;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<WebSocketSession> getMembers() {
return members;
}
public void setMembers(List<WebSocketSession> members) {
this.members = members;
}
public boolean isStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
}
A WebSocketSession is an abstraction to send messages over a WebSocket.
In the backend you can maintain WebSocketSession instances (while generally you maintain some specific values of WebSocketSession such as id) to perform some matching (who does the request and so for...) but you will never expose and send them in a JSON object to the clients.
Why send such a payload to the clients ? Why should they know the network details of the other users (IP, sessionID and so for) ? It will just create an overhead and decrease the security level of your application.
So WebSocketSession doesn't implement Serializable and so is defacto not serializable by Jackson (that requires that as most of JSON processing API in Java).
To resolve your issue :
Since the users need to know the name of the other chat room users you should
create a mapping between WebSocketSession.id and their unique pseudo/username with a Map<String, String> for example.
And now expose List<String> users in the JSON object returned.
Yes, you are right, WebSocketSession not json serializable, and anyway you better dont send this info to clientside.
You can use json ignore annotation here
#JsonIgnore
private List<WebSocketSession> members
so Jackson will ignore this field, when trying to serialize Room object

Creating a custom query with Spring DATA JPA?

I'm working on a project with Spring Data JPA. I have a table in the database as my_query.
I want to create a method which takes a string as a parameter, and then execute it as a query in the database.
Method:
executeMyQuery(queryString)
As example, when I pass
queryString= "SELECT * FROM my_query"
then it should run that query in DB level.
The repository class is as follows.
public interface MyQueryRepository extends JpaRepository<MyQuery, Long>{
public MyQuery findById(long id);
#Modifying(clearAutomatically = true)
#Transactional
#Query(value = "?1", nativeQuery = true)
public void executeMyQuery(String query);
}
However, it didn't work as I expected. It gives the following error.
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''select * from my_query;'' at line 1
Is there any other way, that I could achieve this goal?
The only part of it you can parameterise are values used in WHERE clause. Consider this sample from official doc:
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
User findByEmailAddress(String emailAddress);
}
Using EntityManager you can achieve this .
Suppose your entity class is like bellow:
import javax.persistence.*;
import java.math.BigDecimal;
#Entity
#Table(name = "USER_INFO_TEST")
public class UserInfoTest {
private int id;
private String name;
private String rollNo;
public UserInfoTest() {
}
public UserInfoTest(int id, String name) {
this.id = id;
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", nullable = false, precision = 0)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Basic
#Column(name = "name", nullable = true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Basic
#Column(name = "roll_no", nullable = true)
public String getRollNo() {
return rollNo;
}
public void setRollNo(String rollNo) {
this.rollNo = rollNo;
}
}
And your query is "select id, name from users where roll_no = 1001".
Here query will return an object with id and a name column. Your Response class is like below:
Your Response class is like:
public class UserObject{
int id;
String name;
String rollNo;
public UserObject(Object[] columns) {
this.id = (columns[0] != null)?((BigDecimal)columns[0]).intValue():0;
this.name = (String) columns[1];
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRollNo() {
return rollNo;
}
public void setRollNo(String rollNo) {
this.rollNo = rollNo;
}
}
here UserObject constructor will get an Object Array and set data with the object.
public UserObject(Object[] columns) {
this.id = (columns[0] != null)?((BigDecimal)columns[0]).intValue():0;
this.name = (String) columns[1];
}
Your query executing function is like bellow :
public UserObject getUserByRoll(EntityManager entityManager,String rollNo) {
String queryStr = "select id,name from users where roll_no = ?1";
try {
Query query = entityManager.createNativeQuery(queryStr);
query.setParameter(1, rollNo);
return new UserObject((Object[]) query.getSingleResult());
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
Here you have to import bellow packages:
import javax.persistence.Query;
import javax.persistence.EntityManager;
Now your main class, you have to call this function. First get EntityManager and call this getUserByRoll(EntityManager entityManager,String rollNo) function. Calling procedure is given below:
Here is the Imports
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
get EntityManager from this way:
#PersistenceContext
private EntityManager entityManager;
UserObject userObject = getUserByRoll(entityManager,"1001");
Now you have data in this userObject.
Note:
query.getSingleResult() return a object array. You have to maintain the column position and data type with query column position.
select id,name from users where roll_no = 1001
query return a array and it's [0] --> id and 1 -> name.
More info visit this thread .
There is no special support for this. But what you can do is create a custom method with a String parameter and in your implementation get the EntityManager injected and execute it.
Possibly helpful links:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations
How to access entity manager with spring boot and spring data
Note: I would reconsider if what you are trying to do is a good idea because it bleeds implementation details of the repository into the rest of the application.
if you want to add custom query you should add #Param
#Query("from employee where name=:name")
employee findByName(#Param("name)String name);
}
this query will select unique record with match name.this will work
Thank you #ilya. Is there an alternative approach to achieve this task using Spring Data JPA? Without #Query annotation?
I just want to act on this part. yes there is a way you can go about it without using the #query annotation. what you need is to define a derived query from your interface that implements the JPA repository instance.
then from your repository instance you will be exposed to all the methods that allow CRUD operations on your database such as
interface UserRepository extends CrudRepository<User, Long> {
long deleteByLastname(String lastname);
List<User> removeByLastname(String lastname);
}
with these methods spring data will understand what you are trying to archieve and implement them accordingly.
Also put in mind that the basic CRUD operations are provided from the base class definition and you do not need to re define them. for instance this is the JPARepository class as defined by spring so extending it gives you all the methods.
public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
<S extends T> S save(S entity);
Optional<T> findById(ID primaryKey);
Iterable<T> findAll();
long count();
void delete(T entity);
boolean existsById(ID primaryKey);
}
For more current information check out the documentation at https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
Based on #jelies answer, I am using the following approach
You can create another interface for your custom methods (as example MyQueryCustom) and then implement it as follows.
public class MyQueryRepositoryImpl implements MyQueryRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
public int executeQuery(String query) {
return entityManager.createNativeQuery(query).executeUpdate();
}
}
This will execute a custom query.

rest api returns empty bracket for GET request

I implemented Rest api with Spring Boot. In my controller class, I have code to handle GET request which will return JSON if record found.
// SeqController.java
#Autowired
private SeqService seqService;
#RequestMapping(
value = "/api/seqs/{analysis_id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<SeqTb>> getSeqByAnalysisId(#PathVariable("analysis_id") String analysis_id) {
List<SeqTb> seqs = seqService.findByAnalysisId(analysis_id);
return new ResponseEntity(seqs, HttpStatus.OK);
}
I also create a bean class SeqServiceBean that extends the interface SeqService which in turn calls methods from the following JPA repository for query.
//SeqRepository.java
#Repository
public interface SeqRepository extends JpaRepository<SeqTb, Integer> {
#Override
public List<SeqTb> findAll();
public List<SeqTb> findByAnalysisId(String analysisId);
}
Problem is when I typed the url (http://localhost:8080/api/seqs/fdebfd6e-d046-4192-8b97-ac9f65dc2009) in my browser, it returned nothing but a pair of empty brackets. I just looked in the database and that record is indeed there. What did I do wrong?
A bit late to answer this quesiton, but in case anyone else is having this issue.
This problem may be caused by the class (that we want to be displayed as a json object) missing getter and/or setter methods.
In your case the "seqTab" class may be not have getters.
Without the getters our application can not extract the fileds to build the json object.
Example :
Sample user class
public class User {
private String firstname;
private String lasttname;
int age;
public User(){
}
public User(String fname, String lname, int age){
this.firstname = fname;
this.lasttname = lname;
this.age = age;
}
}
Sample rest controller
#RestController
public class SampleRS {
#RequestMapping(value = {"/sample/{input}"}, method = RequestMethod.GET , produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> startService(#PathVariable("input") String input){
User u = new User(input,"bikila",45);
return new ResponseEntity<User>(u,HttpStatus.OK);
}
}
// If we try to hit the endpoint /sample{input} .. e.g.
Request : localhost:8080/Sample/abebe
Response :
{}
But adding the getters for the User class will solve the problem.
Modified User class with getters
public class User {
private String firstname;
private String lasttname;
int age;
public User(){
}
public User(String fname, String lname, int age){
this.firstname = fname;
this.lasttname = lname;
this.age = age;
}
public String getFirstname() {
return firstname;
}
public String getLasttname() {
return lasttname;
}
public int getAge() {
return age;
}
}
Request : http://localhost:8080/sample/abebe
Response : {"firstname":"abebe","lasttname":"bikila","age":45}
Hope that helps !
In most of case, database driver jar is not deployed in server. Check deployment assembly of project in eclipse. Also see console message to check if it is showing driver jar not found.
If this is case simply deploy this jar in deployment assembly of eclipse.
One thing, if build path has this jdbc driverjar in eclipse, main method will connect to database. But if jar is not deployed jdbc connection will not happen over http request.

Role base Json output in Spring Boot

Is is possible to exclude JsonProperties in the output of a Spring Boot Rest call based on a defined condition? (eg. the role of the user)
Example:
public class Employee{
#JsonProperty
private String name;
#JsonProperty
private String fieldForManagerOnly;
#JsonProperty
private String fieldForEmployeeOnly;
}
I want to have the fieldForManagerOnly only serialized in the JSON output when the user has the ROLE manager.
I've already tried the solution with the #JsonView (as described in Latest Jackson integration improvements in Spring) but that solution is very limited as the #JsonView is bound to one controler method and I want to have only one controller method.
I've solved the problem myself. I used the JsonView solution but instead of an annotation I select the JsonView from code.
First you need an interface for the Views.
public class JsonViews {
public interface EmployeeView {}
public interface ManagerView {}
}
Mark the fields in the Model class with the #JsonView annotations.
public class Employee{
#JsonProperty
private String name;
#JsonView(JsonViews.ManagerView.class)
private String fieldForManagerOnly;
#JsonView(JsonViews.EmployeeView.class)
private String fieldForEmployeeOnly;
}
In your controller set the JsonView to use based on the role (or some other condition):
#RequestMapping(value = "/{employeeId}", method = RequestMethod.GET)
public ResponseEntity<MappingJacksonValue> getEmployee(#PathVariable long employeeId) {
Employee employee = employeeService.getEmployee(employeeId);
MappingJacksonValue jacksonValue = new MappingJacksonValue(employeeResourceAssembler.toResource(employee));
if (getRole().equals("MANAGER")) {
jacksonValue.setSerializationView(JsonViews.ManagerView.class);
} else if (getRole().equals("EMPLOYEE")) {
jacksonValue.setSerializationView(JsonViews.EmployeeView.class);
}
return new ResponseEntity<>(jacksonValue, HttpStatus.OK);
}
Annotate the field with
#JsonInclude(JsonInclude.Include.NON_NULL)
and make sure to set the field fieldForManagerOnly to null if the current user is not a manager.

how do you define custom serializer for list of object before adding it to modelAttribute in Spring 4?

#Controller
public class ManageEmployee{
#ModelAttribute("employeeForm")
public EmployeeForm createEmployeeForm(Model model, HttpSession session){
EmployeeForm eform = new EmployeeForm ();
List<EmployeeDTO> eList = employeeService.getEmployeeList(employeeId)//employeeId comes from session
eform.setEmployeeDTO(eList );
model.addAttribute("empoyeeList",eList );
return eform;
}
#RequestMapping(value = LogInUris.MANAGE_EMPLOYEE, method = RequestMethod.GET)
public String showEmployee(Model model, ModelMap map) throws ServiceException{
return "employeeView";
}
}
public class EmployeeDTO{
private String eId;
private String eName;
private String eLastName;
private String positon;
private String role;
//getter//setter
}
when user calls MANAGE_EMPLOYEE url then I return employeeView(jsp) where I have to display list of employees so that user can edit and save it back again. I know I can user #JsonSerialize(using=EmployeeDTOSerializer.class) at my DTO with http request to Controller and annotating #ResponseBody but here I am adding it to model attribute so i want to know how to serialize list of object before i send it to JSP.
You'll have to do it yourself using one of the libraries. For example you could use ObjectMapper from Jackson :
// In configuration:
ObjectMapper mapper=new ObjectMapper();
and
//In Controller
#ModelAttribute("employeeForm")
public EmployeeForm createEmployeeForm(Model model, HttpSession session){
EmployeeForm eform = new EmployeeForm ();
List<EmployeeDTO> eList = employeeService.getEmployeeList(employeeId)//employeeId comes from session
eform.setEmployeeDTO(eList );
model.addAttribute("empoyeeList", mapper.writeValueAsString(eList) );
return eform;
}
This (probably with some modifications) will write json string to the model. However I would not recommend this. I suggest adding an AJAX call to your jsp which would retrieve the list of employees. Then you'll have to add a method to your controller which would return a list and annotate it with #ResponseBody.