Open Route added with Spring security

This commit is contained in:
MathewFrancis 2025-05-12 17:39:09 +05:30
parent b87c2b6f7b
commit 824d824275
25 changed files with 719 additions and 17 deletions

View File

@ -0,0 +1,204 @@
{
"info": {
"_postman_id": "721d5504-301f-488d-a25b-5e78769eac5a",
"name": "CezenPBX_API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "29498098"
},
"item": [
{
"name": "create a new endpoint",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"id\": \"1005\",\n// \"transport\": \"transport-udp\",\n// \"context\": \"default\",\n// \"disallow\": \"all\",\n// \"allow\": \"ulaw,alaw\",\n// \"directMedia\": \"no\",\n \"connectedLineMethod\": null,\n \"callerid\": null,\n \"dtmfMode\": null,\n// \"mohsuggest\": \"default\",\n \"mailboxes\": null\n}\n\n// {\n// \"id\": \"1004\",\n// \"transport\": \"transport-udp\",\n// \"aors\": \"1004\",\n// \"auth\": \"1004\",\n// \"context\": \"default\",\n// \"disallow\": \"all\",\n// \"allow\": \"ulaw,alaw\",\n// \"directMedia\": \"no\",\n// \"connectedLineMethod\": null,\n// \"callerid\": \"User <1004>\",\n// \"dtmfMode\": null,\n// \"mohsuggest\": \"default\",\n// \"mailboxes\": \"1004@default\"\n// }",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/add_user",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"add_user"
]
}
},
"response": []
},
{
"name": "create a new extension",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"context\": \"default\",\n \"extension\": \"1005\",\n \"priority\": 1,\n \"app\": \"Dial\",\n \"appdata\": \"PJSIP/1005,20,m(default)\"\n}\n",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/add_extension",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"add_extension"
]
}
},
"response": []
},
{
"name": "set_password",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"id\": \"1005\",\n \"authType\": \"userpass\",\n \"userName\": \"1005\",\n \"password\": \"12345\",\n \"md5Cred\": null,\n \"realm\": null\n}\n",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/set_password",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"set_password"
]
}
},
"response": []
},
{
"name": "SetAORS",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"id\": \"1005\",\n \"maxContacts\": 1\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/set_aors",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"set_aors"
]
}
},
"response": []
},
{
"name": "DeleteExtension",
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "http://localhost:8081/cezen/delete_extension?sipNumber=testEndPoint",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"delete_extension"
],
"query": [
{
"key": "sipNumber",
"value": "testEndPoint"
}
]
}
},
"response": []
},
{
"name": "Add_a_global_extension_feature",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"context\": \"default\",\n \"extension\": \"w\",\n \"priority\": 5,\n \"app\": \"Dial\",\n \"appdata\": \"W conf\"\n}\n",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/add_feature",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"add_feature"
]
}
},
"response": []
},
{
"name": "login",
"request": {
"auth": {
"type": "noauth"
},
"method": "POST",
"header": [],
"url": {
"raw": "http://localhost:8081/open/signup",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"open",
"signup"
]
}
},
"response": []
}
]
}

View File

@ -1,11 +1,30 @@
1,50 18,4
18,3
18,2
18,1
18,0
17,3
17,2
17,1
17,0
16,4
16,3
16,2
16,1
16,0
15,3
15,2
15,1
15,0
14,4
14,3
14,2
14,1
14,0
13,3 13,3
13,2 13,2
13,1 13,1
13,0 13,0
11,5
11,4
11,3
11,2 11,2
11,1 11,1
11,0 11,0
@ -25,11 +44,6 @@
8,1 8,1
8,0 8,0
7,3 7,3
5,3
4,3
3,2
2,2
1,2
0,9 0,9
1,45 1,45
3,44 3,44
@ -153,23 +167,15 @@
2,8 2,8
1,8 1,8
3,7 3,7
2,7
1,7
3,6 3,6
2,6
1,6
3,5
2,5 2,5
1,5 1,5
3,4 3,4
2,4 2,4
1,4 1,4
3,3 3,3
3,0
2,3 2,3
2,0
1,3 1,3
1,0
0,6 0,6
0,0 0,0
0,47 0,47
@ -181,6 +187,22 @@
0,10 0,10
0,8 0,8
0,11 0,11
11,5
11,4
11,3
5,3
4,3
3,5
3,2
3,0
2,7
2,6
2,2
2,0
1,7
1,6
1,2
1,0
0,5 0,5
0,7 0,7
0,4 0,4

