Friday, 16 August 2019

Spring Security: Working with Authorizations


In this post, I am going to explain how to secure web application using user roles/authorizations.

Step 1: Create User Entity.
@Entity
@Table(name = "user")
public class User {

    @Id
    @Column(name = "user_id")
    @GeneratedValue
    private int id;

    @Column(name = "user_name", nullable = false, unique = true)
    private String userName;

    @Column(name = "user_password", nullable = false)
    private String password;

    ......
    ......
}


Step 2: Create UserAuthorizations entity.
@Entity
@Table(name = "user_authorizations")
public class UserAuthorizations {

    @Id
    @Column(name = "user_auth_group_id")
    @GeneratedValue
    private int id;

    @Column(name = "user_name")
    private String userName;

    @Column(name = "auth_group")
    private String authGroup;

    .....
    .....
}

'authGroup' property specifies the role of the user.

Step 3: Implement UserDetails interface and override 'getAuthorities' method.
public class UserPrincipal implements UserDetails {


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(authGroups == null) return Collections.EMPTY_SET;
        
        Set<GrantedAuthority> grantedAuthorities = new HashSet<> ();
        
        for(UserAuthorizations authGroup: authGroups) {
            grantedAuthorities.add(new SimpleGrantedAuthority(authGroup.getAuthGroup()));
        }
        
        return grantedAuthorities;
        
    }
    ......
    ......

}


Step 4: Implement UserDetailsService service.
@Service
public class UserDetailsServiceImpl implements UserDetailsService{

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUserName(username);
        
        if(user == null) {
            throw new UsernameNotFoundException("User not exist");
        }
        
        List<UserAuthorizations> authGroups = authGroupRepository.findByUserName(user.getUserName());
        
        
        return new UserPrincipal(user, authGroups);
    }

}

Step 5: Use '@PreAuthorize' annotation to set the roles on APIs.
@RestController
@RequestMapping("employees/")
public class EmployeeController {

    
    @RequestMapping(value = "registered/count", method = RequestMethod.GET)
    @PreAuthorize("hasRole('ADMIN')")
    public String countEmps() {
        return "Total Registered Employees : "+  1024;
    }
    
    @RequestMapping(value = "greetMe", method = RequestMethod.GET)
    @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
    public String greetMe() {
        return "Very Good Day to you";
    }
}

Step 6: Extend 'WebSecurityConfigurerAdapter' and provide a bean of type GrantedAuthoritiesMapper.
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class ApplicationSecurityConfiguration extends WebSecurityConfigurerAdapter {
 
 @Bean
 public DaoAuthenticationProvider authenticationProvider() {
  DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
  daoAuthenticationProvider.setUserDetailsService(userDetailsService);
  daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
  daoAuthenticationProvider.setAuthoritiesMapper(authoritiesMapper());
  return daoAuthenticationProvider;
 }
 
 @Bean
 public GrantedAuthoritiesMapper authoritiesMapper(){
  SimpleAuthorityMapper authorityMapper = new SimpleAuthorityMapper();
  authorityMapper.setConvertToUpperCase(true);
  authorityMapper.setDefaultAuthority("ROLE_USER");
  return authorityMapper;
 }

}


Total project structure looks like below.

Find the below working application.

User.java
package com.sample.app.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

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

 @Id
 @Column(name = "user_id")
 @GeneratedValue
 private int id;

 @Column(name = "user_name", nullable = false, unique = true)
 private String userName;

 @Column(name = "user_password", nullable = false)
 private String password;

 public int getId() {
  return id;
 }

 public void setId(int id) {
  this.id = id;
 }

 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;
 }

}


UserAuthorizations.java
package com.sample.app.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "user_authorizations")
public class UserAuthorizations {

 @Id
 @Column(name = "user_auth_group_id")
 @GeneratedValue
 private int id;

 @Column(name = "user_name")
 private String userName;

 @Column(name = "auth_group")
 private String authGroup;

 public int getId() {
  return id;
 }

