Role-based authorization is a method of restricting access to certain parts of an application based on the roles assigned to users. Each role corresponds to a set of permissions, and users are granted these permissions according to their roles. This ensures that users only have access to the resources and actions necessary for their responsibilities.
Real Life Analogy:
Imagine you're in a company where there are different levels of employees: an intern, a manager, and a CEO. Each employee has different access to certain areas of the office and different permissions.
- Intern: They have access to the office but are only allowed to enter specific areas, like the general workspace. They can't access the meeting rooms or secure areas.
- Manager: A manager can enter more areas than an intern. They have access to meeting rooms, the general workspace, and can make decisions about certain projects.
- CEO: The CEO has the highest level of access. They can go anywhere in the office, access secure areas, and even approve new policies or decisions.
- Roles: The intern, manager, and CEO represent roles.
- Permissions: What they are allowed to do (enter rooms, make decisions, etc.) represent the permissions tied to their roles.
- System: When an employee tries to access a restricted area (like the meeting room), the system checks their role. If the role matches the required permissions for that area, they are granted access.
- Purpose: To verify your identity.
- Analogy: Imagine you're standing at the entrance of a building. The security guard asks you to show your ID or swipe your access card.
- Example: In an app, this is like entering your username and password. The system checks if the credentials are correct and verifies who you are.
- Key Question: Are you really who you say you are?
- Purpose: To determine what you can access or what actions you're allowed to perform.
- Analogy: After you're identified by the security guard (authentication), they check if you're authorized to enter certain areas. Maybe you're allowed in the general office space but not in the restricted server room.
- Example: In an app, once you're logged in, the system checks what resources you're allowed to access (e.g., viewing certain pages or performing specific actions based on your role).
- Key Question: What permissions do you have?
Code
Have to make some roles.
public enum Roles {
USER,
ADMIN,
CREATOR
}
In our UserEntity class we have to implement the getAuthorities() method. Here we define the roles also.
import jakarta.persistence.*;
import lombok.*;
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.Set;
import java.util.stream.Collectors;
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class UserEntity implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)//indicate that email should be unique
private String email;
private String password;
private String name;
// @Enumerated(EnumType.STRING)
// private Roles role; //One user have one Role
@ElementCollection(fetch = FetchType.EAGER)
@Enumerated(EnumType.STRING)
private Set<Roles> roles; //one user have multiple role
//we have to implement these three methods
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles
.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" +role.name()))
.collect(Collectors.toSet());
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.name;
}
}
Basically when we will signup, we have to add the roles also. For this we have to define roles here also.
import lombok.Data;
import java.util.Set;
@Data
public class SignupDto {
private String email;
private String password;
private String name;
private Set<Roles> roles;
}
In our WebSecurityConfig class we used requestMachers() to allow that who have the access for the particular method of the particular endpoint.
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity //by adding this annotation we are telling springboot that we want to we want to configure this SpringSecurityFilterChain
@RequiredArgsConstructor
public class WebSecurityConfig {
private final JWTAuthFilter jwtAuthFilter;
private final OAuth2SuccessHandler oAuth2SuccessHandler;
private static final String[] publicRoutes = {
"/error","/auth/**","/home.html"
};
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeHttpRequests(auth->
auth
.requestMatchers(publicRoutes).permitAll() // Public endpoints
.requestMatchers("/endpoint/**").hasRole(Roles.ADMIN.name()) // all the routes after endpoint will be allowed for "ADMIN" only without admin if any one is trying to send any request on this endpoint then it will show a forbidden error
.anyRequest().authenticated()) // All other requests require authentication
.csrf(csrfConfig-> // Disable CSRF for simplicity, not recommended for production
csrfConfig
.disable())
.sessionManagement(sessionConfig -> // Disable JSESSIONID for simplicity, not recommended for production
sessionConfig
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.oauth2Login(oauth2Config->oauth2Config
.failureUrl("/login?error=true")
.successHandler(oAuth2SuccessHandler));
return httpSecurity.build(); //when you add build this throws an exception
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
Create JWT token using user’s roles.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Service
public class JWTService {
@Value("${jwt.secretKey}")
private String jwtSecreteKey ;
public SecretKey generateSecreteKey(){
return Keys.hmacShaKeyFor(jwtSecreteKey.getBytes(StandardCharsets.UTF_8));
}
//create a jwt
public String createAccessToken(UserEntity user) {
return Jwts.builder()
.subject(user.getId().toString())
.claim("email",user.getEmail())
.claim("roles", user.getRoles().toString()) //for roles
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis()+1000*60*10)) //setting it for 10 mins only
.signWith(generateSecreteKey())
.compact();
}
public String createRefreshToken(UserEntity user) {
return Jwts.builder()
.subject(user.getId().toString())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis()+ 1000L *60*60*24*30*6)) //here we have to set it for long time 6 months
.signWith(generateSecreteKey())
.compact();
}
//verify the token
public Long generateUserIdFromToken(String token) {
Claims claim = Jwts.parser() // to parse it
.verifyWith(generateSecreteKey())// verify the key that we have
.build() // we will get out the token
.parseSignedClaims(token)
.getPayload(); //to get the payload
return Long.valueOf(claim.getSubject());
}
}
Here, create a newToken after authorization.
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
@RequiredArgsConstructor
public class JWTAuthFilter extends OncePerRequestFilter {
private final JWTService jwtService;
private final UserServiceImpl userService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
if(requestTokenHeader == null || !requestTokenHeader.startsWith("Bearer")){
filterChain.doFilter(request,response);
return;
}
String token = requestTokenHeader.split("Bearer ")[1];
Long userId = jwtService.generateUserIdFromToken(token);
if(userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserEntity user = userService.getUserById(userId);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
authenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(request,response);
}
}
Creating some user with roles.
User Entity Table :
User- Role Entity Table:
According to our logic only ‘ADMIN’ can access all the routes after “/endpoint”. According to our database only “Alice” and “Bob” can be accessed this. If we login with any of them then after login AT and RT are generated. And if we want to create a post on “/endpoint” we will be allowed.
According to our logic, only users with the 'ADMIN' role can access all routes starting with /endpoint
. In our database, only Alice and Bob are assigned the 'ADMIN' role, so they are the only users who can access these routes.
- When Alice or Bob logs in, they will be successfully authenticated, and Access Token (AT) and Refresh Token (RT) are generated for them.
- After successful authentication, they will be authorized to access the routes under
/endpoint
because their role ('ADMIN') grants them permission to do so. - If they want to create a post on
/endpoint
(e.g., POST /endpoint
), their 'ADMIN' role gives them the required authorization to perform that action.
In summary, only Alice and Bob can access and create posts on /endpoint
because they are both 'ADMIN' users.
Generate AT and RT.
You need to authenticate the '/endpoint' route by including your access token in the 'Headers' or 'Authorization' section.
You can also include a body in your '/endpoint' route.
Since 'Rama' and 'Raman' are not assigned the 'ADMIN' role, they do not have the necessary permissions to access routes under /endpoint
. So, if Rama or Raman tries to create a post using POST /endpoint
, they will be denied access and will receive a "403 Forbidden Error".
This is because the 'ADMIN' role is required to access and create posts on these routes, and Rama and Raman do not have that role.
Login with Rama.
If you include the accessToken and body and try to send the request, it gives you a 'Forbidden' error.
If we try to create a post on /endpoint without any authentication, the request will be redirected to the login page. This is because authentication is required before accessing any protected routes like /endpoint. Since the user is not logged in, the system redirects them to the login page to verify their identity.
The .requestMatchers()
method is used in Spring Security to define security configurations for specific HTTP requests based on their URL patterns. It allows you to specify which routes or endpoints should be secured or require certain permissions.