Skip to content

Commit

Permalink
Merge pull request #15 from aistoica/Authentication
Browse files Browse the repository at this point in the history
[Authentication] Added basic http authenticatio non all endpoints
  • Loading branch information
vfedoriv authored Jun 22, 2018
2 parents 170046b + e73c997 commit 981d102
Show file tree
Hide file tree
Showing 42 changed files with 851 additions and 75 deletions.
9 changes: 9 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
Expand Down Expand Up @@ -102,6 +106,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
Expand Down
29 changes: 29 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,35 @@ You may also start a Postgres database with docker:
docker run --name postgres-petclinic -e POSTGRES_PASSWORD=petclinic -e POSTGRES_DB=petclinic -p 5432:5432 -d postgres:9.6.0
```

## Security configuration
In its default configuration, Petclinic doesn't have authentication and authorization enabled.

### Basic Authentication
In order to use the basic authentication functionality, turn in on from the application.properties file
```
basic.authentication.enabled=true
```
This will secure all APIs and in order to access them, basic authentication is required.
Apart from authentication, APIs also require authorization. This is done via roles that a user can have.
The existing roles are listed below with the corresponding permissions
* OWNER_ADMIN -> OwnerController, PetController, PetTypeController (getAllPetTypes and getPetType), VisitController
* VET_ADMIN -> PetTypeController, SpecialityController, VetController
* ADMIN -> UserController

There is an existing user with the username admin and password admin that has access to all APIs.
In order to add a new user, please use the following API:
```
POST /api/users
{
"username": "secondAdmin",
"password": "password",
"enabled": true,
"roles": [
{ "name" : "OWNER_ADMIN" }
]
}
```

## Working with Petclinic in Eclipse/STS

### prerequisites
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.springframework.samples.petclinic.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(name = "roles" ,uniqueConstraints = @UniqueConstraint(columnNames = {"username", "role"}))
public class Role extends BaseEntity {

@ManyToOne
@JoinColumn(name = "username")
@JsonIgnore
private User user;

@Column( name = "role")
private String name;

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.springframework.samples.petclinic.model;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(name = "users")
public class User {

@Id
@Column(name = "username")
private String username;

@Column(name = "password")
private String password;

@Column(name = "enabled")
private Boolean enabled;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.EAGER)
private Set<Role> roles;

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public Boolean getEnabled() {
return enabled;
}

public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}

public Set<Role> getRoles() {
return roles;
}

public void setRoles(Set<Role> roles) {
this.roles = roles;
}

@JsonIgnore
public void addRole(String roleName) {
if(this.roles == null) {
this.roles = new HashSet<>();
}
Role role = new Role();
role.setName(roleName);
this.roles.add(role);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.springframework.samples.petclinic.repository;

import org.springframework.dao.DataAccessException;
import org.springframework.samples.petclinic.model.User;

public interface UserRepository {

void save(User user) throws DataAccessException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.springframework.samples.petclinic.repository.jdbc;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.samples.petclinic.model.Role;
import org.springframework.samples.petclinic.model.User;
import org.springframework.samples.petclinic.repository.UserRepository;
import org.springframework.stereotype.Repository;

@Repository
@Profile("jdbc")
public class JdbcUserRepositoryImpl implements UserRepository {

private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private SimpleJdbcInsert insertUser;

@Autowired
public JdbcUserRepositoryImpl(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
this.insertUser = new SimpleJdbcInsert(dataSource).withTableName("users");
}

@Override
public void save(User user) throws DataAccessException {

BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user);

try {
getByUsername(user.getUsername());
this.namedParameterJdbcTemplate.update("UPDATE users SET password=:password, enabled=:enabled WHERE username=:username", parameterSource);
} catch (EmptyResultDataAccessException e) {
this.insertUser.execute(parameterSource);
} finally {
updateUserRoles(user);
}
}

private User getByUsername(String username) {

Map<String, Object> params = new HashMap<>();
params.put("username", username);
return this.namedParameterJdbcTemplate.queryForObject("SELECT * FROM users WHERE username=:username",
params, BeanPropertyRowMapper.newInstance(User.class));
}

private void updateUserRoles(User user) {
Map<String, Object> params = new HashMap<>();
params.put("username", user.getUsername());
this.namedParameterJdbcTemplate.update("DELETE FROM roles WHERE username=:username", params);
for (Role role : user.getRoles()) {
params.put("role", role.getName());
if (role.getName() != null) {
this.namedParameterJdbcTemplate.update("INSERT INTO roles(username, role) VALUES (:username, :role)", params);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.springframework.samples.petclinic.repository.jpa;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.context.annotation.Profile;
import org.springframework.dao.DataAccessException;
import org.springframework.samples.petclinic.model.User;
import org.springframework.samples.petclinic.repository.UserRepository;
import org.springframework.stereotype.Repository;

@Repository
@Profile("jpa")
public class JpaUserRepositoryImpl implements UserRepository {

@PersistenceContext
private EntityManager em;

@Override
public void save(User user) throws DataAccessException {
if (this.em.find(User.class, user.getUsername()) == null) {
this.em.persist(user);
} else {
this.em.merge(user);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.springframework.samples.petclinic.repository.springdatajpa;

import org.springframework.context.annotation.Profile;
import org.springframework.data.repository.Repository;
import org.springframework.samples.petclinic.model.User;
import org.springframework.samples.petclinic.repository.UserRepository;

@Profile("spring-data-jpa")
public interface SpringDataUserRepository extends UserRepository, Repository<User, Integer> {

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.samples.petclinic.model.Owner;
import org.springframework.samples.petclinic.service.ClinicService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -50,6 +51,7 @@ public class OwnerRestController {
@Autowired
private ClinicService clinicService;

@PreAuthorize( "hasRole(@roles.OWNER_ADMIN)" )
@RequestMapping(value = "/*/lastname/{lastName}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Collection<Owner>> getOwnersList(@PathVariable("lastName") String ownerLastName) {
if (ownerLastName == null) {
Expand All @@ -62,6 +64,7 @@ public ResponseEntity<Collection<Owner>> getOwnersList(@PathVariable("lastName")
return new ResponseEntity<Collection<Owner>>(owners, HttpStatus.OK);
}

@PreAuthorize( "hasRole(@roles.OWNER_ADMIN)" )
@RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Collection<Owner>> getOwners() {
Collection<Owner> owners = this.clinicService.findAllOwners();
Expand All @@ -71,6 +74,7 @@ public ResponseEntity<Collection<Owner>> getOwners() {
return new ResponseEntity<Collection<Owner>>(owners, HttpStatus.OK);
}

@PreAuthorize( "hasRole(@roles.OWNER_ADMIN)" )
@RequestMapping(value = "/{ownerId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Owner> getOwner(@PathVariable("ownerId") int ownerId) {
Owner owner = null;
Expand All @@ -81,6 +85,7 @@ public ResponseEntity<Owner> getOwner(@PathVariable("ownerId") int ownerId) {
return new ResponseEntity<Owner>(owner, HttpStatus.OK);
}

@PreAuthorize( "hasRole(@roles.OWNER_ADMIN)" )
@RequestMapping(value = "", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Owner> addOwner(@RequestBody @Valid Owner owner, BindingResult bindingResult,
UriComponentsBuilder ucBuilder) {
Expand All @@ -96,6 +101,7 @@ public ResponseEntity<Owner> addOwner(@RequestBody @Valid Owner owner, BindingRe
return new ResponseEntity<Owner>(owner, headers, HttpStatus.CREATED);
}

@PreAuthorize( "hasRole(@roles.OWNER_ADMIN)" )
@RequestMapping(value = "/{ownerId}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Owner> updateOwner(@PathVariable("ownerId") int ownerId, @RequestBody @Valid Owner owner,
BindingResult bindingResult, UriComponentsBuilder ucBuilder) {
Expand All @@ -119,6 +125,7 @@ public ResponseEntity<Owner> updateOwner(@PathVariable("ownerId") int ownerId, @
return new ResponseEntity<Owner>(currentOwner, HttpStatus.NO_CONTENT);
}

@PreAuthorize( "hasRole(@roles.OWNER_ADMIN)" )
@RequestMapping(value = "/{ownerId}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@Transactional
public ResponseEntity<Void> deleteOwner(@PathVariable("ownerId") int ownerId) {
Expand Down
Loading

0 comments on commit 981d102

Please sign in to comment.