认证授权系统设计
以下是一个完整的认证授权系统的Java实现,包含常见的认证授权功能如JWT生成与验证、角色权限控制等。
1. 项目结构
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── auth/
│ │ ├── config/ # 配置类
│ │ ├── controller/ # 控制器
│ │ ├── dto/ # 数据传输对象
│ │ ├── entity/ # 实体类
│ │ ├── exception/ # 异常处理
│ │ ├── repository/ # 数据访问
│ │ ├── security/ # 安全相关
│ │ ├── service/ # 业务逻辑
│ │ └── AuthApplication.java
│ └── resources/
│ └── application.yml # 配置文件
└── test/ # 测试代码
2. 完整代码实现
2.1 主应用类
package com.example.auth;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级别的权限控制
public class AuthApplication {public static void main(String[] args) {SpringApplication.run(AuthApplication.class, args);}
}
2.2 配置类
WebSecurityConfig.java
package com.example.auth.config;import com.example.auth.security.JwtAuthenticationEntryPoint;
import com.example.auth.security.JwtAuthenticationFilter;
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.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {private final UserDetailsService userDetailsService;private final JwtAuthenticationEntryPoint unauthorizedHandler;public WebSecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationEntryPoint unauthorizedHandler) {this.userDetailsService = userDetailsService;this.unauthorizedHandler = unauthorizedHandler;}@Beanpublic JwtAuthenticationFilter jwtAuthenticationFilter() {return new JwtAuthenticationFilter();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/api/auth/**").permitAll().antMatchers("/api/test/**").permitAll().anyRequest().authenticated();http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}
}
JwtConfig.java
package com.example.auth.config;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class JwtConfig {@Value("${jwt.secret}")private String secret;@Value("${jwt.expiration}")private Long expiration;@Value("${jwt.tokenPrefix}")private String tokenPrefix;@Value("${jwt.headerString}")private String headerString;// Getterspublic String getSecret() {return secret;}public Long getExpiration() {return expiration;}public String getTokenPrefix() {return tokenPrefix;}public String getHeaderString() {return headerString;}
}
2.3 实体类
User.java
package com.example.auth.entity;import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;@Entity
@Table(name = "users")
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(nullable = false, unique = true)private String username;@Column(nullable = false)private String password;@ManyToMany(fetch = FetchType.LAZY)@JoinTable(name = "user_roles",joinColumns = @JoinColumn(name = "user_id"),inverseJoinColumns = @JoinColumn(name = "role_id"))private Set<Role> roles = new HashSet<>();// Constructors, getters and setterspublic User() {}public User(String username, String password) {this.username = username;this.password = password;}// Getters and setterspublic Long getId() {return id;}public void setId(Long id) {this.id = id;}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;}public Set<Role> getRoles() {return roles;}public void setRoles(Set<Role> roles) {this.roles = roles;}
}
Role.java
package com.example.auth.entity;import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;@Entity
@Table(name = "roles")
public class Role {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Enumerated(EnumType.STRING)@Column(length = 20)private ERole name;@ManyToMany(mappedBy = "roles")private Set<User> users = new HashSet<>();public Role() {}public Role(ERole name) {this.name = name;}// Getters and setterspublic Long getId() {return id;}public void setId(Long id) {this.id = id;}public ERole getName() {return name;}public void setName(ERole name) {this.name = name;}public Set<User> getUsers() {return users;}public void setUsers(Set<User> users) {this.users = users;}
}
ERole.java
package com.example.auth.entity;public enum ERole {ROLE_USER,ROLE_MODERATOR,ROLE_ADMIN
}
2.4 DTO类
LoginRequest.java
package com.example.auth.dto;import javax.validation.constraints.NotBlank;public class LoginRequest {@NotBlankprivate String username;@NotBlankprivate String password;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;}
}
SignupRequest.java
package com.example.auth.dto;import javax.validation.constraints.*;
import java.util.Set;public class SignupRequest {@NotBlank@Size(min = 3, max = 20)private String username;@NotBlank@Size(max = 50)@Emailprivate String email;private Set<String> roles;@NotBlank@Size(min = 6, max = 40)private String password;// Getters and setterspublic String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public Set<String> getRoles() {return roles;}public void setRoles(Set<String> roles) {this.roles = roles;}
}
JwtResponse.java
package com.example.auth.dto;import java.util.List;public class JwtResponse {private String token;private String type = "Bearer";private Long id;private String username;private List<String> roles;public JwtResponse(String accessToken, Long id, String username, List<String> roles) {this.token = accessToken;this.id = id;this.username = username;this.roles = roles;}// Getterspublic String getAccessToken() {return token;}public String getTokenType() {return type;}public Long getId() {return id;}public String getUsername() {return username;}public List<String> getRoles() {return roles;}
}
2.5 异常处理
AppException.java
package com.example.auth.exception;import org.springframework.http.HttpStatus;public class AppException extends RuntimeException {private final HttpStatus status;public AppException(String message, HttpStatus status) {super(message);this.status = status;}public HttpStatus getStatus() {return status;}
}
GlobalExceptionHandler.java
package com.example.auth.exception;import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;import java.util.Date;@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(AppException.class)public ResponseEntity<ErrorMessage> handleAppException(AppException ex, WebRequest request) {ErrorMessage message = new ErrorMessage(new Date(),ex.getMessage(),request.getDescription(false),ex.getStatus().value());return new ResponseEntity<>(message, ex.getStatus());}@ExceptionHandler(Exception.class)public ResponseEntity<ErrorMessage> handleGlobalException(Exception ex, WebRequest request) {ErrorMessage message = new ErrorMessage(new Date(),ex.getMessage(),request.getDescription(false),500);return new ResponseEntity<>(message, HttpStatus.INTERNAL_SERVER_ERROR);}
}
2.6 安全相关
JwtAuthenticationEntryPoint.java
package com.example.auth.security;import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response,AuthenticationException authException) throws IOException {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");}
}
JwtAuthenticationFilter.java
package com.example.auth.security;import com.example.auth.config.JwtConfig;
import com.example.auth.service.UserDetailsServiceImpl;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
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 javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate JwtTokenProvider tokenProvider;@Autowiredprivate UserDetailsServiceImpl userDetailsService;@Autowiredprivate JwtConfig jwtConfig;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {try {String jwt = getJwtFromRequest(request);if (jwt != && tokenProvider.validateToken(jwt)) {Long userId = tokenProvider.getUserIdFromToken(jwt);UserDetails userDetails = userDetailsService.loadUserById(userId);UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(userDetails,, userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authentication);}} catch (ExpiredJwtException ex) {request.setAttribute("exception", ex);} catch (Exception ex) {logger.error("Could not set user authentication in security context", ex);}filterChain.doFilter(request, response);}private String getJwtFromRequest(HttpServletRequest request) {String bearerToken = request.getHeader(jwtConfig.getHeaderString());if (bearerToken != && bearerToken.startsWith(jwtConfig.getTokenPrefix())) {return bearerToken.substring(jwtConfig.getTokenPrefix().length() + 1);}return;}
}
JwtTokenProvider.java
package com.example.auth.security;import com.example.auth.config.JwtConfig;
import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;import java.util.Date;@Component
public class JwtTokenProvider {private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);@Autowiredprivate JwtConfig jwtConfig;public String generateToken(Authentication authentication) {UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();Date now = new Date();Date expiryDate = new Date(now.getTime() + jwtConfig.getExpiration());return Jwts.builder().setSubject(userPrincipal.getId().toString()).setIssuedAt(new Date()).setExpiration(expiryDate).signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret()).compact();}public Long getUserIdFromToken(String token) {Claims claims = Jwts.parser().setSigningKey(jwtConfig.getSecret()).parseClaimsJws(token).getBody();return Long.parseLong(claims.getSubject());}public boolean validateToken(String authToken) {try {Jwts.parser().setSigningKey(jwtConfig.getSecret()).parseClaimsJws(authToken);return true;} catch (SignatureException ex) {logger.error("Invalid JWT signature");} catch (MalformedJwtException ex) {logger.error("Invalid JWT token");} catch (ExpiredJwtException ex) {logger.error("Expired JWT token");} catch (UnsupportedJwtException ex) {logger.error("Unsupported JWT token");} catch (IllegalArgumentException ex) {logger.error("JWT claims string is empty");}return false;}
}
UserPrincipal.java
package com.example.auth.security;import com.example.auth.entity.User;
import com.fasterxml.jackson.annotation.JsonIgnore;
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.Objects;
import java.util.stream.Collectors;public class UserPrincipal implements UserDetails {private Long id;private String username;@JsonIgnoreprivate String password;private Collection<? extends GrantedAuthority> authorities;public UserPrincipal(Long id, String username, String password,Collection<? extends GrantedAuthority> authorities) {this.id = id;this.username = username;this.password = password;this.authorities = authorities;}public static UserPrincipal create(User user) {List<GrantedAuthority> authorities = user.getRoles().stream().map(role -> new SimpleGrantedAuthority(role.getName().name())).collect(Collectors.toList());return new UserPrincipal(user.getId(),user.getUsername(),user.getPassword(),authorities);}public Long getId() {return id;}@Overridepublic String getUsername() {return username;}@Overridepublic String getPassword() {return password;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return authorities;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == || getClass() != o.getClass()) return false;UserPrincipal that = (UserPrincipal) o;return Objects.equals(id, that.id);}@Overridepublic int hashCode() {return Objects.hash(id);}
}
2.7 服务层
AuthService.java
package com.example.auth.service;import com.example.auth.dto.LoginRequest;
import com.example.auth.dto.SignupRequest;
import com.example.auth.dto.JwtResponse;
import com.example.auth.entity.ERole;
import com.example.auth.entity.Role;
import com.example.auth.entity.User;
import com.example.auth.exception.AppException;
import com.example.auth.repository.RoleRepository;
import com.example.auth.repository.UserRepository;
import com.example.auth.security.JwtTokenProvider;
import com.example.auth.security.UserPrincipal;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;@Service
public class AuthService {private final AuthenticationManager authenticationManager;private final UserRepository userRepository;private final RoleRepository roleRepository;private final PasswordEncoder passwordEncoder;private final JwtTokenProvider tokenProvider;public AuthService(AuthenticationManager authenticationManager,UserRepository userRepository,RoleRepository roleRepository,PasswordEncoder passwordEncoder,JwtTokenProvider tokenProvider) {this.authenticationManager = authenticationManager;this.userRepository = userRepository;this.roleRepository = roleRepository;this.passwordEncoder = passwordEncoder;this.tokenProvider = tokenProvider;}public JwtResponse authenticateUser(LoginRequest loginRequest) {Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),loginRequest.getPassword()));SecurityContextHolder.getContext().setAuthentication(authentication);String jwt = tokenProvider.generateToken(authentication);UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();List<String> roles = userPrincipal.getAuthorities().stream().map(GrantedAuthority -> GrantedAuthority.getAuthority()).collect(Collectors.toList());return new JwtResponse(jwt, userPrincipal.getId(), userPrincipal.getUsername(), roles);}public User registerUser(SignupRequest signUpRequest) {if (userRepository.existsByUsername(signUpRequest.getUsername())) {throw new AppException("Username is already taken!", HttpStatus.BAD_REQUEST);}// Creating user's accountUser user = new User(signUpRequest.getUsername(),passwordEncoder.encode(signUpRequest.getPassword()));Set<String> strRoles = signUpRequest.getRoles();Set<Role> roles = new HashSet<>();if (strRoles ==) {Role userRole = roleRepository.findByName(ERole.ROLE_USER).orElseThrow(() -> new AppException("Role is not found.", HttpStatus.INTERNAL_SERVER_ERROR));roles.add(userRole);} else {strRoles.forEach(role -> {switch (role) {case "admin":Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN).orElseThrow(() -> new AppException("Role is not found.", HttpStatus.INTERNAL_SERVER_ERROR));roles.add(adminRole);break;case "mod":Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR).orElseThrow(() -> new AppException("Role is not found.", HttpStatus.INTERNAL_SERVER_ERROR));roles.add(modRole);break;default:Role userRole = roleRepository.findByName(ERole.ROLE_USER).orElseThrow(() -> new AppException("Role is not found.", HttpStatus.INTERNAL_SERVER_ERROR));roles.add(userRole);}});}user.setRoles(roles);return userRepository.save(user);}
}
UserDetailsServiceImpl.java
package com.example.auth.service;import com.example.auth.entity.User;
import com.example.auth.repository.UserRepository;
import com.example.auth.security.UserPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class UserDetailsServiceImpl implements UserDetailsService {private final UserRepository userRepository;public UserDetailsServiceImpl(UserRepository userRepository) {this.userRepository = userRepository;}@Override@Transactionalpublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));return UserPrincipal.create(user);}@Transactionalpublic UserDetails loadUserById(Long id) {User user = userRepository.findById(id).orElseThrow(() -> new UsernameNotFoundException("User not found with id: " + id));return UserPrincipal.create(user);}
}
2.8 仓库层
UserRepository.java
package com.example.auth.repository;import com.example.auth.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;import java.util.Optional;@Repository
public interface UserRepository extends JpaRepository<User, Long> {Optional<User> findByUsername(String username);Boolean existsByUsername(String username);
}
RoleRepository.java
package com.example.auth.repository;import com.example.auth.entity.ERole;
import com.example.auth.entity.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;import java.util.Optional;@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {Optional<Role> findByName(ERole name);
}
2.9 控制器层
AuthController.java
package com.example.auth.controller;import com.example.auth.dto.JwtResponse;
import com.example.auth.dto.LoginRequest;
import com.example.auth.dto.SignupRequest;
import com.example.auth.entity.User;
import com.example.auth.service.AuthService;
import org.springframework.http.ResponseEntity;
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;import javax.validation.Valid;@RestController
@RequestMapping("/api/auth")
public class AuthController {private final AuthService authService;public AuthController(AuthService authService) {this.authService = authService;}@PostMapping("/signin")public ResponseEntity<JwtResponse> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {JwtResponse jwtResponse = authService.authenticateUser(loginRequest);return ResponseEntity.ok(jwtResponse);}@PostMapping("/signup")public ResponseEntity<User> registerUser(@Valid @RequestBody SignupRequest signUpRequest) {User user = authService.registerUser(signUpRequest);return ResponseEntity.ok(user);}
}
TestController.java
package com.example.auth.controller;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api/test")
public class TestController {@GetMapping("/all")public String allAccess() {return "Public Content.";}@GetMapping("/user")@PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')")public String userAccess() {return "User Content.";}@GetMapping("/mod")@PreAuthorize("hasRole('MODERATOR')")public String moderatorAccess() {return "Moderator Board.";}@GetMapping("/admin")@PreAuthorize("hasRole('ADMIN')")public String adminAccess() {return "Admin Board.";}
}
2.10 配置文件
application.yml
server:port: 8080spring:datasource:url: jdbc:h2:mem:testdbdriver-class-name: org.h2.Driverusername: sapassword:jpa:database-platform: org.hibernate.dialect.H2Dialecthibernate:ddl-auto: create-dropshow-sql: trueh2:console:enabled: truepath: /h2-consolejwt:secret: mySecretexpiration: 86400000 # 24 hourstokenPrefix: BearerheaderString: Authorization
3. 功能说明
这个认证授权系统实现了以下功能:
- 用户认证:
- 使用JWT进行无状态认证
- 用户名密码登录
- 令牌生成与验证
- 用户注册:
- 新用户注册
- 角色分配(用户、管理员、版主)
- 权限控制:
- 基于角色的访问控制
- 方法级别的权限注解
- 不同角色访问不同端点
- 安全配置:
- CSRF保护禁用(适合REST API)
- CORS配置
- 会话管理设置为无状态
- 异常处理:
- 统一的异常处理机制
- 自定义错误消息
4. 使用说明
- 启动应用:运行
AuthApplication
主类 - 访问H2控制台:
http://localhost:8080/h2-console
- API端点:
- 登录:POST
/api/auth/signin
- 注册:POST
/api/auth/signup
- 测试端点:
- GET
/api/test/all
- 公开访问 - GET
/api/test/user
- 需要认证 - GET
/api/test/mod
- 需要MODERATOR角色 - GET
/api/test/admin
- 需要ADMIN角色
- GET
- 登录:POST
5. 扩展建议
- 添加刷新令牌功能
- 实现密码重置功能
- 添加验证码支持
- 实现OAuth2登录(Google, Facebook等)
- 添加审计日志
- 实现IP白名单/黑名单