Spring Security JWT Authentication + PostgreSQL – RestAPIs SpringBoot + Spring MVC + Spring JPA

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-feature-image

[no_toc]JSON Web Token defines a compact and self-contained way for securely transmitting information as a JSON object. In the tutorial, we show how to build a SpringBoot Security RestAPIs with JSON Web Token (JWT).

Related posts:
Spring Security – JDBC Authentication – SpringBoot + PostgreSQL + Bootstrap
SQL Tutorial – MySQL Many-to-Many Relationship
Spring JPA Hibernate Many to Many – SpringBoot + PostgreSQL

Technologies

– Spring Boot
– jjwt – 0.9.0
– Spring Security
– Spring JPA
– PostgreSQL

JSON Web Token

JSON Web Token (JWT) defines a compact and self-contained way for securely transmitting information between parties as a JSON object.

Scenarios where JSON Web Tokens are useful:

  • Authorization: the most common scenario for using JWT. Single Sign On is a feature that widely uses JWT
  • Information Exchange: Because JWTs can be signed, JSON Web Tokens are a good way of securely transmitting information between parties.

JSON Web Tokens consist of 3 parts:

  • Header
  • Payload
  • Signature

-> JWT looks like Header-Base64-String.Payload-Base64-String.Signature-Base64-String

Header consists of two parts:

  • token type.
  • hashing algorithm.

-> Example:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload contains the claims. Claims are statements about an entity and additional information.
There are 3 types of claims ->

  • Registered claims -> These are a set of predefined claims: iss (issuer), exp (expiration time), sub (subject)
  • Public claims
  • Private claims

Example ->

{
  "sub": "thomasgkz",
  "iat": 1537603195,
  "exp": 1537689595
}

Signature -> To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.

Example ->


HMACSHA512(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

Combine all together, we get 3 Base64-URL strings separated by dots,

-> Example:


eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0aG9tYXNna3oiLCJpYXQiOjE1Mzc2MDMxOTUsImV4cCI6MTUzNzY4OTU5NX0.m2YMjTYmOnfR7nnVNxqCzWbQ2FhKRe1eiizxnC2TF4eAoEzKlwo7PheVkKcxj08ST3vB-ZOIhiORvYVfSgzcog

When accessing a protected route or resource, the user agent should send the JWT, typically in the Authorization header using the Bearer schema.

-> Example:


Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0aG9tYXNna3oiLCJpYXQiOjE1Mzc2MDMxOTUsImV4cCI6MTUzNzY4OTU5NX0.m2YMjTYmOnfR7nnVNxqCzWbQ2FhKRe1eiizxnC2TF4eAoEzKlwo7PheVkKcxj08ST3vB-ZOIhiORvYVfSgzcog

Overview

Project Structure

We create a SpringBoot project as below:

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-project-structure

model package defines 2 entities User & Role that have many-to-many relationship:

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-uml-modeling

repository package contains interfaces that use Hibernate JPA to store/retrieve data from PostgreSQL database.
controller package defines RestAPIs for user signup/signin and testing protected resources that is secured with JWT.
message package defines payload data transferred from user agents (Browser/RestClient…) to RestAPIs and message back.
security package is the main part of the project that implements JWT security.

Goal

– We expose 2 RestAPIs to signup and signin:

  • /api/auth/signup -> sign up

    spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-signup-ADAM

  • /api/auth/signin -> sign in

    spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-ADAM-sign-in

– We expose 3 RestAPIs to test protected resources:


@GetMapping("/api/test/user")
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public String userAccess() {
	return ">>> User Contents!";
}

@GetMapping("/api/test/pm")
@PreAuthorize("hasRole('PM') or hasRole('ADMIN')")
public String projectManagementAccess() {
	return ">>> Board Management Project";
}

@GetMapping("/api/test/admin")
@PreAuthorize("hasRole('ADMIN')")
public String adminAccess() {
	return ">>> Admin Contents";
}
  • Access Successfully ->

    spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-ADAM-access-USER-API-successfully

  • Unauthorized ->

    spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-ADAM-can-NOT-access-PM-API

Practice

Create SpringBoot project

We create a SpringBoot project with below dependencies:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</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-web</artifactId>
</dependency>
<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt</artifactId>
	<version>0.9.0</version>
</dependency>

Create Models

User.java model contains 5 attributes:

  • id
  • name
  • username
  • email
  • password

package com.ozenero.jwtauthentication.model;

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

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

import org.hibernate.annotations.NaturalId;

@Entity
@Table(name = "users", uniqueConstraints = {
        @UniqueConstraint(columnNames = {
            "username"
        }),
        @UniqueConstraint(columnNames = {
            "email"
        })
})
public class User{
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    @Size(min=3, max = 50)
    private String name;

    @NotBlank
    @Size(min=3, max = 50)
    private String username;

    @NaturalId
    @NotBlank
    @Size(max = 50)
    @Email
    private String email;

    @NotBlank
    @Size(min=6, max = 100)
    private String password;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "user_roles", 
    	joinColumns = @JoinColumn(name = "user_id"), 
    	inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set roles = new HashSet<>();

    public User() {}

    public User(String name, String username, String email, String password) {
        this.name = name;
        this.username = username;
        this.email = email;
        this.password = password;
    }

    public Long getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

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

    public Set getRoles() {
        return roles;
    }

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

Role.java model contains 2 attributes:

  • id
  • rolename

package com.ozenero.jwtauthentication.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.NaturalId;

@Entity
@Table(name = "roles")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.STRING)
    @NaturalId
    @Column(length = 60)
    private RoleName name;

    public Role() {}

    public Role(RoleName name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

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

    public RoleName getName() {
        return name;
    }

    public void setName(RoleName name) {
        this.name = name;
    }
}

RoleName.java ->


package com.ozenero.jwtauthentication.model;

public enum  RoleName {
    ROLE_USER,
    ROLE_PM,
    ROLE_ADMIN
}

Implement Repository

UserRepository ->


package com.ozenero.jwtauthentication.repository;

import java.util.Optional;

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

import com.ozenero.jwtauthentication.model.User;

@Repository
public interface UserRepository extends JpaRepository {
    Optional findByUsername(String username);
    Boolean existsByUsername(String username);
    Boolean existsByEmail(String email);
}

RoleRepository.java ->


package com.ozenero.jwtauthentication.repository;

import java.util.Optional;

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

import com.ozenero.jwtauthentication.model.Role;
import com.ozenero.jwtauthentication.model.RoleName;

@Repository
public interface RoleRepository extends JpaRepository {
    Optional findByName(RoleName roleName);
}
Implement JWT Security

– Configure WebSecurityConfig.java:


package com.ozenero.jwtauthentication.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.ozenero.jwtauthentication.security.jwt.JwtAuthEntryPoint;
import com.ozenero.jwtauthentication.security.jwt.JwtAuthTokenFilter;
import com.ozenero.jwtauthentication.security.services.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
		prePostEnabled = true
)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Autowired
    private JwtAuthEntryPoint unauthorizedHandler;

    @Bean
    public JwtAuthTokenFilter authenticationJwtTokenFilter() {
        return new JwtAuthTokenFilter();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable().
                authorizeRequests()
                .antMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

@EnableWebSecurity is used to enable web security in a project.
@EnableGlobalMethodSecurity(prePostEnabled = true) is used to enable Spring Security global method security.

-> Example:


@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public String userAccess() {

@GetMapping("/api/test/pm")
@PreAuthorize("hasRole('PM') or hasRole('ADMIN')")

@GetMapping("/api/test/admin")
@PreAuthorize("hasRole('ADMIN')")

PasswordEncoder uses the BCrypt strong hashing function.

UserDetails Service

UserDetailsServiceImpl implements UserDetailsService that will override loadUserByUsername method.
loadUserByUsername method will find a record from users database tables to build a UserDetails object for authentication.


package com.ozenero.jwtauthentication.security.services;

import com.ozenero.jwtauthentication.model.User;
import com.ozenero.jwtauthentication.repository.UserRepository;
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 org.springframework.transaction.annotation.Transactional;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    UserRepository userRepository;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
    	
        User user = userRepository.findByUsername(username)
                	.orElseThrow(() -> 
                        new UsernameNotFoundException("User Not Found with -> username or email : " + username)
        );

        return UserPrinciple.build(user);
    }
}

