Spring Boot多用户类型认证方案咨询:校园管理系统师生权限管控
Hey there! As someone who's built similar role-based auth systems with Spring Boot, let's walk through exactly how to set this up for your teacher-student management system. We'll cover JWT token issuance, role-based API access, and handle both the legacy WebSecurityConfigurerAdapter approach (since you mentioned it) plus the modern Spring Security 5.7+ way (which is recommended now).
1. Core Concepts to Keep in Mind
First, let's align on the flow:
- User (teacher/student) sends username + password to the login endpoint
- Server validates credentials, generates a JWT token containing user roles
- Client stores the token (usually in localStorage or cookies) and sends it in the
Authorization: Bearer <token>header for subsequent requests - Server filters incoming requests to validate the token, checks if the user has the required role for the API
We'll use two roles: TEACHER and STUDENT (you can define these as enums or strings, just be consistent).
2. Add Required Dependencies
First, add these to your pom.xml (Maven) or build.gradle (Gradle) to get Spring Security and JWT support:
Maven
<dependencies> <!-- Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- JJWT for JWT handling --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <!-- Spring Web for APIs --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
Gradle
dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' implementation 'org.springframework.boot:spring-boot-starter-web' }
3. User Entity & UserDetailsService
Create a User entity to store user credentials and roles. Also implement UserDetailsService to load user data from your database (we'll use an in-memory example for simplicity, but you can swap it with JPA).
User Entity
import jakarta.persistence.*; import java.util.List; @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; @ElementCollection(fetch = FetchType.EAGER) private List<String> roles; // e.g., ["TEACHER", "STUDENT"] // Getters, setters, constructors }
UserDetailsService Implementation
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.User; import org.springframework.stereotype.Service; import java.util.List; @Service public class CustomUserDetailsService implements UserDetailsService { // In real app, inject UserRepository to fetch from DB @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // Example: Fetch user from DB User dbUser = getUserFromDb(username); if (dbUser == null) { throw new UsernameNotFoundException("User not found: " + username); } return User.withUsername(dbUser.getUsername()) .password(dbUser.getPassword()) // Make sure password is encoded! .roles(dbUser.getRoles().toArray(new String[0])) .build(); } // Mock DB call private User getUserFromDb(String username) { // Replace with actual DB query if ("teacher1".equals(username)) { return new User(1L, "teacher1", "$2a$10$...", List.of("TEACHER")); } else if ("student1".equals(username)) { return new User(2L, "student1", "$2a$10$...", List.of("STUDENT")); } return null; } }
Note: Always store passwords using BCrypt (or another strong encoder). Use
PasswordEncoderto hash passwords before saving to the DB.
4. JWT Utility Class
Create a helper class to generate and validate JWT tokens:
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @Component public class JwtUtil { // Use a strong secret key (store in environment variables, not hardcoded!) private String SECRET_KEY = "your-strong-secret-key-here-change-in-production"; public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } public Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.apply(claims); } private Claims extractAllClaims(String token) { return Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token).getBody(); } private Boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); // Add roles to claims claims.put("roles", userDetails.getAuthorities().stream() .map(auth -> auth.getAuthority()) .toList()); return createToken(claims, userDetails.getUsername()); } private String createToken(Map<String, Object> claims, String subject) { return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public Boolean validateToken(String token, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } }
5. JWT Authentication Filter
This filter will intercept incoming requests, validate the JWT token, and set the authenticated user in the security context:
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; 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.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final CustomUserDetailsService userDetailsService; public JwtAuthenticationFilter(JwtUtil jwtUtil, CustomUserDetailsService userDetailsService) { this.jwtUtil = jwtUtil; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwt = null; if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwt = authorizationHeader.substring(7); username = jwtUtil.extractUsername(jwt); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtUtil.validateToken(jwt, userDetails)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } chain.doFilter(request, response); } }
6. Security Configuration
Now let's configure role-based access. We'll cover both the legacy WebSecurityConfigurerAdapter (since you asked about it) and the modern SecurityFilterChain approach (recommended for Spring Boot 2.7+ / Spring Security 5.7+).
Option 1: Legacy WebSecurityConfigurerAdapter
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.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 public class SecurityConfig extends WebSecurityConfigurerAdapter { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CustomUserDetailsService userDetailsService; public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, CustomUserDetailsService userDetailsService) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; this.userDetailsService = userDetailsService; } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() // Allow anonymous access to login endpoint .antMatchers("/api/auth/login").permitAll() // Only teachers can access these endpoints .antMatchers("/api/teacher/**").hasRole("TEACHER") // Only students can access these endpoints .antMatchers("/api/student/**").hasRole("STUDENT") // All other endpoints require authentication .anyRequest().authenticated() .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // No sessions, use JWT http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } @Override protected void configure(org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
Option 2: Modern SecurityFilterChain (Recommended)
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.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 public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CustomUserDetailsService userDetailsService; public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, CustomUserDetailsService userDetailsService) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; this.userDetailsService = userDetailsService; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/login").permitAll() .requestMatchers("/api/teacher/**").hasRole("TEACHER") .requestMatchers("/api/student/**").hasRole("STUDENT") .anyRequest().authenticated()) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } }
7. Login Endpoint Implementation
Create a controller to handle login requests and return JWT tokens:
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; 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; @RestController @RequestMapping("/api/auth") public class AuthController { private final AuthenticationManager authenticationManager; private final CustomUserDetailsService userDetailsService; private final JwtUtil jwtUtil; public AuthController(AuthenticationManager authenticationManager, CustomUserDetailsService userDetailsService, JwtUtil jwtUtil) { this.authenticationManager = authenticationManager; this.userDetailsService = userDetailsService; this.jwtUtil = jwtUtil; } @PostMapping("/login") public String createAuthenticationToken(@RequestBody AuthRequest authRequest) throws Exception { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()) ); } catch (Exception e) { throw new Exception("Invalid username or password", e); } final UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.getUsername()); final String jwt = jwtUtil.generateToken(userDetails); return jwt; } // DTO for login request public static class AuthRequest { private String username; private String password; // Getters and setters 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; } } }
8. Test the Setup
- Send a POST request to
/api/auth/loginwith JSON body{"username": "teacher1", "password": "your-password"}to get a JWT token. - Use this token in the
Authorization: Bearer <token>header to access/api/teacher/**endpoints (students won't be able to access these). - Similarly, students can use their token to access
/api/student/**endpoints, and teachers will be blocked from those.
Key Notes for Production
- Never hardcode your JWT secret key: Store it in environment variables or a secrets manager.
- Set appropriate token expiration: Adjust the expiration time based on your security needs (shorter is better for sensitive systems).
- Use HTTPS: Always serve your API over HTTPS to prevent token interception.
- Handle token refresh: Implement a refresh token mechanism so users don't have to log in every time the access token expires.
内容的提问来源于stack exchange,提问作者Rohit