 public void setId(int id) {
  this.id = id;
 }

 public String getUserName() {
  return userName;
 }

 public void setUserName(String userName) {
  this.userName = userName;
 }

 public String getAuthGroup() {
  return authGroup;
 }

 public void setAuthGroup(String authGroup) {
  this.authGroup = authGroup;
 }

}


UserPrincipal.java
package com.sample.app.model;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class UserPrincipal implements UserDetails {

 private static final long serialVersionUID = 1L;
 private User user;
 private List<UserAuthorizations> authGroups;

 public UserPrincipal(final User user, final List<UserAuthorizations> authGroups) {
  super();
  this.user = user;
  this.authGroups = authGroups;
 }

 @Override
 public Collection<? extends GrantedAuthority> getAuthorities() {
  if(authGroups == null) return Collections.EMPTY_SET;
  
  Set<GrantedAuthority> grantedAuthorities = new HashSet<> ();
  
  for(UserAuthorizations authGroup: authGroups) {
   grantedAuthorities.add(new SimpleGrantedAuthority(authGroup.getAuthGroup()));
  }
  
  return grantedAuthorities;
  
 }

 @Override
 public String getPassword() {
  return user.getPassword();
 }

 @Override
 public String getUsername() {
  return user.getUserName();
 }

 @Override
 public boolean isAccountNonExpired() {
  return true;
 }

 @Override
 public boolean isAccountNonLocked() {
  return true;
 }

 @Override
 public boolean isCredentialsNonExpired() {
  return true;
 }

 @Override
 public boolean isEnabled() {
  return true;
 }


}


UserAuthorizationRepository.java
package com.sample.app.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.sample.app.model.UserAuthorizations;

public interface UserAuthorizationRepository extends JpaRepository<UserAuthorizations, Integer>{

 List<UserAuthorizations> findByUserName(String userName);
}


UserRepository.java
package com.sample.app.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.sample.app.model.User;

public interface UserRepository extends JpaRepository<User, Integer> {

 User findByUserName(String userName);
}


UserDetailsServiceImpl.java
package com.sample.app.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.sample.app.model.UserAuthorizations;
import com.sample.app.model.User;
import com.sample.app.model.UserPrincipal;
import com.sample.app.repository.UserAuthorizationRepository;
import com.sample.app.repository.UserRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService{
 
 @Autowired
 private UserRepository userRepository;
 
 @Autowired
 private UserAuthorizationRepository authGroupRepository;

 @Override
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  User user = userRepository.findByUserName(username);
  
  if(user == null) {
   throw new UsernameNotFoundException("User not exist");
  }
  
  List<UserAuthorizations> authGroups = authGroupRepository.findByUserName(user.getUserName());
  
  
  return new UserPrincipal(user, authGroups);
 }

}


ApplicationSecurityConfiguration.java
package com.sample.app.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.sample.app.service.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class ApplicationSecurityConfiguration extends WebSecurityConfigurerAdapter {

 @Autowired
 private UserDetailsServiceImpl userDetailsService;
 
 @Autowired
 private PasswordEncoder passwordEncoder;
 
 @Bean
 public DaoAuthenticationProvider authenticationProvider() {
  DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
  daoAuthenticationProvider.setUserDetailsService(userDetailsService);
  daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
  daoAuthenticationProvider.setAuthoritiesMapper(authoritiesMapper());
  return daoAuthenticationProvider;
 }

 @Override
 protected void configure(HttpSecurity httpSecurity) throws Exception {
  httpSecurity.csrf().disable().authorizeRequests().antMatchers("/", "/public/*", "/css/*", "/js/*").permitAll()
    .anyRequest().authenticated().and().httpBasic();

 }
 
 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception{
  auth.authenticationProvider(authenticationProvider());
 }
 
 @Bean
 public GrantedAuthoritiesMapper authoritiesMapper(){
  SimpleAuthorityMapper authorityMapper = new SimpleAuthorityMapper();
  authorityMapper.setConvertToUpperCase(true);
  authorityMapper.setDefaultAuthority("ROLE_USER");
  return authorityMapper;
 }

}


