Skip to content

Commit 5606f55

Browse files
fix: improve student management validation and security
1 parent 75132b7 commit 5606f55

11 files changed

Lines changed: 622 additions & 252 deletions

File tree

src/main/java/com/student/DataInitializer.java

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,37 @@
22

33
import com.student.model.Student;
44
import com.student.repository.StudentRepository;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
57
import org.springframework.boot.CommandLineRunner;
68
import org.springframework.context.annotation.Bean;
79
import org.springframework.context.annotation.Configuration;
810

911
@Configuration
1012
public class DataInitializer {
1113

14+
private static final Logger log = LoggerFactory.getLogger(DataInitializer.class);
15+
1216
@Bean
1317
CommandLineRunner initData(StudentRepository repository) {
1418
return args -> {
15-
if (repository.count() == 0) {
16-
repository.save(new Student(null, "Ram", "Raaam@email.com", "Computer Science", 20, "03001234567"));
17-
repository.save(new Student(null, "Radha", "Raadha@email.com", "Software Engineering", 21, "03011234567"));
18-
repository.save(new Student(null, "Krishna", "Krrishna@email.com", "Computer Science", 22, "03021234567"));
19-
repository.save(new Student(null, "Sita", "Siita@email.com", "Data Science", 19, "03031234567"));
20-
repository.save(new Student(null, "Hanuman", "Haanuman@email.com", "Cyber Security", 23, "03041234567"));
21-
22-
System.out.println("Sample data added successfully!");
23-
} else {
24-
System.out.println("Data already exists, skipping insert...");
19+
if (repository.count() > 0) {
20+
log.info("Student data already exists. Skipping sample data.");
21+
return;
2522
}
23+
24+
repository.save(new Student(null, "Ram", "ram@example.com",
25+
"Computer Science", 20, "9000000001"));
26+
repository.save(new Student(null, "Radha", "radha@example.com",
27+
"Software Engineering", 21, "9000000002"));
28+
repository.save(new Student(null, "Krishna", "krishna@example.com",
29+
"Computer Science", 22, "9000000003"));
30+
repository.save(new Student(null, "Sita", "sita@example.com",
31+
"Data Science", 19, "9000000004"));
32+
repository.save(new Student(null, "Hanuman", "hanuman@example.com",
33+
"Cyber Security", 23, "9000000005"));
34+
35+
log.info("Sample student data added successfully.");
2636
};
2737
}
28-
}
38+
}
Lines changed: 86 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,94 @@
11
package com.student.config;
22

3+
import org.springframework.beans.factory.annotation.Value;
34
import org.springframework.context.annotation.Bean;
45
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.http.HttpMethod;
57
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
6-
import org.springframework.security.web.SecurityFilterChain;
7-
import org.springframework.security.crypto.password.PasswordEncoder;
88
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
9+
import org.springframework.security.crypto.password.PasswordEncoder;
910
import org.springframework.security.core.userdetails.User;
1011
import org.springframework.security.core.userdetails.UserDetails;
1112
import org.springframework.security.core.userdetails.UserDetailsService;
1213
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
14+
import org.springframework.security.web.SecurityFilterChain;
15+
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
1316

