Minimal Requirment

  1. Java 17 or higher
  2. Jakarta EE 10
  3. Spring Framework 6
  4. Works on Maven 3.5+
  5. Tomcat 10.0
  6. Improved observability with Micrometer and Micrometer Tracing

Improvements
Performance enhancements and optimizations to boost application responsiveness and efficiency.These improvements focus on reducing startup times, minimizing memory footprint, and optimizing resource utilization.

Changes in Code

  1. When we wanted to configure the Security settings, we had to extend the WebSecurityConfigurerAdapter class.This class has been deprecated and removed in Spring Security 6.
    Instead, we should now take a more component-based approach and create a bean of type SecurityFilterChain.

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig {
     @Bean
      public SecurityFilterChain configure(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(requests -> requests
                .requestMatchers(new AntPathRequestMatcher("/openapi/openapi.yml")).permitAll()
                .anyRequest().authenticated())
            .httpBasic();
        return http.build();
      }
    }
    
  2. Instead of using authorizeRequests, which has been deprecated, we should now use authorizeHttpRequests.This method is part of the HttpSecurity configuration and allows you to configure fine-grained request matching for access control.
  3. Spring Security 6, AntMatcher, MvcMatcher, and RegexMatcher have been depreciated and replaced by requestMatchers or securityMatchers for path-based access control. This allows us to match requests based on patterns or other criteria without relying on specific matchers.
  1. Till now we were sending requests over HTTP. Now lets make HTTP secured using https by installing certificate in our project
  2. To generate certificate we use Key Store Explorer and will generate PKCS#12 Certificate

  3. Generate a public and private key and save the keystore
  4. The Keystore should be copied to the resources folder and the credentials of the keystore should be added to application.properties as below.

    application.properties

    # The format used for the keystore. It could be set to JKS in case it is a JKS file
    server.ssl.key-store-type=PKCS12
    # The path to the keystore containing the certificate
    server.ssl.key-store=classpath:TestCert
    # The password used to generate the certificate
    server.ssl.key-store-password=password
    
  5. Since the certificare generated(keystore file) cannot be cross checked with CA(Certificate Authority) it would display message like one below for the authenticy of certificate, whether it should be accpeted or not. How ever you can continue further by clicking on advanced

Note: While performing CRUD the addition of user is a JSON request which should be carried out only from postman. For this enable interceptor plugin in Chrome so that cookies set in chrome would be available in Postman.

Further Reads:
How SSL Works

  1. Advantage of JSON token is it could be used form multiple platforms like PC, Mobile for accessing secured pages
  2. User enters user credentials(Username and Password) and gets authenticated.
  3. User Credentails are validated and token is generated
  4. On Subsequent request from APIs tokens are used in header for authorization
  5. Tokens are generated and verified by filters.There would be line of filters on way from request to API
  6. JwtUsernameAndPasswordAuthenticationFilter generates the Token after authentication
  7. JwtTokenVerifierFilter authorizes the user in each and every request by interpreting the token in the request header

JwtTokenVerifierFilter.java

public class JwtTokenVerifierFilter extends OncePerRequestFilter {
    private final SecretKey secretKey;
    private final JwtConfig jwtConfig;