-> UserPrinciple will implement UserDetails.
UserPrinciple is not used directly by Spring Security for security purposes.
It simply stores user information which is later encapsulated into Authentication objects. This allows non-security related user information (such as email addresses, telephone numbers etc) to be stored.


package com.ozenero.jwtauthentication.security.services;

import com.ozenero.jwtauthentication.model.User;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class UserPrinciple implements UserDetails {
	private static final long serialVersionUID = 1L;

	private Long id;

    private String name;

    private String username;

    private String email;

    @JsonIgnore
    private String password;

    private Collection authorities;

    public UserPrinciple(Long id, String name, 
			    		String username, String email, String password, 
			    		Collection authorities) {
        this.id = id;
        this.name = name;
        this.username = username;
        this.email = email;
        this.password = password;
        this.authorities = authorities;
    }

    public static UserPrinciple build(User user) {
        List authorities = user.getRoles().stream().map(role ->
                new SimpleGrantedAuthority(role.getName().name())
        ).collect(Collectors.toList());

        return new UserPrinciple(
                user.getId(),
                user.getName(),
                user.getUsername(),
                user.getEmail(),
                user.getPassword(),
                authorities
        );
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public Collection getAuthorities() {
        return authorities;
    }

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

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

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        
        UserPrinciple user = (UserPrinciple) o;
        return Objects.equals(id, user.id);
    }
}

JWT Authentication Classes

JwtAuthTokenFilter extends OncePerRequestFilter.

org.springframework.web.filter.OncePerRequestFilter
-> Executes once per request. This is a filter base class that is used to guarantee a single execution per request dispatch. It provides a doFilterInternal method with HttpServletRequest and HttpServletResponse arguments.

In JwtAuthTokenFilter class, the doFilterInternal method will do:

  • get JWT token from header
  • validate JWT
  • parse username from validated JWT
  • load data from users table, then build an authentication object
  • set the authentication object to Security Context

package com.ozenero.jwtauthentication.security.jwt;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import com.ozenero.jwtauthentication.security.services.UserDetailsServiceImpl;

public class JwtAuthTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JwtProvider tokenProvider;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    private static final Logger logger = LoggerFactory.getLogger(JwtAuthTokenFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
    								HttpServletResponse response, 
    								FilterChain filterChain) 
    										throws ServletException, IOException {
        try {
        	
            String jwt = getJwt(request);
            if (jwt!=null && tokenProvider.validateJwtToken(jwt)) {
                String username = tokenProvider.getUserNameFromJwtToken(jwt);

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication 
                		= new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {
            logger.error("Can NOT set user authentication -> Message: {}", e);
        }

        filterChain.doFilter(request, response);
    }

    private String getJwt(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        	
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
        	return authHeader.replace("Bearer ","");
        }

        return null;
    }
}

JwtAuthEntryPoint is used to handle Error exception when having unauthorized requests.


package com.ozenero.jwtauthentication.security.jwt;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component
public class JwtAuthEntryPoint implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class);
    
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException e) 
                        		 throws IOException, ServletException {
    	
        logger.error("Unauthorized error. Message - {}", e.getMessage());
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error -> Unauthorized");
    }
}

JwtProvider is an util class -> it implements useful functions:

  • generate a JWT token
  • valiate a JWT token
  • parse username from JWT token

package com.ozenero.jwtauthentication.security.jwt;

import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import com.ozenero.jwtauthentication.security.services.UserPrinciple;

import java.util.Date;

@Component
public class JwtProvider {

    private static final Logger logger = LoggerFactory.getLogger(JwtProvider.class);

    @Value("${ozenero.app.jwtSecret}")
    private String jwtSecret;

    @Value("${ozenero.app.jwtExpiration}")
    private int jwtExpiration;

    public String generateJwtToken(Authentication authentication) {

        UserPrinciple userPrincipal = (UserPrinciple) authentication.getPrincipal();

        return Jwts.builder()
		                .setSubject((userPrincipal.getUsername()))
		                .setIssuedAt(new Date())
		                .setExpiration(new Date((new Date()).getTime() + jwtExpiration))
		                .signWith(SignatureAlgorithm.HS512, jwtSecret)
		                .compact();
    }

    public String getUserNameFromJwtToken(String token) {
        return Jwts.parser()
			                .setSigningKey(jwtSecret)
			                .parseClaimsJws(token)
			                .getBody().getSubject();
    }

    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            logger.error("Invalid JWT signature -> Message: {} ", e);
        } catch (MalformedJwtException e) {
            logger.error("Invalid JWT token -> Message: {}", e);
        } catch (ExpiredJwtException e) {
            logger.error("Expired JWT token -> Message: {}", e);
        } catch (UnsupportedJwtException e) {
            logger.error("Unsupported JWT token -> Message: {}", e);
        } catch (IllegalArgumentException e) {
            logger.error("JWT claims string is empty -> Message: {}", e);
        }
        
        return false;
    }
}

Implement RestControllers

Create Payload Message

LoginForm.java contains username & password ->


package com.ozenero.jwtauthentication.message.request;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

public class LoginForm {
    @NotBlank
    @Size(min=3, max = 60)
    private String username;

    @NotBlank
    @Size(min = 6, max = 40)
    private String password;

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

SignUpForm.java contains:

  • name
  • username
  • email
  • role
  • password

package com.ozenero.jwtauthentication.message.request;

import java.util.Set;

import javax.validation.constraints.*;

public class SignUpForm {
    @NotBlank
    @Size(min = 3, max = 50)
    private String name;

    @NotBlank
    @Size(min = 3, max = 50)
    private String username;

    @NotBlank
    @Size(max = 60)
    @Email
    private String email;
    
    private Set role;
    
    @NotBlank
    @Size(min = 6, max = 40)
    private String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUsername() {
        return username;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    
    public Set getRole() {
    	return this.role;
    }
    
    public void setRole(Set role) {
    	this.role = role;
    }
}

JwtResponse.java is returned by SpringBoot server after successful authentication, it contains 2 parts:

  • JWT Token
  • Schema Type of Token

package com.ozenero.jwtauthentication.message.response;

public class JwtResponse {
    private String token;
    private String type = "Bearer";

    public JwtResponse(String accessToken) {
        this.token = accessToken;
    }

    public String getAccessToken() {
        return token;
    }

    public void setAccessToken(String accessToken) {
        this.token = accessToken;
    }

    public String getTokenType() {
        return type;
    }

    public void setTokenType(String tokenType) {
        this.type = tokenType;
    }
}

RestAPIs Controller

AuthRestAPIs.java defines 2 APIs:

  • /api/auth/signup: sign up
    -> check username/email is already in use.
    -> create User object
    -> store to database
  • /api/auth/signin: sign in
    -> attempt to authenticate with AuthenticationManager bean.
    -> add authentication object to SecurityContextHolder
    -> Generate JWT token, then return JWT to client

package com.ozenero.jwtauthentication.controller;

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

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.ozenero.jwtauthentication.message.request.LoginForm;
import com.ozenero.jwtauthentication.message.request.SignUpForm;
import com.ozenero.jwtauthentication.message.response.JwtResponse;
import com.ozenero.jwtauthentication.model.Role;
import com.ozenero.jwtauthentication.model.RoleName;
import com.ozenero.jwtauthentication.model.User;
import com.ozenero.jwtauthentication.repository.RoleRepository;
import com.ozenero.jwtauthentication.repository.UserRepository;
import com.ozenero.jwtauthentication.security.jwt.JwtProvider;

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/auth")
public class AuthRestAPIs {

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    UserRepository userRepository;