EmployeeController.java
package com.sample.app.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("employees/")
public class EmployeeController {

 
 @RequestMapping(value = "registered/count", method = RequestMethod.GET)
 @PreAuthorize("hasRole('ADMIN')")
 public String countEmps() {
  return "Total Registered Employees : "+  1024;
 }
 
 @RequestMapping(value = "greetMe", method = RequestMethod.GET)
 @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
 public String greetMe() {
  return "Very Good Day to you";
 }
}


HelloWorldController.java
package com.sample.app.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {
 @RequestMapping("/")
 public String homePage() {
  return "Welcome to Spring boot Application Development using Spring Security";
 }
 
 @RequestMapping("/public/aboutme")
 public String aboutMe() {
  return "I am securied by spring security module";
 }
 
}


App.java
package com.sample.app;

import com.sample.app.model.User;
import com.sample.app.model.UserAuthorizations;

import java.util.Arrays;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.sample.app.repository.UserAuthorizationRepository;
import com.sample.app.repository.UserRepository;

@SpringBootApplication
public class App {
 public static void main(String[] args) {

  SpringApplication.run(App.class, args);
 }

 @Bean
 public CommandLineRunner demo(UserRepository userRepository,
   UserAuthorizationRepository userAuthorizationRepository) {
  return (args) -> {

   User user1 = new User();
   user1.setUserName("krishna");
   user1.setPassword(passwordEncoder().encode("password123"));

   User user2 = new User();
   user2.setUserName("ram");
   user2.setPassword(passwordEncoder().encode("ram123"));

   UserAuthorizations userAuthorization1 = new UserAuthorizations();
   userAuthorization1.setUserName("krishna");
   userAuthorization1.setAuthGroup("ROLE_ADMIN");

   UserAuthorizations userAuthorization2 = new UserAuthorizations();
   userAuthorization2.setUserName("ram");
   userAuthorization2.setAuthGroup("ROLE_USER");

   userRepository.saveAll(Arrays.asList(user1, user2));
   userAuthorizationRepository.saveAll(Arrays.asList(userAuthorization1, userAuthorization2));
  };
 }

 @Bean
 public PasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder(16);
 }
}


application.properties
logging.level.root=WARN
logging.level.org.hibernate=ERROR

## H2 specific properties
spring.h2.console.enabled=true
spring.h2.console.path=/h2

spring.datasource.url=jdbc:h2:file:~/db/myOrg.db;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;

spring.datasource.username=krishna
spring.datasource.password=password123

spring.datasource.driverClassName=org.h2.Driver

## JPA specific properties
# Creates the schema, destroying previous data.
spring.jpa.hibernate.ddl-auto=create

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

#spring.jpa.show-sql=true
#spring.jpa.properties.hibernate.format_sql=true

## Database connection pooling properties
# Number of ms to wait before throwing an exception if no connection is available.
spring.datasource.max-wait=10000

# Maximum number of active connections that can be allocated from this pool at the same time.
spring.datasource.tomcat.max-active=10
spring.datasource.tomcat.max-idle=5
spring.datasource.tomcat.min-idle=3

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true


pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>springSecurity</groupId>
 <artifactId>springSecurity</artifactId>
 <version>1</version>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.1.6.RELEASE</version>
 </parent>

 <dependencies>

  <dependency>
   <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.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>


  <dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
  </dependency>


 </dependencies>
</project>

Run App.java.

Open the url 'http://localhost:8080/employees/greetMe' in browser.

Enter user name as 'ram' and password as 'ram123' and click on OK button.

You can see below kind of screen.
Open the url 'http://localhost:8080/employees/registered/count' in the same browser, since ram has only USER access, you will get 'Forbidden' response.


You can download complete working application from this link.

Previous                                                    Next                                                    Home

No comments:

Post a Comment