    public JwtTokenVerifierFilter(SecretKey secretKey,
                                  JwtConfig jwtConfig) {
        this.secretKey = secretKey;
        this.jwtConfig = jwtConfig;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain) throws ServletException, IOException {

        String authorizationHeader = httpServletRequest.getHeader(jwtConfig.getAuthorizationHeader());


        if(Strings.isNullOrEmpty(authorizationHeader) || !authorizationHeader.startsWith("Bearer ")){
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        String token = authorizationHeader.replace(jwtConfig.getTokenPrefix(), "");

        try{
            Jws<Claims> claimsJws = Jwts.parser()
                    .setSigningKey(secretKey)
                    .parseClaimsJws(token);

            Claims body     = claimsJws.getBody();
            String username = body.getSubject();


            List<Map<String, String>> authorities = (List<Map<String, String>>)body.get("authorities");
            Set<SimpleGrantedAuthority> simpleGrantedAuth = authorities.stream()
                    .map(m -> new SimpleGrantedAuthority(m.get("authority")))
                    .collect(Collectors.toSet());

            Authentication authentication = new UsernamePasswordAuthenticationToken(username,  null, simpleGrantedAuth);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }catch (JwtException e){
            throw new IllegalStateException(String.format("Token %s cannot be Trusted", token));
        }

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

JwtUsernameAndPasswordAuthenticationFilter.java

public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private final AuthenticationManager authenticationManager;
    private final JwtConfig jwtConfig;
    private final SecretKey secretKey;


    public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authenticationManager,
                                                      JwtConfig jwtConfig,
                                                      SecretKey secretKey) {
        this.authenticationManager = authenticationManager;
        this.jwtConfig = jwtConfig;
        this.secretKey = secretKey;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {

        try {
            UsernameAndPasswordAuthenticationRequest authenticationRequest = new ObjectMapper()
                    .readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class);

            Authentication authentication = new UsernamePasswordAuthenticationToken(
                    authenticationRequest.getUsername(),
                    authenticationRequest.getPassword()
            );

            Authentication authenticate = authenticationManager.authenticate(authentication);
            return authenticate;

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {

        String token = Jwts.builder()
                .setSubject(authResult.getName())
                .claim("authorities", authResult.getAuthorities())
                .setIssuedAt(new Date())
                .setExpiration(java.sql.Date.valueOf(LocalDate.now().plusWeeks(jwtConfig.getTokenExpirationAfterDays())))
                .signWith(secretKey)
                .compact();

        response.addHeader(jwtConfig.getAuthorizationHeader(), jwtConfig.getTokenPrefix() + token);
    }
}

Download Code

  1. What is the difference between FormBased and BasicAuth?
    The Difference between FormAuth and BasicAuth is in BasicAuth UserName and Password would be sent everytime when making a request to the server in the header as base64 encoded character. BasicAuth happens at http Protocol level where as Formbased Auth happens at Framework level.
  2. What is the difference between JKS and PKCS12?
    JKS is the most common if you stay within the Java world. PKCS#12 isn’t Java-specific, it’s particularly convenient to use certificates (with private keys) backed up from a browser or coming from OpenSSL-based tools.JKS, Java Key Store. You can find this file at sun.security.provider.JavaKeyStore. This keystore is Java specific, it usually has an extension of jks. This type of keystore can contain private keys and certificates, but it cannot be used to store secret keys. Since it’s a Java specific keystore, so it cannot be used in other programming languages.PKCS12, this is a standard keystore type which can be used in Java and other languages. You can find this keystore implementation at sun.security.pkcs12.PKCS12KeyStore. It usually has an extension of p12 or pfx. You can store private keys, secret keys and certificates on this type.

  3. How safe is Userid and Password in Basic Auth?
    UserId and Password in BasicAuth would transferred using base64 encoded UserName and Password attached to Header prepending Basic

    Authorization : Basic base64(UserId:Password)
    
  4. How POST, PUT and DELETE works in Basic Auth?
    In Basic Auth with respect to Spring Security CSRF Token would be used incase CSRF is enabled. If CSRF is disabled Authorization in header would be used
    for access. csrf().disable() should be always used incase you are using other than GET method.
  5. What is JSESSIONID?
    JSESSIONID which is generated and Stored as Cookie in Postman helps the spring boot app to recognizane whether the User is Authenticated or not.
  6. How Spring Security handles CSRF in client Side when enabled?
    Spring Security generates JSESSIONID and XSRF-TOKEN. Both were Set in Cookie. On Subsequent request X-XSRF-TOKEN should be sent in Header for Authorization.
  7. What happens when I delete XSRF token in Cookie?
    New Token would be generated based on JSESSIONID in cookie
  8. What happens when I delete XSRF token and in Cookie and try POST, DELETE and PUT over API?
    New JSESSIONID would be generated and placed in cookie. For this X-XSRF-TOKEN should be passed in header.
  9. Where is JSESSION stored?
    JSESSION is session ID which is stored in Inmemory Database in Server and in a Cookie in Client Side.
  10. What is the default Expiration time of JSESSION Cookie?
    30 Minutes
  11. Spring Security Arcitecture?
  12. Encoding vs Encrytion vs Hashing?
    Encoding refers to any transformation of a given input. For example, if we have a function x that reverses a string, function x -> y applied to ABCD produces DCBA.

    x -> y
    

    Encryption is a particular type of encoding where, to obtain the output, you provide both the input value and a key. The key makes it possible for choosing afterward who should be able to reverse the function (obtain the input from the output). The simplest form of representing encryption as a function looks like this:

    (x, k) -> y
    

    where x is the input, k is the key, and y is the result of the encryption. This way, an individual knows the key can use a known function to obtain the input from the output (y, k) -> x. We call this reverse function decryption. If the key used for encryption is the same as the one used for decryption, we usually call it a symmetric key.If we have two different keys for encryption ((x, k1) -> y) and decryption ((y,k2) -> x), then we say that the encryption is done with asymmetric keys. Then (k1,k2) is called a key pair. The key used for encryption, k1, is also referred to as the public key, while k2 is known as the private one. This way, only the owner of the private key can decrypt the data.

    Hashing is a particular type of encoding, except the function is only one way. That is,from an output y of the hashing function, you cannot get back the input x. However,there should always be a way to check if an output y corresponds to an input x, so we can understand the hashing as a pair of functions for encoding and matching. If hashing is x -> y, then we should also have a matching function (x,y) ->boolean.

    (x,y) ->boolean
    

    Sometimes the hashing function could also use a random value added to the input:
    (x, k) -> y. We refer to this value as salt. The salt makes the function stronger, enforcing the difficulty of applying a reverse function to obtain the input from the
    result.

  13. Two ways of Configuring Spring Security?
    Method 1: Adding userservice and passwordEncoder in configure method(Not Recommended)

    @Configuration
    public class ProjectConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("john")
                    .password("12345")
                    .authorities("read")
                    .and()
                    .passwordEncoder(NoOpPasswordEncoder.getInstance());
        }
    }
    

    Method 2: Using Seperate Configuration class for Bean and injecting beans(Recommended Method)
    PasswordConfig.java

    @Configuration
    public class PasswordConfig {
        @Bean
        public PasswordEncoder passwordEncoder()
        {
            return new BCryptPasswordEncoder(10);
        }
    }
    

    ApplicationSecurityConfig.java

    @Configuration
    @EnableWebSecurity
    public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
       @Autowired
       private PasswordEncoder passwordEncoder; 
     
        @Override
        @Bean
        protected UserDetailsService userDetailsService() {
    	   UserDetails mugilUsrBuilder = User.builder()
    			   .username("Mugil")
    			   .password(this.passwordEncoder.encode("password"))
    			   .roles("ADMIN")
    			   .build();
     
    	   return new InMemoryUserDetailsManager(mugilUsrBuilder);
        }
    
     
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception{
            httpSecurity.csrf().disable()
                         .authorizeRequests()
                         //Whitelisting URLS
                        .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                        .antMatchers(HttpMethod.GET,"/api/**").permitAll()
                        .antMatchers(HttpMethod.DELETE,"/api/**").hasRole("ADMIN")
                        .antMatchers(HttpMethod.PUT,"/api/**").hasRole("ADMIN")
                        .antMatchers(HttpMethod.POST,"/api/**").hasRole("ADMIN")
                        .anyRequest()
                        .authenticated()
                        .and()
                        .httpBasic();
        }
    }
    
  14. What are the interface methods available in Spring Security?
    UserDetailsManager
    public interface UserDetailsManager extends UserDetailsService {
    void createUser(UserDetails user);
    void updateUser(UserDetails user);
    void deleteUser(String username);
    void changePassword(String oldPassword, String newPassword);
    boolean userExists(String username);
    }
    UserDetailsService
    public interface UserDetailsService {
    UserDetails loadUserByUsername(String username)
    throws UsernameNotFoundException;
    }
    UserDetails
    public interface UserDetails extends Serializable {
    String getUsername();
    String getPassword();
    Collection<? extends GrantedAuthority>
    ➥getAuthorities();
    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
    }
    AuthenticationProvider
    public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication)
    throws AuthenticationException;
    boolean supports(Class<?> authentication);
    }
    Authentication
    public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean isAuthenticated)
    throws IllegalArgumentException;
    }