View File

@ -80,6 +80,25 @@
<artifactId>spring-security-test</artifactId> <artifactId>spring-security-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- JWT dependency -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -0,0 +1,10 @@
package com.example.cezenPBX.DAO;
// TODO only one admin allowed ... once the admin creates an
// account they should not be able to make the account again
// admin login, logout and signup DAO operations
public interface UserOpsDAO {
// admin login
}

View File

@ -0,0 +1,9 @@
package com.example.cezenPBX.DAO;
import org.springframework.stereotype.Repository;
@Repository
public class UserOpsDAOImpl implements UserOpsDAO{
}

View File

@ -1,11 +1,102 @@
package com.example.cezenPBX.config; package com.example.cezenPBX.config;
import com.example.cezenPBX.security.JWTTokenGeneratorFilter;
import com.example.cezenPBX.security.JWTTokenValidatorFilter;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
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.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import java.util.Collections;
import java.util.List;
// this class will handel the routs that are protected and // this class will handel the routs that are protected and
// allow spring security to accept login details from our custom login page // allow spring security to accept login details from our custom login page
@Configuration @Configuration
public class CezenLoginSecurityChain { public class CezenLoginSecurityChain {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
//the token is generated here
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
requestHandler.setCsrfRequestAttributeName("_csrf");
//CSRF cookie
final CookieCsrfTokenRepository cookieCsrfTokenRepo = new CookieCsrfTokenRepository();
//make secure true when using only https
cookieCsrfTokenRepo.setCookieCustomizer(responseCookieBuilder -> responseCookieBuilder.secure(true));
// bellow line is used when you are using JWT tokens instead of jSession session keys but i put always because i guess CSRF token needs it
http.
logout((logout) -> logout.deleteCookies("Authorization", "JSESSIONID", "XSRF-TOKEN"))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
//now because we aare sending the JWT token to The UI Application in a Header
//we need to manage it in the CORs config
.cors(corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
//check CORs and CSRF in Previous commits
CorsConfiguration config = new CorsConfiguration();
// config.setAllowedOrigins(Collections.singletonList("http://localhost:4200"));
config.setAllowedOrigins(Collections.singletonList("*"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowCredentials(true);
config.setAllowedHeaders(Collections.singletonList("*"));
//the JWT will be sent to UI under Authorization header and XSR under X-XSRF-TOKEN
config.setExposedHeaders(List.of("Authorization", "X-XSRF-TOKEN"));
config.setMaxAge(3600L);
return config;
}
}))
.csrf(AbstractHttpConfigurer::disable)
//.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
//token generation after BasicAuthenticationFilter.class
.addFilterAfter(new JWTTokenGeneratorFilter(), BasicAuthenticationFilter.class)
//then position the verification filter
.addFilterBefore(new JWTTokenValidatorFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests((requests) -> requests
//only admin can use this rout
//user roles :- ROLE_admin ROLE_employee ROLE_manager ROLE_user
.requestMatchers(
"/admin/get_all_users",
"/admin/list_all_branches_with_manager"
).hasAnyRole("admin")
//any one who is authenticated can access /logout
.requestMatchers("/bankUser/login", "/user/getXSRfToken", "/logout").authenticated()
.requestMatchers("/bankUser/**").hasAnyRole("user")
//all the rest are open to public
.requestMatchers("/open/**").permitAll()
//.requestMatchers(HttpMethod.POST, "/open/**").permitAll()
)
// redirect to /login if the user is not authenticated Customizer.withDefaults() enables a security feature using the defaults provided by Spring Security
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults());
return http.build();
}
// to encode the password
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
} }

View File