1417
@Configuration
1518
public class SecurityConfig {
1619

20+
@Value("${app.admin.username}")
21+
private String adminUsername;
22+
23+
@Value("${app.admin.password}")
24+
private String adminPassword;
25+
26+
@Value("${app.demo.username}")
27+
private String demoUsername;
28+
29+
@Value("${app.demo.password}")
30+
private String demoPassword;
31+
1732
@Bean
1833
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
34+
35+
CookieCsrfTokenRepository csrfTokenRepository =
36+
CookieCsrfTokenRepository.withHttpOnlyFalse();
37+
1938
http
20-
.csrf(csrf -> csrf
21-
.ignoringRequestMatchers("/api/**")
22-
.ignoringRequestMatchers("/login")
23-
)
39+
.csrf(csrf -> csrf
40+
.csrfTokenRepository(csrfTokenRepository)
41+
)
42+
2443
.authorizeHttpRequests(auth -> auth
25-
26-
.requestMatchers("/login.html", "/login", "/css/**", "/js/**").permitAll()
27-
28-
.requestMatchers("/api/students/**").authenticated()
29-
.requestMatchers("/dashboard.html").authenticated()
30-
.requestMatchers("/dashboard/**").authenticated()
31-
.requestMatchers("/").authenticated()
44+
.requestMatchers(
45+
"/login.html",
46+
"/login",
47+
"/csrf",
48+
"/css/**",
49+
"/js/**"
50+
).permitAll()
51+
52+
.requestMatchers(HttpMethod.GET, "/api/students/**")
53+
.authenticated()
54+
55+
.requestMatchers(HttpMethod.POST, "/api/students/**")
56+
.hasRole("ADMIN")
57+
58+
.requestMatchers(HttpMethod.PUT, "/api/students/**")
59+
.hasRole("ADMIN")
60+
61+
.requestMatchers(HttpMethod.DELETE, "/api/students/**")
62+
.hasRole("ADMIN")
63+
64+
.requestMatchers("/dashboard.html", "/dashboard/**", "/")
65+
.authenticated()
66+
3267
.anyRequest().authenticated()
3368
)
69+
3470
.formLogin(form -> form
3571
.loginPage("/login.html")
3672
.loginProcessingUrl("/login")
3773
.defaultSuccessUrl("/dashboard", true)
3874
.failureUrl("/login.html?error=true")
3975
.permitAll()
4076
)
77+
4178
.logout(logout -> logout
4279
.logoutUrl("/logout")
43-
.logoutSuccessUrl("/login.html")
80+
.logoutSuccessUrl("/login.html?logout=true")
81+
.invalidateHttpSession(true)
82+
.clearAuthentication(true)
83+
.deleteCookies("JSESSIONID", "XSRF-TOKEN")
4484
.permitAll()
45-
)
85+
)
86+
4687
.sessionManagement(session -> session
47-
.maximumSessions(1)
48-
.maxSessionsPreventsLogin(false)
88+
.maximumSessions(1)
89+
.maxSessionsPreventsLogin(false)
4990
);
91+
5092
return http.build();
5193
}
5294

@@ -55,22 +97,32 @@ public PasswordEncoder passwordEncoder() {
5597
return new BCryptPasswordEncoder();
5698
}
5799

58-
59100
@Bean
60-
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
61-
UserDetails admin = User.builder()
62-
.username(
63-
System.getenv("ADMIN_USERNAME") != null
64-
? System.getenv("ADMIN_USERNAME")
65-
: "localadmin"
66-
)
67-
.password(encoder.encode(
68-
System.getenv("ADMIN_PASSWORD") != null
69-
? System.getenv("ADMIN_PASSWORD")
70-
: "localpass123"
71-
))
72-
.roles("ADMIN")
73-
.build();
74-
return new InMemoryUserDetailsManager(admin);
101+
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
102+
103+
requireCredential("app.admin.username", adminUsername);
104+
requireCredential("app.admin.password", adminPassword);
105+
requireCredential("app.demo.username", demoUsername);
106+
requireCredential("app.demo.password", demoPassword);
107+
108+
UserDetails admin = User.builder()
109+
.username(adminUsername)
110+
.password(passwordEncoder.encode(adminPassword))
111+
.roles("ADMIN")
112+
.build();
113+
114+
UserDetails demoUser = User.builder()
115+
.username(demoUsername)
116+
.password(passwordEncoder.encode(demoPassword))
117+
.roles("USER")
118+
.build();
119+
120+
return new InMemoryUserDetailsManager(admin, demoUser);
121+
}
122+
123+
private void requireCredential(String property, String value) {
124+
if (value == null || value.isBlank()) {
125+
throw new IllegalStateException(property + " is missing");
126+
}
75127
}
76-
}
128+
}
Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,46 @@
11
package com.student.controller;
22

3-
43
import org.springframework.security.core.Authentication;
5-
import org.springframework.security.core.context.SecurityContextHolder;
4+
import org.springframework.security.web.csrf.CsrfToken;
65
import org.springframework.stereotype.Controller;
7-
import org.springframework.web.bind.annotation.*;
6+
import org.springframework.web.bind.annotation.GetMapping;
7+
import org.springframework.web.bind.annotation.ResponseBody;
88