ApplicationSecurityConfig.java

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.authorizeRequests()
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
}

Whitelisting some URLs(index, js and CSS files)

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.authorizeRequests()
                    //Whitelisting URLS
                    .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
}

Authentication with password with no encryption

@Override
@Bean
 protected UserDetailsService userDetailsService() {
       UserDetails mugilUsrBuilder = User.builder()
              .username("Mugil")
              .password("{noop}password")
              .roles("ADMIN")
              .build();

      return new InMemoryUserDetailsManager(mugilUsrBuilder);
}

If {noop} is not used in password Spring security would throw an error asking to encode the password with password encoder as below.
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id “null”

Using Password Simple Encoder
PasswordConfig.java

@Configuration
public class PasswordConfig {
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder(10);
    }
}

ApplicationSecurityConfig.java

  1. Inject the passwordEncoder from PasswordConfig class to ApplicationSecurityConfig
  2. Encode the password using instance of injected encoder in ApplicationSecurityConfig
 @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    public ApplicationSecurityConfig(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        UserDetails mugilUsrBuilder = User.builder()
                .username("Mugil")
                .password(this.passwordEncoder.encode("password"))
                .roles("ADMIN")
                .build();

        return new InMemoryUserDetailsManager(mugilUsrBuilder);
    }

Allowing Access to API based on Role – Authorization
ApplicationSecurityConfig.java

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
.
.
 @Override
    @Bean
    //Authentication
    protected UserDetailsService userDetailsService() {
        UserDetails adminUsrBuilder = User.builder()
                .username("admin")
                .password(this.passwordEncoder.encode("password"))
                .roles("ADMIN")
                .build();

        UserDetails regularUsrBuilder = User.builder()
                .username("user")
                .password(this.passwordEncoder.encode("password"))
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(adminUsrBuilder, regularUsrBuilder);  
    }

    @Override
    //Authorization
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.authorizeRequests()
                     //Whitelisting URLS
                    .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                    .antMatchers("/api/**").hasRole("ADMIN")
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
.
.
  1. In the above code we have added two roles – ADMIN and USER
  2. Both were authenticated to access the application.But to access the API the role should be ADMIN
      antMatchers("/api/**").hasRole("ADMIN")
    
  3. If the user with Role USER try to access API then it would end up in 403 – Forbidden Error

Access Allowed

Forbidden Access

Allowing Access based on 2 Different Role
ApplicationSecurityConfig.java

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
.
.

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        UserDetails adminUsrBuilder = User.builder()
                .username("admin")
                .password(this.passwordEncoder.encode("password"))
                .roles("ADMIN")
                .build();

        UserDetails regularUsrBuilder = User.builder()
                .username("user")
                .password(this.passwordEncoder.encode("password"))
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(adminUsrBuilder, regularUsrBuilder);
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.csrf().disable()
                     .authorizeRequests()
                     //Whitelisting URLS
                    .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                    .antMatchers(HttpMethod.GET,"/api/**").permitAll()
                    .antMatchers(HttpMethod.DELETE,"/api/**").hasRole("ADMIN")
                    .antMatchers(HttpMethod.PUT,"/api/**").hasRole("ADMIN")
                    .antMatchers(HttpMethod.POST,"/api/**").hasRole("ADMIN")
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
}
  1. In the above piece of code we have defined two roles ADMIN and USER
  2. Those with USER role can access the API with HTTP Get Method. That means both ADMIN and USER role could access all the API using GET method
    .
    .antMatchers(HttpMethod.GET,"/api/**").permitAll()
    .
    
  3. Those with ADMIN role can access the API with HTTP POST, DELETE and PUT Method which corresponds to Create, Delete and Update as per Open API Specifiaction.

    .
    .antMatchers(HttpMethod.DELETE,"/api/**").hasRole("ADMIN")
    .antMatchers(HttpMethod.PUT,"/api/**").hasRole("ADMIN")
    .antMatchers(HttpMethod.POST,"/api/**").hasRole("ADMIN")
    .
    
  4. The above could be cross checked by changing postman call with HttpMethods and Credentials

StudentsService.java

@RestController
@RequestMapping("/api/v1/students")
public class StudentsService {
    @Autowired
    StudentRepo studentRepo;

    @GetMapping(path="{studentId}")
    public Student getStudentById(@PathVariable("studentId") String studentId){
        return studentRepo.getStudentById(studentId);
    }

    @GetMapping
    public List<Student> getStudentList(){
        return studentRepo.getStudentsList();
    }

    @PutMapping
    public String updateStudent(@RequestBody Student student){
        return studentRepo.updateStudent(student);
    }

    @PostMapping
    public String addStudent(@RequestBody Student student){
        if(studentRepo.addStudents(student))
            return  "Student with Id " + student.getStudentId() + " added successfully";
        else
            return  "Error:Unable to create Student";
    }

    @DeleteMapping(path="{studentId}")
    public String deleteStudent(@PathVariable("studentId") String studentId){
        studentRepo.deleteStudent(studentId);
        return "Student Deleted Successfully";
    }
}