@ -0,0 +1,146 @@
//package com.example.cezenPBX.config;
//
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.security.authentication.AuthenticationProvider;
//import org.springframework.security.authentication.BadCredentialsException;
//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
//import org.springframework.security.core.Authentication;
//import org.springframework.security.core.AuthenticationException;
//import org.springframework.security.core.GrantedAuthority;
//import org.springframework.security.core.authority.SimpleGrantedAuthority;
//import org.springframework.security.crypto.password.PasswordEncoder;
//
//import java.util.ArrayList;
//import java.util.List;
//
//public class CustomAuthenticationProviderForCezen implements AuthenticationProvider {
//
// @Autowired
// private UserRepository userRepository;
//
// @Autowired
// private EmployeeRepository employeeRepository;
//
// @Autowired
// private PasswordEncoder passwordEncoder;
//
// @Override
// public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//
// //get credentials from login form
// String username = authentication.getName();
// String pwd = authentication.getCredentials().toString();
//
// //sanity check
// if (username.isEmpty() || pwd.isEmpty()) return null;
//
// System.out.println(pwd);
// System.out.println(username);
//
// int employeeId = 0;
// boolean isEmployee = false;
//
// //what if the username is an employee login
// try {
// //if true
// employeeId = Integer.parseInt(username);
// isEmployee = true;
// } catch (Exception e) {
// System.out.println(e.toString());
// }
//
// //employee auth
// if (isEmployee) {
// // if it is a valid number range
// if (employeeId > 0) {
// //check for employee
// Employee employee = null;
// try {
// //check if employee exists if yes then fetch details
// employee = employeeRepository.getEmployeeAndRolesById(employeeId);
// } catch (Exception e) {
// System.out.println(e.toString());
// return null;
// }
//
// if (passwordEncoder.matches(pwd, employee.getPassword())) {
//
// //then it is a match a number of springs granted authorities
// List<GrantedAuthority> authorities = new ArrayList<>();
//
// //loop through the users authorities and add each of them to simple granted authority
// try {
//
// //check if employee is part of permission set for employee signing in
// boolean hasEmployee = false;
// for(var permission : employee.getRoles()){
// if(permission.getRole().equals("ROLE_employee")) hasEmployee = true;
// }
// if(!hasEmployee) throw new BadCredentialsException("no employee permission for given employee");
//
// employee.getRoles().forEach(a -> authorities.add(new SimpleGrantedAuthority(a.getRole())));
// } catch (Exception e) {
// //use/**/r doesn't have permissions or roles = null
// System.out.println(e.toString());
// return null;
// }
//
// return new UsernamePasswordAuthenticationToken(employeeId, pwd, authorities);
// } else {
// throw new BadCredentialsException("Invalid password!");
// }
// } else {
// throw new BadCredentialsException("No user registered with this details!");
// }
//
// }
//
// //customer
// User customer = null;
// try {
// customer = userRepository.getUserDetailsByUserName(username);
//
// } catch (Exception e) {
// throw new BadCredentialsException("No user registered with this details!");
// }
//
// //if the person exists
// if (customer != null) {
// System.out.println(customer.getPassword());
//
// //check for a match
// if (passwordEncoder.matches(pwd, customer.getPassword())) {
//
// //then it is a match a number of springs granted authorities
// List<GrantedAuthority> authorities = new ArrayList<>();
//
// //loop through the users authorities and add each of them to simple granted authority
// try {
// customer.getRoles().forEach(a -> authorities.add(new SimpleGrantedAuthority(a.getRole())));
// } catch (Exception e) {
// //user doesn't have permissions or roles = null
// System.out.println(e.toString());
// return null;
// }
// //final send the username password and auth as a token which will call the authenticate method in the ProviderManager
// // in this edit i wont store the password but a use id
//
// //this is so that i can get a global access to an authenticated users name and id
// username = username + "," + customer.getId() + "," + customer.getUserAccountId().getId();
//
// return new UsernamePasswordAuthenticationToken(username, pwd, authorities);
// } else {
// throw new BadCredentialsException("Invalid password!");
// }
// } else {
// throw new BadCredentialsException("No user registered with this details!");
// }
// }
//
// @Override
// public boolean supports(Class<?> authentication) {
// //tells spring that i want to support username password style of auth
// return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
// }
//
//}

View File

@ -0,0 +1,7 @@
package com.example.cezenPBX.constents;
public interface SecurityConstants {
public static final String JWT_KEY = ";sdmn3426FHB426RH62389;]['/.sdwswa";
public static final String JWT_HEADER = "Authorization";
}