99
@Controller
1010
public class DashboardController {
11-
11+
1212
@GetMapping("/")
13-
public String root() {
14-
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
15-
if (auth == null || !auth.isAuthenticated() ||
16-
auth.getPrincipal().equals("anonymousUser")) {
13+
public String root(Authentication authentication) {
14+
if (authentication == null || !authentication.isAuthenticated()) {
1715
return "redirect:/login.html";
1816
}
1917
return "redirect:/dashboard";
2018
}
2119

2220
@GetMapping("/dashboard")
23-
public String dashboard() {
24-
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
25-
if (auth == null || !auth.isAuthenticated() || auth.getPrincipal().equals("anonymousUser")) {
21+
public String dashboard(Authentication authentication) {
22+
if (authentication == null || !authentication.isAuthenticated()) {
2623
return "redirect:/login.html";
2724
}
2825
return "forward:/dashboard.html";
2926
}
3027

3128
@GetMapping("/getRole")
3229
@ResponseBody
33-
public String getRole() {
34-
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
35-
if (auth != null && auth.getAuthorities() != null) {
36-
return auth.getAuthorities().stream()
37-
.findFirst()
38-
.map(a -> a.getAuthority().replace("ROLE_", ""))
39-
.orElse("USER");
30+
public String getRole(Authentication authentication) {
31+
if (authentication == null || !authentication.isAuthenticated()) {
32+
return "USER";
4033
}
41-
return "USER";
34+
35+
return authentication.getAuthorities().stream()
36+
.findFirst()
37+
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
38+
.orElse("USER");
39+
}
40+
41+
@GetMapping("/csrf")
42+
@ResponseBody
43+
public CsrfToken csrf(CsrfToken token) {
44+
return token;
4245
}
43-
}
46+
}

src/main/java/com/student/controller/StudentController.java

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@
22

33
import com.student.model.Student;
44
import com.student.service.StudentService;
5-
import org.springframework.beans.factory.annotation.Autowired;
5+
import jakarta.validation.Valid;
66
import org.springframework.http.HttpStatus;
77
import org.springframework.http.ResponseEntity;
8-
import org.springframework.security.core.Authentication;
9-
import org.springframework.security.core.context.SecurityContextHolder;
108
import org.springframework.web.bind.annotation.*;
119

1210
import java.util.List;
1311

1412
@RestController
1513
@RequestMapping("/api/students")
16-
@CrossOrigin(origins = "https://prashant-students.onrender.com")
1714
public class StudentController {
1815

19-
@Autowired
20-
private StudentService studentService;
16+
private final StudentService studentService;
17+
18+
public StudentController(StudentService studentService) {
19+
this.studentService = studentService;
20+
}
2121

2222
@GetMapping
2323
public ResponseEntity<List<Student>> getAllStudents() {
@@ -32,39 +32,23 @@ public ResponseEntity<Student> getStudentById(@PathVariable Long id) {
3232
}
3333

3434
@PostMapping
35-
public ResponseEntity<Student> addStudent(@RequestBody Student student) {
35+
public ResponseEntity<Student> addStudent(@Valid @RequestBody Student student) {
3636
Student saved = studentService.addStudent(student);
3737
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
3838
}
3939

4040
@PutMapping("/{id}")
41-
public ResponseEntity<Student> updateStudent(@PathVariable Long id, @RequestBody Student student) {
42-
try {
43-
Student updated = studentService.updateStudent(id, student);
44-
return ResponseEntity.ok(updated);
45-
} catch (RuntimeException e) {
46-
return ResponseEntity.notFound().build();
47-
}
41+
public ResponseEntity<Student> updateStudent(
42+
@PathVariable Long id,
43+
@Valid @RequestBody Student student) {
44+
45+
return ResponseEntity.ok(studentService.updateStudent(id, student));
4846
}
4947

5048
@DeleteMapping("/{id}")
5149
public ResponseEntity<String> deleteStudent(@PathVariable Long id) {
52-
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
53-
54-
boolean isAdmin = auth != null && auth.getAuthorities().stream()
55-
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
56-
57-
if (!isAdmin) {
58-
return ResponseEntity.status(HttpStatus.FORBIDDEN)
59-
.body("Access Denied! Only ADMIN can delete.");
60-
}
61-
62-
try {
63-
studentService.deleteStudent(id);
64-
return ResponseEntity.ok("Student deleted successfully!");
65-
} catch (RuntimeException e) {
66-
return ResponseEntity.notFound().build();
67-
}
50+
studentService.deleteStudent(id);
51+
return ResponseEntity.ok("Student deleted successfully!");
6852
}
6953

7054
@GetMapping("/search")
@@ -76,4 +60,4 @@ public ResponseEntity<List<Student>> searchStudents(@RequestParam String name) {
7660
public ResponseEntity<List<Student>> getStudentsByCourse(@PathVariable String course) {
7761
return ResponseEntity.ok(studentService.getStudentsByCourse(course));
7862
}
79-
}
63+
}

0 commit comments

Comments
 (0)