Allowing Access based on 2 Different Authority(or)Permission

  1. In the below code instead of using ROLES to authorize users to do something we use AUTHORITIES to allow user
  2. There are two ways to do this. One is by using hasAuthority in configure(HttpSecurity httpSecurity) method as below
  3. .
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    .
    .antMatchers(HttpMethod.POST,"/api/v1/students/").hasAuthority("WRITE")
    .antMatchers(HttpMethod.DELETE,"/api/v1/students/**").hasAuthority("WRITE")
    .antMatchers(HttpMethod.PUT,"/api/v1/students/").hasAuthority("WRITE")
    .
    
  4. Other is by using @preauthorize annotation to decide the methods
    which could be allowed access to

    .
    .
    @PreAuthorize("hasAuthority('READ')")
    public List getStudentList(){
    .
    
    @PreAuthorize("hasAuthority('WRITE')")
    public String updateStudent(@RequestBody Student student){
    .
    .
    

ApplicationSecurityConfig.java
Using hasAuthority

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    public ApplicationSecurityConfig(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        GrantedAuthority[] arrGrantedAuthAdmin = {new SimpleGrantedAuthority("READ"), new SimpleGrantedAuthority("WRITE")};
        GrantedAuthority[] arrGrantedAuthUser = {new SimpleGrantedAuthority("READ")};

        UserDetails adminUsrBuilder = User.builder()
                .username("admin")
                .password(this.passwordEncoder.encode("password"))
                .authorities("READ", "WRITE")
                .build();

        UserDetails regularUsrBuilder = User.builder()
                .username("user")
                .password(this.passwordEncoder.encode("password"))
                .authorities("READ")
                .build();

        return new InMemoryUserDetailsManager(adminUsrBuilder, regularUsrBuilder);
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.csrf().disable()
                     .authorizeRequests()
                     .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                    .antMatchers(HttpMethod.POST,"/api/v1/students/").hasAuthority("WRITE")
                    .antMatchers(HttpMethod.DELETE,"/api/v1/students/**").hasAuthority("WRITE")
                    .antMatchers(HttpMethod.PUT,"/api/v1/students/").hasAuthority("WRITE")
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
}

ApplicationSecurityConfig.java
Using @PreAuthorize and EnableGlobalMethodSecurity

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

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    public ApplicationSecurityConfig(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        GrantedAuthority[] arrGrantedAuthAdmin = {new SimpleGrantedAuthority("READ"), new SimpleGrantedAuthority("WRITE")};
        GrantedAuthority[] arrGrantedAuthUser = {new SimpleGrantedAuthority("READ")};

        UserDetails adminUsrBuilder = User.builder()
                .username("admin")
                .password(this.passwordEncoder.encode("password"))
                .authorities("READ", "WRITE")
                .build();

        UserDetails regularUsrBuilder = User.builder()
                .username("user")
                .password(this.passwordEncoder.encode("password"))
                .authorities("READ")
                .build();

        return new InMemoryUserDetailsManager(adminUsrBuilder, regularUsrBuilder);
    }

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

StudentsService.java

@RestController
@RequestMapping("/api/v1/students")
public class StudentsService {
    @Autowired
    StudentRepo studentRepo;

    @GetMapping(path="{studentId}")
    public Student getStudentById(@PathVariable("studentId") String studentId){
        return studentRepo.getStudentById(studentId);
    }

    @GetMapping
    @PreAuthorize("hasAuthority('READ')")
    public List<Student> getStudentList(){
        return studentRepo.getStudentsList();
    }

    @PutMapping
    @PreAuthorize("hasAuthority('WRITE')")
    public String updateStudent(@RequestBody Student student){
        return studentRepo.updateStudent(student);
    }

    @PostMapping
    @PreAuthorize("hasAuthority('WRITE')")
    public String addStudent(@RequestBody Student student){
        if(studentRepo.addStudents(student))
            return  "Student with Id " + student.getStudentId() + " added successfully";
        else
            return  "Error:Unable to create Student";
    }

    @DeleteMapping(path="{studentId}")
    @PreAuthorize("hasAuthority('WRITE')")
    public String deleteStudent(@PathVariable("studentId") String studentId){
        studentRepo.deleteStudent(studentId);
        return "Student Deleted Successfully";
    }
}

5 Core Concepts of Spring Security

  1. Authentication and Authorization
    – Authentication – Who are you – Answer by showing ID(Facebook, LinkedIn for ID which uniquely identifies you)
    – Authorization – What you want – State what you want

    Knowledge Based Authentication – Providing details you know about you to prove its you. Downside is details can be stolen.
    Possession Based Authentication – Key Cards for accessing Building Doors, Phone OTP. Authenticates by checking the user posses something which
    realuser should posses.

    Multi Factor Authentication – Enter password and enter OTP(KBA + PBA)

  2. Authorization – Checks whether the person is allowed to do something. For Authorization, Authentication is needed at first place.
  3. Principal
    – Person identified through process of Authentication
    – Person who has logged in. Currently logged in user (or) account.
    – App remembers the principal in context as currently loggedin user.
  4. Granted Authority
    – Authority includes whether the user is allowed to Read, Write, Update and Delete at permission level
  5. Role
    – Group of Authorities assigned together forms a role

Formbased Authentication
pom.xml

.
.
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
.
.

Basic Auth
null

  1. Client Sends a request without username and password and gets 401 Unauthorized as Response
  2. Now Client Sends a request with username and password with Base64 Encoding
  3. Server validates whether user exists in DB
  4. Server replies with 200 Ok if user authentication is successful
  5. Basic ENCODED-BASE64-USERIDPASSWORD is the one sent in header to server from client
  6. In postman basic auth can be done by adding Authorization and base64 encoded user and password to header
    Header : Authorization
    Value : Basic base64('YourOrgName:YourAPIKEY');
    
  7. Base64 encoded text can be got from JS Console in browser as below

    "username:password!" // Here I used basic Auth string format
    
    // Encode the plain string to base64
    btoa("username:password!"); // output: "dXNlcm5hbWU6cGFzc3dvcmQh"
    
    
    // Decode the base64 to plain string
    atob("dXNlcm5hbWU6cGFzc3dvcmQh"); // output: "username:password!"
    
  8. Using Authorization Tab in post man does the same thing of adding base64 encoded UserName and Password to Header prepending Basic

The Difference between FormAuth and BasicAuth is in BasicAuth UserName and Password would be sent everytime when making a request to the server in the header as base64 encoded character.

Form-based authentication
Form-based authentication is not formalized by any RFC.They don’t use the formal HTTP authentication techniques.They use the standard HTML form fields to pass the username and password values to the server.The server validates the credentials and then creates a “session” that is tied to a unique key that is passed between the client and server on each http put and get request.When the user clicks “log off” or the server logs the user off (for example after certain idle time), the server will invalidate the session key, which makes any subsequent communication between the client and server require re-validation

null

Basic Auth
null

Basic Auth with Authorization in Headers as seen in DevTool
null

Creating the below class in Spring Boot project would enable the Basic auth(httpAuth) instead of default formbased auth which we get after adding spring security starter dependency to pom.xml

ApplicationSecurityConfig.java
Using Custom Username and Password for Inmemory Authentication

@Configuration
@EnableWebSecurity
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity.authorizeRequests()
                    .anyRequest()
                    .authenticated()
                    .and()
                    .httpBasic();
    }
}