View File

@ -0,0 +1,25 @@
package com.example.cezenPBX.controller;
import com.example.cezenPBX.DTO.ReturnStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/open")
public class SignUpController {
//sign up route
@PostMapping("/signup")
public ReturnStatus signUp(){
return new ReturnStatus(true, "User created successfully", "");
}
// and a login route
// forgot password
}

View File

@ -0,0 +1,81 @@
package com.example.cezenPBX.security;
import com.example.cezenPBX.constents.SecurityConstants;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
public class JWTTokenGeneratorFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//at this point the user is authenticated we just have to send the token back
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println(authentication);
if (null != authentication) {
//get the JWT key from the contents we defined
// Keys, Jwts class comes from pom.xml
SecretKey key = Keys.hmacShaKeyFor(SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
//creating a JWT token
// issuer issues a jwt token
//subject can be any value
String jwt = Jwts.builder().issuer("Mathew Francis").subject("JWT_Token")
//building the token
.claim("username", authentication.getName())
.claim("authorities", populateAuthorities(authentication.getAuthorities()))
.issuedAt(new Date())
.expiration(new Date((new Date()).getTime() + 30000000))
//signing it with the key we set on line 35
.signWith(key).compact();
//SecurityConstants.JWT_HEADER, in the Constants SecurityConstants folder
//response.setHeader(SecurityConstants.JWT_HEADER, jwt);
//uncomment for cookie based saving
Cookie cookie = new Cookie(SecurityConstants.JWT_HEADER,jwt);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
response.addCookie(cookie);
System.out.println("JWT Generated");
}
System.out.println("Intercepted");
System.out.println(response.getHeader("X-XSRF-TOKEN"));
filterChain.doFilter(request, response);
}
//only generate if the path is login
//other words this method will return false for /login
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return (request.getServletPath().equals("/open/signup") || request.getServletPath().equals("/open/employee-login"));
}
// gets the authority's from granted authority which we set in the configuration CustomAuthenticationProvider class
// plug in user auth into jwt token
private String populateAuthorities(Collection<? extends GrantedAuthority> collection) {
Set<String> authoritiesSet = new HashSet<>();
for (GrantedAuthority authority : collection) {
authoritiesSet.add(authority.getAuthority());
}
return String.join(",", authoritiesSet);
}
}

View File

@ -0,0 +1,81 @@
package com.example.cezenPBX.security;
import com.example.cezenPBX.constents.SecurityConstants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class JWTTokenValidatorFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// SecurityConstants
// public static final String JWT_KEY = "jxgEQeXHuPq8VdbyYFNkANdudQ53YUn4";
// public static final String JWT_HEADER = "Authorization";
//String jwt = request.getHeader(SecurityConstants.JWT_HEADER);
//below is the COOKIE approach
String jwt = null;
for(var cookie : request.getCookies()){
if(cookie.getName().equals("Authorization")){
System.out.print("COOKIE");
System.out.println(cookie.getValue());
jwt = cookie.getValue();
}
}
if (null != jwt) {
try {
//generating the key
SecretKey key = Keys.hmacShaKeyFor(
SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
//verification of legitimacy
Claims claims = Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(jwt)
.getPayload();
String username = String.valueOf(claims.get("username"));
String authorities = (String) claims.get("authorities");
// System.out.println("JWT name : "+ username);
// System.out.println("JWT auth "+ authorities);
//if successful the result will be stored in SecurityContextHolder
Authentication auth = new UsernamePasswordAuthenticationToken(username, null,
//this comes in a string of comas and values
AuthorityUtils.commaSeparatedStringToAuthorityList(authorities));
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception e) {
throw new BadCredentialsException("Invalid Token received!");
}
}
filterChain.doFilter(request, response);
}
//should be executed for all the api except the login api
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getServletPath().equals("/open/signup")
|| request.getServletPath().equals("/employee/employee-login");
// //bellow was done to archive this /exposed/**
// request.getServletPath().split("/")[1].equals("exposed");
}
}

View File

@ -0,0 +1,7 @@
package com.example.cezenPBX.service;
import org.springframework.stereotype.Service;
@Service
public class PbxUserController {
}