    @Autowired
    RoleRepository roleRepository;

    @Autowired
    PasswordEncoder encoder;

    @Autowired
    JwtProvider jwtProvider;

    @PostMapping("/signin")
    public ResponseEntity authenticateUser(@Valid @RequestBody LoginForm loginRequest) {

        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginRequest.getUsername(),
                        loginRequest.getPassword()
                )
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);

        String jwt = jwtProvider.generateJwtToken(authentication);
        return ResponseEntity.ok(new JwtResponse(jwt));
    }

    @PostMapping("/signup")
    public ResponseEntity registerUser(@Valid @RequestBody SignUpForm signUpRequest) {
        if(userRepository.existsByUsername(signUpRequest.getUsername())) {
            return new ResponseEntity("Fail -> Username is already taken!",
                    HttpStatus.BAD_REQUEST);
        }

        if(userRepository.existsByEmail(signUpRequest.getEmail())) {
            return new ResponseEntity("Fail -> Email is already in use!",
                    HttpStatus.BAD_REQUEST);
        }

        // Creating user's account
        User user = new User(signUpRequest.getName(), signUpRequest.getUsername(),
                signUpRequest.getEmail(), encoder.encode(signUpRequest.getPassword()));

        Set strRoles = signUpRequest.getRole();
        Set roles = new HashSet<>();

        strRoles.forEach(role -> {
        	switch(role) {
	    		case "admin":
	    			Role adminRole = roleRepository.findByName(RoleName.ROLE_ADMIN)
	                .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
	    			roles.add(adminRole);
	    			
	    			break;
	    		case "pm":
	            	Role pmRole = roleRepository.findByName(RoleName.ROLE_PM)
	                .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
	            	roles.add(pmRole);
	            	
	    			break;
	    		default:
	        		Role userRole = roleRepository.findByName(RoleName.ROLE_USER)
	                .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
	        		roles.add(userRole);        			
        	}
        });
        
        user.setRoles(roles);
        userRepository.save(user);

        return ResponseEntity.ok().body("User registered successfully!");
    }
}

TestRestAPIs define 3 RestAPIs:

  • /api/test/user -> access by users has USER_ROLE or ADMIN_ROLE
  • /api/test/pm -> access by users has USER_PM or ADMIN_ROLE
  • /api/test/admin -> access by users has ADMIN_ROLE

package com.ozenero.jwtauthentication.controller;

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

@RestController
public class TestRestAPIs {
	
	@GetMapping("/api/test/user")
	@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
	public String userAccess() {
		return ">>> User Contents!";
	}
	
	@GetMapping("/api/test/pm")
	@PreAuthorize("hasRole('PM') or hasRole('ADMIN')")
	public String projectManagementAccess() {
		return ">>> Board Management Project";
	}
	
	@GetMapping("/api/test/admin")
	@PreAuthorize("hasRole('ADMIN')")
	public String adminAccess() {
		return ">>> Admin Contents";
	}
}

Application Properties

application.properties file ->


spring.datasource.url=jdbc:postgresql://localhost/testdb
spring.datasource.username=postgres
spring.datasource.password=123
spring.jpa.generate-ddl=true

# App Properties
ozenero.app.jwtSecret=jwtGrokonezSecretKey
ozenero.app.jwtExpiration=86400

Run & Check Results

Start SpringBoot

– Start Springboot server by commandline mvn spring-boot:run

– Check database tables ->

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-list-all-tables

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-schema-tables

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-select-row-tables

– Insert data to roles table ->


INSERT INTO roles(name) VALUES('ROLE_USER');
INSERT INTO roles(name) VALUES('ROLE_PM');
INSERT INTO roles(name) VALUES('ROLE_ADMIN');

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-insert-record-to-roles-tables

SignUp

Sign-Up 3 users:

  • Jack has ROLE_USER role
  • Adam has ROLE_PM & ROLE_USER roles
  • Thomas has ROLE_ADMIN role

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-signup-ADAM

– Check database’s tables ->

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-record-table-after-signup

SignIn and Access Protected Resources

Adam can access api/test/user url, can NOT access others.

-> Access Protected Resources:

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-ADAM-access-USER-API-successfully

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-ADAM-can-NOT-access-PM-API

Jack can access api/test/user and api/test/pm url.
Can NOT access /api/test/admin url.

-> Sign In:

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-JACK-sign-in

-> Access Protected Resources:

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-JACK-can-access-USER-APIs

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-JACK-can-access-PM-API-successfully

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-JACK-can-NOT-access-ADMIN-API-successfully

Thomas can access all URLs.

-> Sign In:

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-THOMAS-sign-in

-> Access Protected Resource:

spring-security-jwt-json-web-token-authentication-springboot-spring-jpa-postgresql-THOMAS-can-access-ADMIN-API-successfully

SourceCode

SpringBootJwtAuthentication