Why we need Dependency Injection?
Its to overcome monotony behavior. If you are asking for Apple(from Apple.java) you would be served apple object. If you are asking for Mango(from Mango.java) you would be served mango object. Now in case, you are having Fruit and now you are asking Fruit(from Fruit.java) you would be served either apple or mango. Lets take a simple example as below

  1. I have a banking application where I have Account Interface with calculateIntereest Method
  2. I have different account types like SavingsAccount, PersonalLoanAccount and vehicleLoanAccount, HousingLoanAccount
  3. I have a Customer class where he would own a particular account type which wont be known until runtime

Accounts.java

package com.mugil.core;

public interface Accounts 
{
 public Integer calculateInterest();
}

SavingsAccount.java

package com.mugil.core;

public class SavingsAccount implements Accounts 
{
 public Integer calculateInterest() 
 {
  return 8;
 }
}

Customer.java

package com.mugil.core;

public class Customer {
 Accounts accountType;

 public Customer(Accounts paccountType) {
  this.accountType = paccountType;
 }

 public Accounts getAccountType() {
  return accountType;
 }

 public void setAccountType(Accounts accountType) {
  this.accountType = accountType;
 }
}

GetInterestFactory.java

package com.mugil.core;

public class GetInterestFactory {
 Accounts objAccount = null;

 public static void main(String[] args) {
  GetInterestFactory objGetInterest = new GetInterestFactory();
  objGetInterest.showInterestRate();
 }

 public void showInterestRate() {
  String strAccType = "Savings";

  switch (strAccType) {
   case "Savings":
    this.objAccount = new SavingsAccount();
    break;
   case "Vehicle":
    this.objAccount = new VehicleLoanAccount();
    break;
   default:
    this.objAccount = new SavingsAccount();
    break;
  }

  System.out.println("Interest Rate - " + this.objAccount.calculateInterest());
 }
}

Output

Interest Rate - 8
  1. In the above java code we decide the Account Type only when the switch case is executed
  2. Until then the account type is kept as generic value using interface

Now what spring does is the same code can be rewritten to determine the Account Type during runtime in setters and constructors as below

Dependency Injection without Spring using Java Constructor

  1. I have created a new class VehicleLoanAccount.java which has a different interest rate
  2. Now I am going to decide the account type in the Person.java in its constructor as below

VehicleLoanAccount.java

package com.mugil.core;

public class VehicleLoanAccount implements Accounts {
 public Integer calculateInterest() {
  return 11;
 }

}

Dependency Injection without Spring using Java Constructor
GetInterestConsWithoutSpring .java

package com.mugil.core;

public class GetInterestConsWithoutSpring {
 public static void main(String[] args) {
  Customer objPerson = new Customer(new SavingsAccount());
  System.out.println("Interest Rate - " + objPerson.getAccountType().calculateInterest());
 }
}

Output

Interest Rate - 11

Dependency Injection without Spring using Java Setter

  1. Now I am going to decide the account type in the GetInterest.java in its setter method
  2. I would be passing the value of the actual type inside the setter at runtime to decide the account type
  3. The Only thing which I have changed in the addition of new constructor to the Customer Class
  4. Setter method would be passed with specific account type during runtime

Customer.java

package com.mugil.core;

public class Customer {
 Accounts accountType;

 public Customer(Accounts paccountType) {
  this.accountType = paccountType;
 }

 public Customer() {}

 public Accounts getAccountType() {
  return accountType;
 }

 public void setAccountType(Accounts accountType) {
  this.accountType = accountType;
 }
}

Dependency Injection without Spring using Setter Method
GetInterestSettWithoutSpring .java

package com.mugil.core;

public class GetInterestSettWithoutSpring {
 public static void main(String[] args) {
  Customer customer = new Customer();
  customer.setAccountType(new SavingsAccount());
  System.out.println("Interest Rate - " + customer.getAccountType().calculateInterest());
 }
}

Output

Interest Rate - 8

Ways of Bean Injection using Spring
In Spring we can let the container create the bean in two ways

  1. Bean definition in XML
  2. Bean definition using @Component
  3. Bean definition using Java

Bean definition in XML
beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-2.5.xsd">
	<bean id="customer" class="com.mugil.core.Customer" autowire="constructor">				
	</bean>
	<bean  id="savingsType" class="com.mugil.core.SavingsAccount"/>
	<bean  id="vehicleLoanAccount" class="com.mugil.core.VehicleLoanAccount"/>		
</beans>

context:component-scan used for detecting bean
Bean definition using @Component
beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-2.5.xsd">
	<context:annotation-config />
	<context:component-scan base-package="com.mugil.core"></context:component-scan>			
</beans>

@Component marking bean
Customer.java

package com.mugil.core;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@Component
public class Customer 
{

 @Autowired
 @Qualifier("savings")
 Accounts accountType;
.
.
.
}

@Component marking bean
SavingsAccount.java

package com.mugil.core;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Qualifier("savings")
public class SavingsAccount implements Accounts
{	
	public Integer calculateInterest() 
	{
		return 9;
	}
}

Bean definition using Java

Ways of Bean Injection using Spring
Now, what if we do the same thing from XML and using annotations. Spring offers 3 ways by which dependency injection could be done

  1. XML
    1. Constructor
    2. Setter
    3. Autowiring
      1. byType
      2. byName
      3. Constructor
      4. No
  2. Annotation Based
    1. byType
    2. byName
  3. Java Based

Constructor Based – Dependency Injection using XML

  1. In the below code beans are loaded when the application is deployed and the JVM starts
  2. The beans are uniquely identified using their IDS, in our case it is customer
  3. The parameter for constructor is defined inside constructor-arg in XML

Beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
   <bean id="customer" class="com.mugil.core.Customer">
      <constructor-arg>
         <bean id="savingAccount" class="com.mugil.core.SavingsAccount" />
      </constructor-arg>
   </bean>
</beans>

GetInterestConsWithSpring.xml

package com.mugil.core;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class GetInterestConsWithSpring {
 public static void main(String[] args) {
  ApplicationContext context = new ClassPathXmlApplicationContext("SpringBeans.xml");

  Customer customer = (Customer) context.getBean("customer");

  System.out.println("Interest Rate - " + customer.getAccountType().calculateInterest());
 }
}

