chore: initial commit — UdyamLinc backend, phases A-E
Spring Boot 4 / Java 17 / Postgres. Masters and serving (A), Agmarknet and climate ingestion (B), feature store with 14 derivations (C), SpEL rule engine and risk scoring (D). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package com.lincfusion.udyamlinc.security;
|
||||
|
||||
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.authority.SimpleGrantedAuthority;
|
||||
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;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Reads the bearer token and grants two authorities: the role (ROLE_FIELD_OFFICER,
|
||||
* ROLE_OWNER…) and the scope (SCOPE_OFFICER / SCOPE_ENTERPRISE), which is what the
|
||||
* two API surfaces are separated on.
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
String header = request.getHeader("Authorization");
|
||||
if (header != null && header.startsWith("Bearer ")) {
|
||||
String token = header.substring(7);
|
||||
if (jwtUtil.isValid(token) && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
String subject = jwtUtil.subject(token);
|
||||
var authorities = List.of(
|
||||
new SimpleGrantedAuthority("ROLE_" + jwtUtil.role(token)),
|
||||
new SimpleGrantedAuthority("SCOPE_" + jwtUtil.scope(token)));
|
||||
var auth = new UsernamePasswordAuthenticationToken(subject, null, authorities);
|
||||
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
}
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.lincfusion.udyamlinc.security;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.security.Key;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Issues and reads the two token flavours: officer console and enterprise app. */
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
@Value("${jwt.expiration:86400000}")
|
||||
private long expirationMs;
|
||||
|
||||
private Key signingKey() {
|
||||
return Keys.hmacShaKeyFor(secret.getBytes());
|
||||
}
|
||||
|
||||
/** Field officer session — scope=OFFICER, carries the role and the blocks in view. */
|
||||
public String generateOfficerToken(String username, String role, String blocks) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("realm", "UDYAM");
|
||||
claims.put("scope", "OFFICER");
|
||||
claims.put("role", role);
|
||||
claims.put("blocks", blocks);
|
||||
return build(claims, username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enterprise app session — scope=ENTERPRISE, pinned to one enterprise id so
|
||||
* the owner can only ever read their own record.
|
||||
*/
|
||||
public String generateEnterpriseToken(String enterpriseId, String sector) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("realm", "UDYAM");
|
||||
claims.put("scope", "ENTERPRISE");
|
||||
claims.put("role", "OWNER");
|
||||
claims.put("enterpriseId", enterpriseId);
|
||||
claims.put("sector", sector);
|
||||
return build(claims, enterpriseId);
|
||||
}
|
||||
|
||||
private String build(Map<String, Object> claims, String subject) {
|
||||
long now = System.currentTimeMillis();
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(subject)
|
||||
.setIssuedAt(new Date(now))
|
||||
.setExpiration(new Date(now + expirationMs))
|
||||
.signWith(signingKey(), SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public Claims parse(String token) {
|
||||
return Jwts.parserBuilder().setSigningKey(signingKey()).build().parseClaimsJws(token).getBody();
|
||||
}
|
||||
|
||||
public String subject(String token) {
|
||||
return parse(token).getSubject();
|
||||
}
|
||||
|
||||
public String scope(String token) {
|
||||
return String.valueOf(parse(token).get("scope"));
|
||||
}
|
||||
|
||||
public String role(String token) {
|
||||
return String.valueOf(parse(token).get("role"));
|
||||
}
|
||||
|
||||
public boolean isValid(String token) {
|
||||
try {
|
||||
return parse(token).getExpiration().after(new Date());
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user