810 thoughts on “Spring Security JWT Authentication + PostgreSQL – RestAPIs SpringBoot + Spring MVC + Spring JPA”

  1. Thanks so much. I lost a lot of time to find document about that. So, Iam really happy when I see your post. Once again, hope you have more post useful.

  2. Thanks. Very helpful.
    One question: how can I get username or userid in the TestRestAPIs controller? I need to know which user access the contents?

    1. Thanks. I got the solution for my above question.

      Using following:
      Authentication authInfo = SecurityContextHolder.getContext().getAuthentication();
      UserPrinciple userPrincipal = (UserPrinciple)authInfo.getPrincipal();
      long userId = userPrincipal.getId();

  3. Alguien le tira error al ejecutar el codigo, me tira un error con respecto al rol que se le esta signando 😮 ?

    Hibernate: select role0_.id as id1_0_, role0_.name as name2_0_ from role role0_ where role0_.name=?
    2019-04-23 04:55:44.620 ERROR 15954 — [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: Fail! -> Cause: User Role not find.] with root cause

    java.lang.RuntimeException: Fail! -> Cause: User Role not find.
    at cl.sportapp.evaluation.controller.configController.AuthRestAPIs.lambda$null$0(AuthRestAPIs.java:107) ~[classes/:na]
    at java.util.Optional.orElseThrow(Optional.java:290) ~[na:1.8.0_181]

    Atento a sus comentarios.

    Saludos.

  4. Seeing as its open season on people commenting on my blogs with their damn back links i thought id have a go so blah blah blah insert keywords for my crappy forum here blah blah yet more phrases keywords for crap back links. Annoying aint it!!!

  5. There are a couple of fascinating points on time in this post but I don’t know if all of them center to heart. There may be some validity but I’m going to take hold opinion until I investigate it further. Excellent post , thanks so we want a lot more! Put into FeedBurner as well

  6. This is really exciting, You’re an awfully skilled article writer. I have signed up with your feed additionally look forward to enjoying your personal fabulous write-ups. What’s more, We’ve shared your websites throughout our myspace.

  7. One thing I’ve noticed is that there are plenty of fallacies regarding the banking companies intentions if talking about foreclosures. One fantasy in particular is the fact that the bank needs to have your house. The bank wants your money, not your house. They want the money they gave you having interest. Preventing the bank will undoubtedly draw some sort of foreclosed final result. Thanks for your post.

  8. Aw, this was a really nice post. Finding
    the time and actual effort to generate a superb article… but what can I
    say… I hesitate a lot and never seem to get nearly anything done.

  9. Usually I don’t learn post on blogs, but I wish to say
    that this write-up very compelled me to take a
    look at and do it! Your writing taste has been surprised me.
    Thank you, quite nice post.

  10. Definitely believe that which you said. Your favorite justification seemed to
    be on the internet the easiest thing to be aware of.
    I say to you, I definitely get annoyed while people consider worries that they plainly don’t know about.
    You managed to hit the nail upon the top and
    defined out the whole thing without having side effect , people can take a signal.
    Will likely be back to get more. Thanks

  11. Hey there! I know this is somewhat off topic but I was wondering which blog
    platform are you using for this site? I’m getting sick and tired of
    Wordpress because I’ve had issues with hackers and I’m looking
    at alternatives for another platform. I would be awesome if you
    could point me in the direction of a good platform.

  12. Having read this I thought it was really informative.

    I appreciate you finding the time and energy to put this content together.
    I once again find myself personally spending way too much time both reading and
    posting comments. But so what, it was still worthwhile!

  13. Fantastic goods from you, man. I’ve understand your stuff previous to and you are just too great.
    I really like what you’ve acquired here, certainly like what you’re stating and the way in which you
    say it. You make it enjoyable and you still take care of to keep it smart.
    I can not wait to read far more from you.
    This is really a terrific website.

  14. What’s Going down i’m new to this, I stumbled upon this I
    have found It positively useful and it has helped me out loads.
    I’m hoping to contribute & help other users like its helped me.
    Good job.

  15. I’m really impressed along with your writing
    talents as neatly as with the layout for your weblog. Is this a paid theme or did you modify it yourself?

    Either way stay up the excellent high quality writing, it’s rare
    to peer a nice weblog like this one nowadays..

  16. Having read this I thought it was extremely enlightening.
    I appreciate you finding the time and energy to put this article together.
    I once again find myself spending a significant amount of time both reading and leaving comments.
    But so what, it was still worth it!

  17. Hey There. I found your blog using msn. This is a really well written article.
    I’ll make sure to bookmark it and come back to read
    more of your useful info. Thanks for the post. I will certainly comeback.

  18. When I originally commented I clicked the “Notify me when new comments are added” checkbox and now each time
    a comment is added I get several e-mails with the same comment.
    Is there any way you can remove me from that service?
    Thanks a lot!

  19. I’m not positive where you are getting your info, but
    good topic. I needs to spend some time finding out much more or understanding more.
    Thanks for fantastic info I was in search of this information for
    my mission.

  20. I’ve been browsing online more than three hours today, yet I never found any interesting article like yours.
    It’s pretty worth enough for me. In my opinion, if all site owners and bloggers made good content as you did,
    the net will be a lot more useful than ever before.

  21. For most recent information you have to pay a quick visit internet and on internet I found this web site as a best
    site for most up-to-date updates.

  22. Thank you for the good writeup. It actually used to be a
    leisure account it. Glance advanced to far added agreeable from you!

    By the way, how could we keep up a correspondence?

  23. Hello there! I could have sworn I’ve been to this blog before but after
    checking through some of the post I realized it’s new to me.
    Anyways, I’m definitely delighted I found it and I’ll be bookmarking and checking back often!

  24. When I originally commented I clicked the “Notify me when new comments are added” checkbox and now each time a comment is added I get three e-mails with the same comment.
    Is there any way you can remove people from that service?
    Thanks a lot!

  25. Hello there, just became aware of your blog through Google,
    and found that it’s truly informative. I’m going to watch out for brussels.

    I will be grateful if you continue this in future.
    Numerous people will be benefited from your writing. Cheers!

  26. I simply could not depart your site prior to suggesting
    that I extremely enjoyed the standard information a person provide in your visitors?
    Is going to be again often in order to investigate cross-check new
    posts

  27. Fantastic post however I was wanting to know if you could write a litte more on this topic?
    I’d be very grateful if you could elaborate a little bit more.
    Thanks!

  28. Hey I am so grateful I found your blog, I really found you
    by mistake, while I was researching on Digg for something else, Nonetheless I
    am here now and would just like to say cheers for a remarkable post and a all round
    enjoyable blog (I also love the theme/design), I don’t have time to go
    through it all at the minute but I have saved it and also added
    your RSS feeds, so when I have time I will be back to read a
    great deal more, Please do keep up the excellent work.

  29. This design is spectacular! You definitely know how to
    keep a reader amused. Between your wit and your videos, I
    was almost moved to start my own blog (well, almost…HaHa!) Fantastic
    job. I really loved what you had to say,
    and more than that, how you presented it.
    Too cool!

  30. That is very attention-grabbing, You’re an excessively professional blogger.
    I have joined your rss feed and look forward to seeking extra of your excellent post.

    Also, I’ve shared your web site in my social networks

  31. Oh my goodness! Awesome article dude! Many thanks, However I am going
    through troubles with your RSS. I don’t know why I can’t subscribe to it.
    Is there anybody else having the same RSS problems?
    Anyone that knows the answer can you kindly respond?
    Thanx!!

  32. Hello would you mind letting me know which web host you’re
    working with? I’ve loaded your blog in 3 completely different web browsers and I must say this blog loads a lot quicker then most.
    Can you suggest a good internet hosting provider at a fair price?
    Kudos, I appreciate it!

  33. Unquestionably believe that which you said. Your favorite reason appeared to be on the web
    the simplest thing to be aware of. I say to you,
    I certainly get annoyed while people consider worries that they just do
    not know about. You managed to hit the nail upon the top and
    also defined out the whole thing without having side-effects , people could
    take a signal. Will probably be back to get more.
    Thanks

  34. I know this if off topic but I’m looking into starting my own blog and was wondering what all
    is needed to get set up? I’m assuming having a blog like yours would cost a pretty penny?
    I’m not very web smart so I’m not 100% positive.

    Any recommendations or advice would be greatly appreciated.
    Thank you

  35. Do you mind if I quote a few of your posts as long as I provide credit and sources back to your weblog?
    My blog is in the exact same area of interest as yours and my
    visitors would genuinely benefit from some of the information you present here.
    Please let me know if this okay with you. Appreciate it!

  36. Hi! I could have sworn I’ve visited this website before but
    after going through a few of the articles I realized it’s new to me.

    Anyways, I’m definitely pleased I discovered it and I’ll
    be bookmarking it and checking back frequently!

  37. Do you have a spam issue on this blog; I also am a blogger,
    and I was wanting to know your situation; we have created some nice methods and we
    are looking to swap methods with other folks, why not
    shoot me an e-mail if interested.

  38. Woah! I’m really enjoying the template/theme of this site.

    It’s simple, yet effective. A lot of times it’s hard to get that “perfect balance” between superb
    usability and appearance. I must say that you’ve done a awesome job with this.

    Additionally, the blog loads extremely quick for me on Safari.
    Superb Blog!

  39. Very good blog! Do you have any helpful hints for aspiring writers?

    I’m planning to start my own blog soon but I’m a
    little lost on everything. Would you advise starting with a free platform like
    Wordpress or go for a paid option? There are so many options out there that I’m completely
    overwhelmed .. Any suggestions? Thanks a lot!

  40. Hmm is anyone else encountering problems with the pictures on this blog loading?
    I’m trying to find out if its a problem on my
    end or if it’s the blog. Any suggestions would be greatly appreciated.

  41. Its like you read my mind! You seem to understand
    a lot about this, such as you wrote the e-book in it or something.
    I think that you just could do with some p.c. to power the message house
    a bit, however other than that, that is great blog. An excellent read.
    I will certainly be back.

  42. We’re a group of volunteers and starting a new scheme in our community.
    Your website offered us with valuable information to work on. You’ve done a
    formidable job and our whole community will be thankful to you.

  43. Howdy would you mind sharing which blog platform you’re using?
    I’m looking to start my own blog in the near future but I’m
    having a difficult time making a decision between BlogEngine/Wordpress/B2evolution and Drupal.
    The reason I ask is because your design seems different then most blogs and I’m looking for something completely
    unique. P.S Apologies for being off-topic but I had to ask!

  44. I’m truly enjoying the design and layout of your site.
    It’s a very easy on the eyes which makes it much more enjoyable for me to come here and visit more often. Did you hire out a designer to create your theme?
    Fantastic work!

  45. Wow, wonderful blog format! How long have you been running a blog for?
    you make blogging glance easy. The overall look of your web site
    is magnificent, let alone the content!

  46. Hi there! This is my 1st comment here so I just wanted to give a quick
    shout out and tell you I genuinely enjoy reading through your articles.
    Can you suggest any other blogs/websites/forums that deal with the same subjects?
    Many thanks!

  47. Hello! I know this is somewhat off topic but I was wondering which blog platform are you using for this website?
    I’m getting tired of WordPress because I’ve had problems with
    hackers and I’m looking at alternatives for another platform.
    I would be awesome if you could point me in the direction of
    a good platform.

  48. Hello there! I know this is kinda off topic but I was wondering which blog
    platform are you using for this site? I’m getting sick and tired
    of WordPress because I’ve had problems with hackers and I’m looking at alternatives for another platform.
    I would be great if you could point me in the
    direction of a good platform.

  49. Do you mind if I quote a couple of your articles as long as
    I provide credit and sources back to your site?
    My blog site is in the exact same niche as yours and my users would certainly benefit from a lot of the information you present here.

    Please let me know if this alright with you. Cheers!

  50. I like the valuable info you provide in your articles.

    I’ll bookmark your weblog and check again here frequently.
    I’m quite certain I’ll learn many new stuff right here!
    Best of luck for the next!

  51. Hey there I am so grateful I found your blog page, I
    really found you by mistake, while I was browsing on Digg for
    something else, Nonetheless I am here now and would just like to
    say many thanks for a marvelous post and a all
    round enjoyable blog (I also love the theme/design), I
    don’t have time to browse it all at the minute but I have
    bookmarked it and also included your RSS feeds, so
    when I have time I will be back to read a lot
    more, Please do keep up the awesome work.

  52. Pretty great post. I simply stumbled upon your blog and wanted to mention that I have really loved surfing around your weblog posts.

    In any case I’ll be subscribing on your feed and I am hoping you write once more soon!

  53. You are so awesome! I don’t believe I’ve read
    through something like that before. So nice to discover somebody with a few genuine thoughts on this subject.

    Really.. thanks for starting this up. This web site is one thing that’s needed on the web, someone with some originality!

  54. I’m extremely impressed along with your writing talents and also with
    the format for your blog. Is this a paid subject matter or did
    you modify it yourself? Anyway keep up the excellent quality writing,
    it is uncommon to see a great blog like this one today..

  55. I’ve learn a few excellent stuff here. Definitely value bookmarking for revisiting.
    I wonder how a lot attempt you place to create one of these great informative site.

  56. Excellent blog here! Also your website loads up very fast!
    What host are you using? Can I get your affiliate link to your host?
    I wish my site loaded up as quickly as yours lol

  57. Unquestionably believe that that you said. Your
    favorite justification appeared to be at the web the easiest
    factor to consider of. I say to you, I definitely get annoyed while other
    people think about concerns that they just don’t recognise about.
    You controlled to hit the nail upon the highest and also outlined out the entire thing without having side-effects , folks could take a
    signal. Will likely be again to get more. Thank you

  58. Valuable information. Fortunate me I discovered your web site unintentionally, and I am stunned why this accident
    did not took place earlier! I bookmarked it.

  59. Write more, thats all I have to say. Literally, it seems as though you
    relied on the video to make your point. You obviously
    know what youre talking about, why waste your intelligence on just posting videos to your weblog when you could be giving
    us something informative to read?

  60. Hi! I could have sworn I’ve been to this site before but after browsing through
    some of the post I realized it’s new to me. Nonetheless, I’m
    definitely happy I found it and I’ll be bookmarking
    and checking back frequently!

  61. My brother recommended I may like this website.
    He was once totally right. This publish truly made my day.
    You can not believe simply how so much time I had spent for this info!
    Thank you!

  62. I am really impressed with your writing skills and also with the layout on your weblog.
    Is this a paid theme or did you modify it yourself?
    Either way keep up the excellent quality writing, it
    is rare to see a nice blog like this one today.

  63. I’ve been exploring for a little for any high quality
    articles or weblog posts on this sort of
    area . Exploring in Yahoo I ultimately stumbled upon this site.
    Studying this information So i’m happy to exhibit
    that I’ve a very good uncanny feeling I came upon exactly what I needed.

    I such a lot for sure will make sure to do not forget this site and provides it a glance on a
    relentless basis.

  64. Hey there this is kind of of off topic but I was wondering if blogs use WYSIWYG editors or if you have to manually code with HTML.
    I’m starting a blog soon but have no coding skills so I wanted to get advice from someone
    with experience. Any help would be enormously appreciated!

  65. Hi, i feel that i noticed you visited my weblog so
    i came to return the prefer?.I am attempting to to find things
    to improve my site!I guess its ok to make use of some
    of your ideas!!

  66. When I initially commented I seem to have clicked on the -Notify me when new comments are added- checkbox and now each time a comment is added I receive four emails with the exact same comment.

    Is there an easy method you can remove me from that
    service? Thank you!

  67. Very good blog! Do you have any tips for aspiring writers?

    I’m hoping to start my own blog soon but I’m a little lost
    on everything. Would you suggest starting with a free platform like WordPress or go for a paid option? There are
    so many choices out there that I’m totally overwhelmed ..
    Any tips? Thanks a lot!

  68. I’m amazed, I must say. Seldom do I come across a blog that’s both educative and entertaining, and
    without a doubt, you have hit the nail on the head. The issue is something
    not enough men and women are speaking intelligently about.
    I’m very happy I stumbled across this in my search for something concerning this.

  69. I was curious if you ever thought of changing the structure of
    your blog? Its very well written; I love what youve got to say.

    But maybe you could a little more in the way of content so people could
    connect with it better. Youve got an awful lot of text for only having
    1 or 2 pictures. Maybe you could space it out better?

  70. Hi there would you mind letting me know which hosting
    company you’re utilizing? I’ve loaded your blog in 3 completely different browsers and I must
    say this blog loads a lot quicker then most. Can you suggest a good
    hosting provider at a reasonable price? Cheers, I appreciate it!

  71. Awesome blog! Do you have any tips for aspiring writers? I’m hoping to start my
    own site soon but I’m a little lost on everything.
    Would you recommend starting with a free platform like WordPress or go for a
    paid option? There are so many options out there that I’m completely overwhelmed ..
    Any recommendations? Thanks!

  72. Hi, I do think this is a great blog. I stumbledupon it 😉
    I am going to come back yet again since I saved
    as a favorite it. Money and freedom is the
    greatest way to change, may you be rich and continue to help other people.

  73. My spouse and I stumbled over here from a different page and thought I
    should check things out. I like what I see so now i
    am following you. Look forward to finding out about your web page for a second time.

  74. Zudem sollten sie externe Links unzertrennlich eigenen Fenster oder
    Tab öffnen lassen, damit sie User nicht von der eigenen Seite
    verlieren. Bei der Checkliste für den Content
    solltest du auch den Einsatz von optimierten Bildern hängenbleiben. Diese werten Texte nicht nur visuell auf,
    sondern senden ebenfalls positive SEO-Signale annähernd Suchmaschinen. Daneben sollten Seitenbetreiber darauf achten, dass Bilder zwar eine gute Qualität haben, aber nicht zu groß
    ausfallen. Zur Orientierung: 2-4 Verlinkungen pro
    1000 Wörter zeigen Google und Nutzern, mit welchen Quellen deine
    Seite arbeitet. Der Spaß kostet SEO-Punkte. Letzten Endes sollten Unternehmen Bilder
    mit einem ALT-Attribut versehen, das das Fokus-Keyword trägt.

    Ansonsten leidet der Pagespeed darunter. Ausführliche Informationen und praktische Checkliste gibt es im Blogartikel zu Bilder SEO.
    Videos haben die letzte zeit Jahren im Web stark an Bedeutung gewonnen. Das gilt auch für den Dateinamen der
    Bilder. Unternehmen sollten ihre Videos auf YouTube hochladen. Denn: Bewegtbild
    eignet sich nicht nur, Content schnell und einfach zu erklären, sondern steigert auch die
    Verweildauer auf der Seite. Ob Newsletter-Anmeldung, Produktkauf oder Download:
    Jede Seite will User dazu bewegen, eine bestimmte Aktion vorzunehmen. Nach Google und Google Images ist
    das Videoportal die drittgrößte Suchmaschine des Webs. Dieser sollte verständlich formuliert und grafisch hervorgehoben sein. Dabei unterstützen wir Sie
    als erfahrene SEO Agentur mit fundiertem Wissen und zahlreichen individuell zugeschnittenen Maßnahmen, die Sie und Ihr Angebot in den Suchmaschinen anheizen und Ihr Ranking verbessern.
    Korrekte Darstellung auf allen Endgeräten: Smartphones, PCs, Tablets… Mehr Informationen auf OnPage Optimierung Agentur.
    Unternehmen sollten daher an jeder wichtigen Stelle auf der Seite einen Call-to-Action-Button integrieren.
    Sie suchen eine SEO Agentur? Kontaktiere uns für ein persönliches kostenloses
    Erstgespräch. Als erfahrener Partner helfen wir von seonative unseren Kunden dabei, auf Google besser
    gefunden zu werden und die Umsätze zu steigern. Sie wollen SEO Selber machen? Lernen Sie SEO in unseren SEO Workshops!

  75. Do not miss the fortunate probability because it an excellent
    opportunity to earn real cash and to turn out to be wealthy.
    When you play Joker 123 on-line, you’ll get a lot of money.
    Electronic gadgets are attending to be more particular, which implies that it’s a must to get one that’s made for your wants.
    All slots that have a excessive variance can potentially pay out larger amounts
    of cash prizes versus these which might be medium or low variance slots.
    Frame – to the shield wire Because the 850 was relatively costly, supplied extra capabilities than the
    typical consumer was in search of, and was at times unavailable from Atari despite high demand, there have been many 3rd-party
    interfaces designed to supply some suitable subset of the 850’s options.
    Vulnerable prospects due to the excessive demand for online slots”. ROYALPULSA99 meng-gratiskan pembuatan akun slot deposit pulsa untuk semua pemain yang ingin bergabung. Kami siuman kalau tujuan pemain judi slot deposit pulsa mempertaruhkan penghasilannya sebab mau memperoleh bonus keuangan supaya dapat penuhi kebutuhan hidup tiap- tiap.

  76. Thank you for every other informative website. The
    place else may just I am getting that type of info written in such a perfect method?

    I have a project that I am simply now operating on, and
    I have been on the glance out for such info.

  77. you are in reality a excellent webmaster. The site loading velocity is amazing.
    It sort of feels that you are doing any distinctive trick.
    Furthermore, The contents are masterpiece. you have performed a magnificent task on this matter!

  78. Hmm it appears like your blog ate my first comment (it was super long) so I
    guess I’ll just sum it up what I wrote and say, I’m thoroughly
    enjoying your blog. I too am an aspiring blog writer but I’m still new to everything.
    Do you have any points for inexperienced blog writers?
    I’d really appreciate it.

  79. I love what you guys tend to be up too. This type of clever work and reporting!
    Keep up the great works guys I’ve incorporated you guys to my own blogroll.

  80. What’s Going down i am new to this, I stumbled upon this I
    have found It absolutely helpful and it has helped me out loads.
    I am hoping to give a contribution & assist different users like its helped me.
    Good job.

  81. Does your website have a contact page? I’m having trouble locating it but, I’d like to shoot you an e-mail.
    I’ve got some recommendations for your blog you might be interested in hearing.

    Either way, great site and I look forward to seeing it develop over time.

  82. Amazing! This blog looks just like my old one! It’s on a entirely different subject but it has pretty much the same layout and design. Superb choice of colors!

  83. Excellent post. Keep writing such kind of info on your blog.
    Im really impressed by it.
    Hi there, You’ve performed a fantastic job. I’ll certainly
    digg it and in my view suggest to my friends. I’m confident they’ll be benefited from this site.

  84. My programmer is trying to persuade me to move to .net from PHP.
    I have always disliked the idea because of the costs.

    But he’s tryiong none the less. I’ve been using WordPress on various websites for about a year and am
    anxious about switching to another platform. I have heard great things about blogengine.net.
    Is there a way I can transfer all my wordpress posts
    into it? Any help would be greatly appreciated!

  85. Hi there, I found your web site by the use of Google whilst looking for a similar matter, your site got here up, it appears
    to be like good. I have bookmarked it in my google bookmarks.

    Hi there, just was aware of your blog via Google, and located that it’s really informative.

    I’m going to watch out for brussels. I will appreciate when you continue this in future.
    Many people can be benefited out of your writing. Cheers!

  86. Thank you, I’ve recently been looking for info
    approximately this topic for a long time and yours is the greatest
    I have found out so far. But, what about the bottom line?
    Are you certain in regards to the supply?

  87. Simply want to say your article is as surprising. The
    clearness on your submit is just cool and i could assume you’re knowledgeable on this subject.
    Well with your permission allow me to take hold of your RSS feed to keep up to date with impending post.
    Thanks one million and please continue the enjoyable work.

  88. I was suggested this web site by my cousin. I am not sure whether this post
    is written by him as no one else know such detailed
    about my problem. You are wonderful! Thanks!

  89. Hi there, You’ve done a great job. I will certainly digg
    it and personally recommend to my friends. I am confident
    they’ll be benefited from this website.

  90. Heya i’m for the primary time here. I found this board and I
    in finding It really helpful & it helped me out a lot.
    I am hoping to provide something again and aid others such as you aided me.

  91. I like the valuable information you provide in your
    articles. I will bookmark your blog and check again here regularly.
    I am quite sure I’ll learn a lot of new stuff right here!
    Good luck for the next!

  92. Write more, thats all I have to say. Literally, it seems as though
    you relied on the video to make your point. You definitely know what youre talking about,
    why waste your intelligence on just posting videos to
    your weblog when you could be giving us
    something informative to read?

  93. No matter if some one searches for his vital thing, thus he/she needs to be available that
    in detail, so that thing is maintained over here.

  94. You really make it appear so easy along with your
    presentation however I find this topic to be really one thing
    that I feel I would by no means understand. It seems too complex and very extensive for me.
    I am taking a look ahead in your next publish, I will try
    to get the hold of it!

  95. Usually I don’t read post on blogs, however I would like
    to say that this write-up very pressured me to try and
    do so! Your writing taste has been amazed me.
    Thanks, very great article.

  96. Your evaluation can be extremely interesting. If you need to play bandar slot online, I love to recommend
    playing upon respected slot pulsa niche sites. As you can attain big results all the advantages and receive given the assurance pay-out probabilities.
    If you want to experience, you may straight click the link00 listed below.

    The link could possibly be a video slot blog this is frequently used amidst Indonesian member.

  97. Can I simply say what a comfort to uncover somebody
    who truly understands what they’re talking about online.
    You actually realize how to bring an issue to light and
    make it important. More and more people should look at this and understand this side
    of your story. It’s surprising you’re not more popular since you
    most certainly have the gift.

  98. Hi there, I discovered your web site via Google at the
    same time as looking for a related matter, your web site came up, it appears
    good. I have bookmarked it in my google bookmarks.

    Hi there, simply changed into aware of your blog through Google, and located that it is
    really informative. I am going to watch out for brussels.
    I will be grateful for those who continue this in future.
    Many folks will probably be benefited from your writing.
    Cheers!

  99. Excellent items from you, man. I have keep in mind your stuff prior to and you’re just too magnificent.
    I really like what you’ve acquired here, certainly
    like what you are stating and the way in which wherein you assert it.

    You are making it entertaining and you still care for to keep it sensible.
    I can not wait to read much more from you. That is really a terrific website.

  100. Fantastic items from you, man. I’ve bear in mind your
    stuff prior to and you’re just too fantastic. I really
    like what you’ve got right here, certainly like what you’re stating and the best way in which you say it.
    You make it enjoyable and you still take care of to stay it smart.
    I can not wait to learn much more from you. This is
    actually a great site.

  101. Definitely consider that that you said. Your favorite reason appeared to
    be on the internet the simplest factor to understand of.
    I say to you, I definitely get irked while people think
    about issues that they just don’t recognize about. You controlled to hit the nail upon the top and outlined
    out the whole thing with no need side effect ,
    people can take a signal. Will likely be again to get more.
    Thanks

  102. May I simply just say what a comfort to find someone who actually knows what
    they’re talking about on the internet. You certainly know how to bring an issue to light and make it important.
    More people should read this and understand this side of the story.

    It’s surprising you aren’t more popular because you most certainly have the gift.

  103. Simply want to say your article is as surprising.
    The clearness in your post is just great and i can assume you
    are an expert on this subject. Well with your permission allow me to grab your feed to keep up to date with
    forthcoming post. Thanks a million and please continue the rewarding work.

  104. Hey there are using WordPress for your site platform?
    I’m new to the blog world but I’m trying to get started and
    set up my own. Do you need any html coding expertise
    to make your own blog? Any help would be really appreciated!

  105. Can I simply just say what a relief to uncover
    a person that really knows what they are talking
    about on the net. You actually understand how to bring an issue
    to light and make it important. More and more people have to check this
    out and understand this side of the story.
    I can’t believe you are not more popular since you
    definitely have the gift.

  106. Hey I know this is off topic but I was wondering if you knew of any widgets I could add to my blog that automatically tweet my newest twitter
    updates. I’ve been looking for a plug-in like this for quite
    some time and was hoping maybe you would have some experience
    with something like this. Please let me know if you run into anything.
    I truly enjoy reading your blog and I look forward
    to your new updates.

  107. I loved as much as you will receive carried out right here.
    The sketch is tasteful, your authored subject matter stylish.
    nonetheless, you command get bought an edginess over that you wish be delivering the following.
    unwell unquestionably come further formerly again since exactly the same nearly a lot often inside case you shield this increase.

  108. I absolutely love your blog and find many of your post’s to be precisely what I’m looking
    for. Do you offer guest writers to write content to
    suit your needs? I wouldn’t mind publishing a post or elaborating on most
    of the subjects you write regarding here. Again, awesome site!

  109. I loved as much as you will receive carried out right here.

    The sketch is attractive, your authored material stylish.

    nonetheless, you command get bought an shakiness over that you
    wish be delivering the following. unwell unquestionably come further formerly again as
    exactly the same nearly a lot often inside case
    you shield this hike.

  110. May I simply say what a relief to find someone who really understands
    what they are talking about over the internet. You
    actually know how to bring an issue to light and make it important.

    More people have to look at this and understand this side of your story.

    I was surprised that you are not more popular since you certainly possess
    the gift.

  111. After going over a few of the blog posts on your blog, I seriously like your way of blogging.

    I added it to my bookmark website list and will be checking back in the near future.
    Please visit my web site as well and let me know your opinion.

  112. Hi, I do believe this is a great web site. I stumbledupon it 😉 I may revisit
    once again since i have book-marked it. Money and freedom is the best way to change, may
    you be rich and continue to guide others.

  113. I’m really loving the theme/design of your blog. Do you ever run into any web browser compatibility problems?
    A small number of my blog audience have complained about my blog not working correctly in Explorer but
    looks great in Opera. Do you have any tips to help
    fix this problem?

  114. Hmm is anyone else experiencing problems with the images on this blog loading?
    I’m trying to figure out if its a problem
    on my end or if it’s the blog. Any feedback would
    be greatly appreciated.

  115. Thanks for ones marvelous posting! I definitely enjoyed reading it, you may be a great author.
    I will be sure to bookmark your blog and will eventually come
    back from now on. I want to encourage continue your great job, have a nice holiday
    weekend!

  116. Hi there, I discovered your website by means of Google whilst looking for a related subject, your
    website got here up, it seems great. I’ve bookmarked it in my
    google bookmarks.
    Hello there, just was aware of your weblog thru Google, and found that it is
    truly informative. I am gonna watch out for brussels.
    I’ll appreciate in the event you proceed this in future.
    A lot of other people will probably be benefited out of your writing.
    Cheers!

  117. Thank you a bunch for sharing this with all people you really recognise
    what you are speaking about! Bookmarked. Kindly also discuss with my web site =).
    We can have a link exchange contract among us

  118. Howdy! I know this is kinda off topic but I was wondering which blog platform are you using for this
    website? I’m getting tired of WordPress because I’ve had
    problems with hackers and I’m looking at alternatives for another platform.

    I would be great if you could point me in the direction of a good platform.

  119. Hi, this weekend is good in favor of me, for the reason that this occasion i am reading this wonderful
    informative piece of writing here at my residence.

  120. Thanks for some other informative web site. Where else may just I get that type of info
    written in such an ideal means? I have a project that I am just now working
    on, and I have been at the look out for such info.

  121. Oh my goodness! Impressive article dude! Thank you so much,
    However I am having issues with your RSS. I don’t know the reason why I am unable to join it.
    Is there anybody else getting the same RSS issues?
    Anyone that knows the answer will you kindly respond?
    Thanks!!

  122. You could certainly see your skills within the work you write.
    The sector hopes for even more passionate writers such as you who
    are not afraid to mention how they believe. All the time follow your
    heart.

  123. Hey superb blog! Does running a blog like this take a large amount of work?
    I’ve no expertise in coding however I had been hoping to start my own blog in the near future.
    Anyhow, if you have any suggestions or techniques for new blog owners please share.

    I know this is off subject nevertheless I just wanted to ask.
    Kudos!

  124. Thanks , I’ve just been searching for information approximately this subject for a while and yours
    is the greatest I have came upon so far. However, what about the conclusion? Are you sure concerning the source?

  125. May I simply say what a comfort to discover an individual who genuinely understands what they’re discussing over the internet.

    You definitely understand how to bring a problem to light and make
    it important. More and more people really need to read this and understand this side of the story.
    I was surprised that you’re not more popular given that you most certainly have the gift.

  126. Great blog here! Additionally your website
    rather a lot up very fast! What host are you the usage of?
    Can I get your associate hyperlink for your host?

    I desire my site loaded up as quickly as yours lol

  127. of course like your web site but you have to test the
    spelling on several of your posts. Several of them are rife with spelling problems and I find it very bothersome to tell the reality however I will certainly
    come back again.

  128. I was recommended this website by my cousin. I’m
    not sure whether this post is written by him as no one else know
    such detailed about my problem. You are amazing!
    Thanks!

  129. I loved as much as you will receive carried out right
    here. The sketch is tasteful, your authored material stylish.
    nonetheless, you command get bought an edginess
    over that you wish be delivering the following. unwell unquestionably
    come more formerly again as exactly the same nearly very often inside case you shield this hike.

  130. Hi there! I know this is kinda off topic but I was wondering which blog
    platform are you using for this site? I’m getting sick and tired of WordPress
    because I’ve had issues with hackers and I’m looking at options for another
    platform. I would be fantastic if you could point me in the direction of a good platform.

  131. Good day! I know this is kinda off topic however I’d figured I’d ask.
    Would you be interested in trading links or maybe guest authoring a blog
    article or vice-versa? My blog discusses a lot of the same subjects as yours and I think we could greatly benefit from each other.
    If you are interested feel free to send me an email. I look
    forward to hearing from you! Wonderful blog by the way!

  132. Hello there, I found your web site by the use of Google even as looking for a comparable subject,
    your web site came up, it seems to be great. I’ve bookmarked it in my
    google bookmarks.
    Hi there, simply became aware of your blog thru Google, and
    located that it’s truly informative. I am gonna watch out for
    brussels. I’ll appreciate in case you proceed this in future.
    Lots of people can be benefited out of your writing.
    Cheers!

  133. Hello, i think that i saw you visited my web site thus i came to “return the favor”.I am attempting to find things to enhance my website!I suppose
    its ok to use a few of your ideas!!

  134. Have you ever thought about publishing an e-book or guest authoring on other websites?
    I have a blog based on the same information you discuss and would love to have you share some stories/information. I know my subscribers would enjoy your work.
    If you’re even remotely interested, feel free to send me an e-mail.

  135. Heya! I understand this is kind of off-topic however
    I had to ask. Does building a well-established blog like yours take a lot of work?
    I’m brand new to blogging however I do write in my diary every day.
    I’d like to start a blog so I can easily share my experience
    and feelings online. Please let me know if you have any kind of ideas or tips for new aspiring blog owners.
    Thankyou!

  136. You’re so awesome! I don’t believe I’ve read a single thing like this before.

    So nice to find another person with a few unique thoughts on this issue.
    Seriously.. many thanks for starting this up. This site is one thing that
    is required on the web, someone with some originality!

  137. Hey there would you mind letting me know which web host you’re utilizing?
    I’ve loaded your blog in 3 different web browsers and I must say this blog loads a lot quicker then most.
    Can you suggest a good web hosting provider at a reasonable price?
    Thank you, I appreciate it!

  138. Oh my goodness! Incredible article dude! Thank you, However I
    am going through difficulties with your RSS.
    I don’t know why I cannot subscribe to it. Is there anyone else getting identical RSS problems?
    Anybody who knows the solution will you kindly respond?

    Thanks!!

  139. I am not sure where you are getting your info, but great topic.
    I needs to spend some time learning more or understanding more.
    Thanks for wonderful information I was looking for this information for my
    mission.

  140. It is the best time to make some plans for the future and it is time to
    be happy. I have read this post and if I could I desire to suggest you some
    interesting things or advice. Maybe you can write next articles referring to this
    article. I desire to read even more things about it!

  141. What i do not understood is in reality how you’re no
    longer really a lot more neatly-appreciated than you may be right now.
    You’re so intelligent. You realize thus considerably when it comes to
    this subject, made me individually imagine it from numerous numerous angles.
    Its like men and women are not fascinated except it is something to do
    with Girl gaga! Your personal stuffs great. All the time maintain it up!

  142. I don’t even know how I stopped up here, however I assumed this post used to be great.
    I do not recognise who you might be however certainly you’re going to
    a well-known blogger if you aren’t already. Cheers!

  143. Hello there I am so glad I found your webpage, I really found you by accident, while I
    was researching on Digg for something else, Nonetheless I am here now and would
    just like to say kudos for a fantastic post and a all round exciting blog (I
    also love the theme/design), I don’t have time to read it all at the moment but I have saved it and also added in your RSS feeds,
    so when I have time I will be back to read much more, Please
    do keep up the excellent b.

  144. Hi! I know this is somewhat off topic but I was wondering if you knew where
    I could locate a captcha plugin for my comment form?
    I’m using the same blog platform as yours and I’m having difficulty finding
    one? Thanks a lot!

  145. It’s in point of fact a nice and helpful piece of info.
    I am satisfied that you shared this helpful information with
    us. Please stay us up to date like this. Thank you for sharing.

  146. Hi there I am so thrilled I found your blog, I really found
    you by error, while I was browsing on Aol for something else, Anyhow I am here now
    and would just like to say cheers for a remarkable post and a all round interesting blog (I also
    love the theme/design), I don’t have time to browse it all at the minute but I
    have saved it and also added in your RSS feeds, so when I have time I will be back to
    read a great deal more, Please do keep up the superb job.

  147. Heya excellent website! Does running a blog like this take a
    massive amount work? I have virtually no knowledge of programming but I had been hoping to start
    my own blog soon. Anyhow, should you have any suggestions or tips for new blog owners please
    share. I know this is off topic nevertheless I just wanted to ask.
    Many thanks!

  148. I’ve been exploring for a little for any high quality articles or weblog posts on this kind of space .
    Exploring in Yahoo I eventually stumbled upon this site.
    Reading this info So i’m satisfied to show that I’ve a very excellent uncanny feeling I discovered
    just what I needed. I such a lot without a doubt will make sure to
    don?t forget this website and provides it a glance on a relentless basis.

  149. I really like what you guys tend to be up too. Such clever work and reporting!
    Keep up the amazing works guys I’ve added you guys
    to my blogroll.

  150. Magnificent site. Plenty of helpful info here. I am sending it to
    some friends ans also sharing in delicious. And obviously, thank you to your sweat!

  151. Hello, I think your blog might be having browser compatibility
    issues. When I look at your blog in Safari, it
    looks fine but when opening in Internet Explorer, it has some overlapping.
    I just wanted to give you a quick heads up!
    Other then that, great blog!

  152. Useful information. Fortunate me I found your web site by chance, and I’m shocked why this coincidence did not came about in advance!

    I bookmarked it.

  153. Good day! This is kind of off topic but I need some
    help from an established blog. Is it hard to set up your own blog?
    I’m not very techincal but I can figure things out pretty
    fast. I’m thinking about setting up my own but I’m not sure where to
    start. Do you have any tips or suggestions? Thank you

  154. Hello, I think your website might be having browser compatibility issues.
    When I look at your website in Firefox, it looks fine
    but when opening in Internet Explorer, it has some overlapping.
    I just wanted to give you a quick heads up! Other then that, amazing blog!

  155. Asking questions are truly fastidious thing if you are not understanding something
    entirely, except this paragraph presents fastidious understanding even.

  156. Hey there! This is kind of off topic but I need some help from
    an established blog. Is it very hard to set up your own blog?
    I’m not very techincal but I can figure things out
    pretty fast. I’m thinking about creating my own but I’m not sure where to begin. Do you have any ideas or
    suggestions? Appreciate it

  157. An outstanding share! I’ve just forwarded this onto a co-worker
    who had been doing a little homework on this.
    And he actually ordered me breakfast due to the fact that I found it for him…
    lol. So allow me to reword this…. Thanks for the meal!!

    But yeah, thanx for spending time to discuss this subject here on your
    web site.