Output

Interest Rate - 8

Setter Based – Dependency Injection using XML

  1. For setter injection the only things we need to change is XML
  2. XML should be modified to take value by setter rather than constructor as before using property tag

Beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
   <bean id="customer" class="com.mugil.core.Customer">
      <property name="accountType" ref="savingAccount" />
   </bean>
   <bean id="savingAccount" class="com.mugil.core.SavingsAccount" />
</beans>

GetInterestSettWithSpring.xml

package com.mugil.core;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class GetInterestSettWithSpring {
 public static void main(String[] args) {
  ApplicationContext context = new ClassPathXmlApplicationContext(
   "SpringBeans.xml");

  Customer customer = (Customer) context.getBean("customer");

  System.out.println("Interest Rate - " + customer.getAccountType().calculateInterest());
 }
}

Output

Interest Rate - 8

Autowiring byName

  1. In autoWiring byName the Name of the Instance Varable(accountType) and the ID of the bean in XML should be same
  2. If there is no bean matching the name is found it will throw NullPointerException

Customer.java

package com.mugil.core;

public class Customer {
 Accounts accountType;

 public Customer(Accounts paccountType) {
  this.accountType = paccountType;
 }

 public Customer() {}

 public Accounts getAccountType() {
  return accountType;
 }

 public void setAccountType(Accounts accountType) {
  this.accountType = accountType;
 }
}

Beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	<bean id="customer" class="com.mugil.core.Customer" autowire="byName">				
	</bean>
	<bean id="accountType" class="com.mugil.core.SavingsAccount"/>
	<bean id="vehicleLoanAccount" class="com.mugil.core.VehicleLoanAccount"/>
</beans>

Output

Interest Rate - 9

What if Bean of Correct Name is notdefined in XML? Inour case it is account Type
Beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	<bean id="customer" class="com.mugil.core.Customer" autowire="byName">				
	</bean>
	<bean id="savingAccount" class="com.mugil.core.SavingsAccount"/>
	<bean id="vehicleLoanAccount" class="com.mugil.core.VehicleLoanAccount"/>
</beans>

Output

Exception in thread "main" java.lang.NullPointerException
	at com.mugil.core.GetInterestConstAutoWiringXML.main(GetInterestConstAutoWiringXML.java:13)

Autowiring byType

  1. In autoWiring is byType then there should be at least one bean defined for the matching type, in our case it is Accounts
  2. If there is no bean defined of the type then it will throw null pointer exception
  3. If there is more than one matching bean of the same type is found it will throw No unique bean of type

Beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	<bean id="customer" class="com.mugil.core.Customer" autowire="byType">				
	</bean>
	<bean id="savingsType" class="com.mugil.core.SavingsAccount"/>
</beans>

Output

Interest Rate - 9

What if No bean of right Type is defined in XML or More than one bean of same type defined?
Beans.xml
No bean of right Type is defined in XML

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	<bean id="customer" class="com.mugil.core.Customer" autowire="byType">				
	</bean>
</beans>

Output

Exception in thread "main" java.lang.NullPointerException
	at com.mugil.core.GetInterestConstAutoWiringXML.main(GetInterestConstAutoWiringXML.java:13)

Beans.xml
More than one bean of same type defined

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	<bean id="customer" class="com.mugil.core.Customer" autowire="byType">				
	</bean>
	<bean id="savingsType" class="com.mugil.core.SavingsAccount"/>
	<bean id="vehicleLoanAccount" class="com.mugil.core.VehicleLoanAccount"/>
</beans>

Output

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customer' defined in class path resource [SpringBeans.xml]: Unsatisfied dependency expressed through bean property 'accountType': : No unique bean of type [com.mugil.core.Accounts] is defined: expected single matching bean but found 2: [accountType, vehicleLoanAccount]; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [com.mugil.core.Accounts] is defined: expected single matching bean but found 2: [accountType, vehicleLoanAccount]

Autowiring Contructor

  1. In autoWiring using Constructor spring tries to find bean using type.In our case it is Account type
  2. If there is no bean defined of the type then spring will not guess and it will throw null pointer exception
  3. If there is more than one matching bean then spring will not guess and it will throw null pointer exception
  4. If there is more than one constructor spring wont guess the bean and it will throw null pointer exception

Beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	<bean id="customer" class="com.mugil.core.Customer" autowire="constructor">				
	</bean>
	<bean id="savingsType" class="com.mugil.core.SavingsAccount"/>
</beans>

Output

Interest Rate - 9

Beans.xml
Two bean of same type in constructor injection

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	<bean id="customer" class="com.mugil.core.Customer" autowire="constructor">				
	</bean>
	<bean id="savingsType" class="com.mugil.core.SavingsAccount"/>
<bean id="vehicleLoanAccount" class="com.mugil.core.VehicleLoanAccount"/>
</beans>

Beans.xml
No bean of matching type in constructor injection

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	<bean id="customer" class="com.mugil.core.Customer" autowire="constructor">				
	</bean>
</beans>

Output

Exception in thread "main" java.lang.NullPointerException
	at com.mugil.core.GetInterestConstAutoWiringXML.main(GetInterestConstAutoWiringXML.java:13)

Autowiring using autodetect
When the bean is configured to autowire by autodetect spring will attempt to autowire by constructor first.If no suitable constructor to bean is found then spring will attempt to autowire byType.
Beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	<bean id="customer" class="com.mugil.core.Customer" autowire="autodetect">				
	</bean>
</beans>

Annotation Based – byType

  1. In Annotation based autowiring the beans would be injected by xml and would be available in container
  2. context:annotation-config is used to activate annotations in beans already registered in the application context (no matter if they were defined with XML or by package scanning)
  3. Now by using @Autowired tag we inject the bean as dependency where it is required.It is used either over variable in class or over setter or over constructor
  4. The default @Autowired decides the bean based on its type.If more than one bean if found it will throw no unique bean found exception.If no bean found it will throw nullpointer exception
  5. Incase of more than one bean of same type, we can narrow down the selection by using @Qualifier annotation and converting to byName @Autowiring

Beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-2.5.xsd">

	<context:annotation-config />
	<bean id="customer" class="com.mugil.core.Customer" autowire="constructor">				
	</bean>
	<bean id="savingsType" class="com.mugil.core.SavingsAccount"/>	
