今天在使用SpringSecuity6.0时发现.authorizeRequests()已经被弃用了,发现网上的教程都是旧的,就写一篇关于最新稳定版的初步使用。
通过本教程学习可以通过密码使用jwt进行登录。
<!-- JWT API -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<!-- JWT Implementation -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
</dependency>
<!-- JWT and Jackson Integration -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
</dependency>
前置知识
使用框架的密码登录的流程
- 首先前端会传递一个JOSN,包含用户名和密码,我们用DTO层接收,我们先思考一下,对于用户名和密码,我们得先从数据库查找到用户名然后和密码进行匹配,对于密码肯定不能明文存储,所以框架给我们提供了接口和密码存储的工具。
- 因为框架有很多验证方式,本文采用的就是账户密码的方式,所以使用
UsernamePasswordAuthenticationToken来进行处理用户名和密码,对框架来说就是一个Authentication,它就是会保存你登录后一些信息,登录成功后需要设置在SecurityContext这样框架就可以的对用户进行认证授权。 - 同时我们还需要使用框架的接口来存储信息,一个是
UserDetails,一个是UserDetailsService,UserDetails就是通过UserDetailsService的一个方法loadUserByUsername来从数据库获取信息,获取到的信息会被存在Authentication的principal属性上 - 这时就有个问题,你说来实现
UsernamePasswordAuthenticationToken处理登录,框架怎么知道我写的是哪个服务的,所以这时就需要AuthenticationManager,它就是专门负责我们定义的处理方式,我们通过DaoAuthenticationProvider来添加我们自定义的UserDetailsServiceUserDetails就可以了,DaoAuthenticationProvider就是AuthenticationManager的子类 - 最后因为密码要安全的存储到数据库,就需要添加框架的密码
PasswordEncoder来添加自定义的存储方式
代码实现
- 配置securityFilterChain
// SecurityConfig
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
@NonNull JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@NonNull CorsFilter corsFilter;
@NonNull UserServiceImpl userService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers("/", "/login", "/swagger-ui.html", "/register").permitAll()
.requestMatchers("/swagger-resources/**").permitAll()
.requestMatchers("/webjars/**").permitAll()
.requestMatchers("/*/api-docs/**").anonymous()
.requestMatchers(HttpMethod.GET, "/", "/*.html", "/*/*.html", "/*/*.css", "/*/*.js",
"/profile/**").permitAll()
.anyRequest().authenticated()
)
.csrf(AbstractHttpConfigurer::disable)
.logout((logout) ->
logout
.logoutUrl("/logout")
.logoutSuccessUrl("/logout/success")
)
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
return http.build();
}
}
- 配置JWT
@Component
@Slf4j
@Data
@RequiredArgsConstructor
public class TokenProvider {
/**
* 自定义令牌标识
*/
@Value("${token.header}")
private String header;
/**
* 令牌秘钥
*/
@Value("${token.secret}")
private String secret;
/**
* 自动刷新token的时间,当过期时间不足autoRefreshTime的值的时候,会触发刷新用户登录缓存的时间
* 比如这个值是20, 用户是8点登录的, 8点半缓存会过期, 当过8.10分的时候,就少于20分钟了,便触发
* 刷新登录用户的缓存时间
*/
@Value("${token.autoRefreshTime}")
private int autoRefreshTime;
public String generateToken(Authentication authentication) {
User userPrincipal = (User) authentication.getPrincipal();
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).collect(Collectors.joining(","));
return Jwts.builder()
.setClaims(Map.of("aut", authorities))
.setSubject(userPrincipal.getUsername())
.setExpiration(DateUtil.offsetHour(DateUtil.date(), autoRefreshTime))
.signWith(getKey())
.compact();
}
public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getKey())
.build()
.parseClaimsJws(token).getBody();
}
public boolean validateToken(String authToken) {
try {
Claims claims = parseToken(authToken);
return !isTokenExpired(claims);
} catch (MalformedJwtException | UnsupportedJwtException |
IllegalArgumentException jwtException) {
log.error("Token validation error: {}", jwtException.getMessage());
throw new ApiException(jwtException, ErrorCodeMsg.FAIL);
}
}
private boolean isTokenExpired(Claims claims) {
return claims.getExpiration().toInstant().isBefore(Instant.now());
}
/**
* 生成key
*
* @return
*/
public Key getKey() {
return new SecretKeySpec(secret.getBytes(), SignatureAlgorithm.HS256.getJcaName());
}
}
- 配置JWTFilter
@Component
@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@NonNull
TokenProvider tokenProvider;
@NonNull UserService userService;
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {
Optional<String> jwt = resolveHeaderToken(request);
if (jwt.isPresent() && tokenProvider.validateToken(jwt.get())) {
String subject = tokenProvider.parseToken(jwt.get()).getSubject();
UserDetails userDetails = userService.loadUserByUsername(subject);
var authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
}
filterChain.doFilter(request, response);
}
private Optional<String> resolveHeaderToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
return StringUtils.isNotEmpty(bearerToken) && bearerToken.startsWith("Bearer ") ? Optional.of(bearerToken.substring(7)) : Optional.empty();
}
}
- 配置跨域
@Configuration
public class FilterConfig {
/**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOriginPattern("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}
}
- 配置Controller
@RestController
@Slf4j
@RequiredArgsConstructor
@Tag(name = "登录API", description = "登录相关接口")
public class LoginController {
@NonNull
private PasswordEncoder passwordEncoder;
@NonNull
private AuthenticationManager authenticationManager;
@NonNull
private TokenProvider tokenProvider;
@NonNull
private UserService userService;
@GetMapping("/")
public String home() {
log.info("1");
return "Hello Nexta1";
}
@PostMapping("/register")
@Operation(summary = "注册")
public ResponseDTO<Void> register(@Valid @RequestBody SignupDTO userDTO) {
User user = new User();
BeanUtils.copyProperties(userDTO, user);
var encodePassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodePassword);
userService.save(user);
return ResponseDTO.ok();
}
@PostMapping("/login")
@Operation(summary = "登录")
public ResponseDTO<String> login(@RequestBody @Valid LoginDTO loginDTO) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
loginDTO.username(), loginDTO.password()));
context.setAuthentication(authentication);
String token = tokenProvider.generateToken(authentication);
return ResponseDTO.ok(token);
}
}
- 配置DTO
@Data
@AllArgsConstructor
public class ResponseDTO<T> {
private Integer code;
private String msg;
private T data;
public static <T> ResponseDTO<T> ok() {
return build(null, ErrorCodeMsg.SUCCESS);
}
public static <T> ResponseDTO<T> ok(T data) {
return build(data, ErrorCodeMsg.SUCCESS);
}
public static <T> ResponseDTO<T> fail() {
return build(null, ErrorCodeMsg.FAIL);
}
public static <T> ResponseDTO<T> fail(T data) {
return build(data, ErrorCodeMsg.FAIL);
}
public static <T> ResponseDTO<T> fail(ErrorInterface code) {
return build(null, code);
}
public static <T> ResponseDTO<T> fail(ErrorInterface code, Object... args) {
return build(null, code, args);
}
public static <T> ResponseDTO<T> fail(ApiException exception) {
return build(exception.getError().getCode(), exception.getError().getMsg());
}
public static <T> ResponseDTO<T> build(T data, ErrorInterface code, Object... args) {
return new ResponseDTO<>(code.getCode(), StrUtil.format(code.getMsg(), args), data);
}
public static <T> ResponseDTO<T> build(Integer code, String msg) {
return new ResponseDTO<>(code, msg, null);
}
}
// LoginDTO
public record LoginDTO(String username, @NotNull String password) {
}
// SignUpDTO
@EqualsAndHashCode(callSuper = true)
@Data
public class SignupDTO extends UserDTO {
String password;
}
// UserDTO
@Data
public class UserDTO {
String username;
}
- 配置MVC
@Entity
@Getter
@Setter
@ToString
@RequiredArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
User user = (User) o;
return getId() != null && Objects.equals(getId(), user.getId());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
public interface UserRepository extends JpaRepository<User, Long> {
User findUserByUsername(String username);
}
// interface
public interface UserService extends UserDetailsService, UserDetailsPasswordService {
List<User> getUsers();
User save(User user);
}
// impl
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
@NonNull
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findUserByUsername(username);
if (user == null) {
throw new ApiException(ErrorCodeMsg.FAIL);
}
return user;
}
@Override
public List<User> getUsers() {
return userRepository.findAll();
}
@Override
public User save(User user) {
return userRepository.save(user);
}
@Override
public UserDetails updatePassword(UserDetails user, String newPassword) {
return null;
}
}
- 配置错误码
public interface ErrorInterface {
int getCode();
String getMsg();
}
public enum ErrorCodeMsg implements ErrorInterface {
SUCCESS(0, "操作成功"),
FAIL(-1, "操作失败");
private final int code;
private final String msg;
ErrorCodeMsg(int code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public int getCode() {
return code;
}
@Override
public String getMsg() {
return msg;
}
}
@EqualsAndHashCode(callSuper = true)
@Data
public class ApiException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
ErrorInterface error;
public ApiException(Throwable e, ErrorInterface error) {
}
public ApiException(ErrorInterface error) {
}
}
- 配置拦截器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionInterceptor {
/**
* 业务异常
*/
@ExceptionHandler(ApiException.class)
public ResponseDTO<?> handleServiceException(ApiException e) {
log.error(e.getMessage(), e);
return ResponseDTO.fail(e);
}
}
- 配置yml
token:
header: Authorization
secret: V1ckAluCnzBH2vDfNH1ZZVyi/YiuVCTX+SKUUv30e3c=
autoRefreshTime: 12