</beans>

Customer.java

package com.mugil.core;

import org.springframework.beans.factory.annotation.Autowired;

public class Customer 
{
 @Autowired
 private Accounts accountType;

 public Customer(Accounts paccountType) {
  this.accountType = paccountType;
 }

 public Customer() {}

 public Accounts getAccountType() {
  return accountType;
 }
 
 public void setAccountType(Accounts accountType) {
  this.accountType = accountType;
 }
}

Annotation Based – byName

  1. In the below code we have two bean of same type
  2. Using @autowired would try to find bean byType.Since there are two beans it would throw no unique bean found exception
  3. Now we need to use @Qualifier passing the name (or) id of the bean as parameter
  4. Incase only Id of bean is there then same would be taken for name, If Name is there then name of bean would be given preference, incase no bean matches name then Id would be given preference

Beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-2.5.xsd">

	<context:annotation-config />
	<bean id="customer" class="com.mugil.core.Customer" autowire="constructor">				
	</bean>
	<bean name="savings" id="savingsType" class="com.mugil.core.SavingsAccount"/>
	<bean name="vehicle" id="vehicleLoanAccount" class="com.mugil.core.VehicleLoanAccount"/>		
</beans>

Customer.java

package com.mugil.core;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class Customer 
{

 @Autowired
 @Qualifier("savings")
 Accounts accountType;
.
.
.
}

The below code will work by taking bean id into consideration despite the name doesn’t match.
Customer.java

package com.mugil.core;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class Customer 
{

 @Autowired
 @Qualifier("savingsType")
 Accounts accountType;
.
.
.
}

What if there are two bean with the Same Name?
It will not throw exception during compilation but during runtime it will throw bean name already in use exception
Output

Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Bean name 'savings' is already used in this file
Offending resource: class path resource [SpringBeans.xml]

What if there are two bean with the Same ID?

  1. Eclipse will complain for violating ID should be unique and you are violating
  2. If you build the code still builds but when you run will endup with Caused by: org.xml.sax.SAXParseException; lineNumber: 13; columnNumber: 69; cvc-id.2: There are multiple occurrences of ID value ‘savingsType’

Beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-2.5.xsd">

	<context:annotation-config />
	<bean id="customer" class="com.mugil.core.Customer" autowire="constructor">				
	</bean>
	<bean id="savingsType" class="com.mugil.core.SavingsAccount"/>
	<bean id="savingsType" class="com.mugil.core.VehicleLoanAccount"/>		
</beans>

Using @Component Scan to detect and load beans

  1. We can make spring to detect beans on its own by using @component annotation rather then defining in XML with bean tags
  2. Using context:component-scan with base package pointed to beans package will load the bean marked with @component annotation
  3. If there is bean of one type then it would work fine during autowiring, if there is more than one bean of same type then we should uniquely identify the bean using @qualifier annotation
  4. @qualifier annotation should be used both in the place where the bean is referred and also in the place where it is defined.In our case it is Customer.java and SavingsAccount.java

Beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-2.5.xsd">
	<context:annotation-config />
        <context:component-scan base-package="com.mugil.core"></context:component-scan>	
</beans>

Customer.java

package com.mugil.core;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@Component
public class Customer 
{

 @Autowired
 @Qualifier("savings")
 Accounts accountType;
.
.
.
}

SavingsAccount.java

package com.mugil.core;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Qualifier("savings")
public class SavingsAccount implements Accounts
{	
	public Integer calculateInterest() 
	{
		return 9;
	}
}

What is the difference between applicationcontext and webapplicationcontext in Spring?

  1. The WebApplicationContext is an extension of the plain ApplicationContext that has some extra features necessary for web applications. It differs from a normal ApplicationContext in that it is capable of resolving themes (see Using themes), and that it knows which Servlet it is associated with (by having a link to the ServletContext). The WebApplicationContext is bound in the ServletContext, and by using static methods on the RequestContextUtils class you can always look up the WebApplicationContext if you need access to it.
  2. ApplicationContext (Root Application Context) : Every Spring MVC web application has an applicationContext.xml file which is configured as the root of context configuration. Spring loads this file and creates an applicationContext for the entire application. This file is loaded by the ContextLoaderListener which is configured as a context param in web.xml file. And there will be only one applicationContext per web application.

    WebApplicationContext : WebApplicationContext is a web aware application context i.e. it has servlet context information. A single web application can have multiple WebApplicationContext and each Dispatcher servlet (which is the front controller of Spring MVC architecture) is associated with a WebApplicationContext. The webApplicationContext configuration file *-servlet.xml is specific to a DispatcherServlet. And since a web application can have more than one dispatcher servlet configured to serve multiple requests, there can be more than one webApplicationContext file per web application.

  3. Spring allows you to build multilevel application context hierarchies, so the required bean will be fetched from the parent context if it’s not present in the current application context. In web apps as default there are two hierarchy levels, root and servlet
  4. Web Application context extended Application Context which is designed to work with the standard javax.servlet.ServletContext so it’s able to communicate with the container.
    public interface WebApplicationContext extends ApplicationContext {
        ServletContext getServletContext();
    }
    
  5. Beans, instantiated in WebApplicationContext will also be able to use ServletContext if they implement ServletContextAware interface
    package org.springframework.web.context;
    public interface ServletContextAware extends Aware 
    { 
         void setServletContext(ServletContext servletContext);
    }
    

How to define RootApplicationContext?
This two level separation comes out of the box when you use the spring servlet classes: to configure the root application context you should use context-param tag in your web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/root-context.xml
            /WEB-INF/applicationContext-security.xml
    </param-value>
</context-param>

The rootapplicationcontext is created by ContextLoaderListener which is declared in web.xml and servletApplicationContexts using servlet tag as below

<!-- rootapplicationcontext-->
<listener>
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> 

<!-- servletApplicationContext-->
<servlet>
   <servlet-name>myservlet</servlet-name>
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
   <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>app-servlet.xml</param-value>
   </init-param>
</servlet>

In Spring MVC Exceptions can be handled at three levels.

1.Controller Based
We can define exception handler methods in our controller classes. All we need is to annotate these methods with @ExceptionHandler annotation. This annotation takes the Exception class as an argument. So if we have defined one of these for Exception class, then all the exceptions thrown by our request handler method will have handled.
These exception handler methods are just like other request handler methods and we can build error response and respond with a different error page. We can also send a JSON error response, that we will look later on in our example.

@ExceptionHandler({SpringException.class})
.
.
@RequestMapping(value = "/addStudent", method = RequestMethod.POST)
@ExceptionHandler({SpringException.class})
public String addStudent(@ModelAttribute("HelloWeb") Student student, ModelMap model) 
{
 if (student.getName().length() < 5)  
  throw new SpringException("Given name is too short");
 else
  model.addAttribute("name", student.getName());
 
 if (student.getAge() < 10)
  throw new SpringException("Given age is too low");
 else
  model.addAttribute("age", student.getAge());

  model.addAttribute("id", student.getId());
  return "result";
}
.
.
.

If there are multiple exception handler methods defined, then handler method that is closest to the Exception class is used. For example, if we have two handler methods defined for IOException and Exception and our request handler method throws IOException, then the handler method for IOException will get executed.

2.Global Exception Handler-The handler methods in Global Controller Advice is same as Controller based exception handler methods and used when controller class is not able to handle the exception.@ControllerAdvice is a annotation provided by Spring allowing you to write global code that can be applied to a wide range of controllers, varying from all controllers to a chosen package or even a specific annotation. The annotation could be applied at Package Level, Class Level and at Annotation Level.

Package Level Application

@ControllerAdvice("my.chosen.package")
@ControllerAdvice(value = "my.chosen.package")
@ControllerAdvice(basePackages = "my.chosen.package")

Package Level Application – This will apply across all the classes inside the package where MyClass.class is placed.

@ControllerAdvice(basePackageClasses = MyClass.class)

Controller Level
Controller advice can be limited to certain controllers (not methods) by using one of the values of the @ControllerAdvice annotation, e.g.

@ControllerAdvice(assignableTypes = {MyController1.class, MyController2.class})

Controller identified by Annotation
If you want to apply it to controllers with certain annotations. The below snippet would only assist controllers annotated with @RestController (which it covers by default) but will not include @Controller annotated classes.

@ControllerAdvice(annotations = RestController.class)

3.HandlerExceptionResolver – can be implemented by the application to resolve exceptions thrown during processing an HTTP request. The exception can be thrown by one of the application’s handler methods or outside of it but during processing a request. The method, HandlerExceptionResolver#resolveException(), returns an instance of ModelAndView specifying an error page. The Implementations are typically registered as beans in the application context. The application registered HandlerExceptionResolvers will only be invoked if the exception is not already handled by the default HandlerExceptionResolvers. We can, however, change the order of the resolvers so that a given resolver can be invoked first.

@ExceptionHandler vs HandlerExceptionResolver vs @ControllerAdvice
@ExceptionHandler works at the Controller level and it is only active for that particular Controller, not globally for the entire application.

@ControllerAdvice used for global error handling in the Spring MVC application.It also has full control over the body of the response and the status code.

HandlerExceptionResolver-This will resolve any exception thrown by the application. It is used to resolve standard Spring exceptions to their corresponding HTTP Status Codes. It does not have control over the body of the response, means it does not set anything to the body of the Response.It does map the status code on the response but the body is null.

Posted in MVC.

The heart of any Hibernate application is in its configuration. There are two pieces of configuration required in any Hibernate application: one creates the database connections, and the other creates the object-to-table mapping

null

To create a connection to the database, Hibernate must know the details of our database, tables, classes, and other mechanics. This information is ideally provided as an XML file (usually named hibernate.cfg.xml) or as a simple text file with name/value pairs (usually named hibernate.properties).
In XML style. We name this file hibernate.cfg.xml so the framework can load this file automatically.

hibernate.cfg.xml

<hibernate-configuration>  
    <session-factory>
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/hibernate</property>
        <property name="connection.username">root</property>
        <property name="connection.password">pass</property>
         
        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>
 
        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
 
        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>
 
        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>
 
        <!-- Drop and re-create the database schema on startup -->
        <property name="hbm2ddl.auto">update</property>
         
        <!-- Name of the Annotated Entity class -->
        <mapping class="com.mugil.dto.UserDetails"/>
    </session-factory>
</hibernate-configuration>

hibernate.properties

hibernate.connection.driver_class = com.mysql.jdbc.Driver
hibernate.connection.url = jdbc:mysql://localhost:3307/JH
hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

We must let Hibernate know our mapping definition files by including an element mapping property in the previous config file, as shown here:
hibernate.cfg.xml

<hibernate-configuration>
 <session-factory>
 ...
 <mapping resource="table1.hbm.xml" />
 <mapping resource="table2.hbm.xml" />
 <mapping resource="table3.hbm.xml" />
 </session-factory>
</hibernate-configuration>

Once we have the connection configuration ready, the next step is to prepare the table1.hbm.xml file consisting of object-table mapping definitions
XML Mapping

<hibernate-mapping>
 <class name="com.java.latte.table1" table="TABLE1">
 <id name="id" column="ID">
 <generator class="native"/>
 </id>
 <property name="title" column="TITLE"/>
 <property name="director" column="DIRECTOR"/>
 <property name="synopsis" column="SYNOPSIS"/>
 </class>
</hibernate-mapping>
  1. The Hibernate framework reads the hibernate.cfg.xml file to create a SessionFactory, which is thread-safe global factory class for creating Sessions. We should ideally create a single SessionFactory and share it across the application.SessionFactory is defined for one, and only one, database.
  2. SessionFactory is to create Session objects.It is the Session’s job to take care of all database operations such as saving, loading, and retrieving records from relevant tables.Session objects are not thread-safe and therefore should not be shared across different classes.
  3. The Session wraps the underlying JDBC connection or J2EE data source, and it serves as a first-level cache for persistent objects bound to it.
  4. Hibernate specifies how each object state is retrieved and stored in the database via an XML configuration file. Hibernate mappings are loaded at startup and are cached in the SessionFactory. Each mapping specifies a variety of parameters related to the persistence lifecycle of instances of the mapped class

null

More on hibernate Object States here
More on hibernate Object types here