使用Spring Security鉴权及异常捕获
2023/7/6大约 3 分钟
使用Spring Security鉴权及异常捕获
添加依赖 Spring Security 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>过滤器配置
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// permitAll表示允许未登录的状态下访问
// denyAll表示未登录的状态下禁止访问
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/resources/**", "/user/**").permitAll()
.requestMatchers("/user/**").permitAll()
.requestMatchers("/error").permitAll()
// 其他请求都需要校验登录状态
.anyRequest().authenticated()
// 关闭csrf,否则用户未登录时无法发送post请求
).csrf(AbstractHttpConfigurer::disable).exceptionHandling(authorize -> authorize.authenticationEntryPoint(customAuthenticationEntryPoint()));
return http.build();
}
@Bean
public AuthenticationEntryPoint customAuthenticationEntryPoint() {
return new CustomAuthenticationEntryPoint();
}
}Security登录相关工具类
import com.caigouzi1.webServer.entity.User;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
public class SpringSecurityUtil {
public static void login(User user) {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
// 构造用户认证信息
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, user.getPassword(), null);
// 将认证信息存储在 SecurityContextHolder 中
SecurityContextHolder.getContext().setAuthentication(token);
// 生成session下发给浏览器
request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
}
public static User getCurrentUser() {
// 从SecurityContextHolder中取出保存的用户登录信息
var user = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// 当用户未授权是抛出异常信息
if (!SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) {
throw new AuthorizationError("用户未登录");
}
return (User) user;
}
public static boolean isLogin() {
// 判断用户是否已登录
return Objects.nonNull(SecurityContextHolder.getContext().getAuthentication()) && !(SecurityContextHolder.getContext().getAuthentication() instanceof AnonymousAuthenticationToken);
}
public static void logout() {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
// 从SecurityContextHolder设置为空
SecurityContextHolder.getContext().setAuthentication(null);
// Session设置失效
request.getSession().invalidate();
}
}鉴权相关错误信息自定义处理
自定义AuthenticationEntryPoint实现报错页面自定义,参考文档
import com.caigouzi1.webServer.model.vo.ResultVO;
import com.caigouzi1.webServer.util.SpringSecurityUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import java.io.IOException;
import java.io.PrintWriter;
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
int statusCode = HttpStatus.FORBIDDEN.value();
ResultVO<Object> body = ResultVO.fail("用户权限不足");
if (!SpringSecurityUtil.isLogin()) {
statusCode = HttpStatus.UNAUTHORIZED.value();
body.setMessage("用户未登录");
}
ObjectMapper objectMapper = new ObjectMapper();
response.setContentType("application/json;charset=UTF-8");
response.setStatus(statusCode);
PrintWriter out = response.getWriter();
out.write(objectMapper.writeValueAsString(body));
out.flush();
out.close();
}
}使用Spring Session将session信息存储在Redis中
添加依赖
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>配置创建SessionConfig类
@Configuration(proxyBeanMethods = false) @EnableRedisHttpSession(redisNamespace = "spring:session:order") public class SessionConfig implements BeanClassLoaderAware { private ClassLoader loader; @Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { return new GenericJackson2JsonRedisSerializer(objectMapper()); } /** * Customized {@link ObjectMapper} to add mix-in for class that doesn't have default * constructors * * @return the {@link ObjectMapper} to use */ private ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModules(SecurityJackson2Modules.getModules(this.loader)); return mapper; } /* * @see * org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang * .ClassLoader) */ @Override public void setBeanClassLoader(ClassLoader classLoader) { this.loader = classLoader; } }User类添加注解支持JSON转换
@Data @TableName("user") @JsonDeserialize @JsonSerialize public class User { @TableId(type = IdType.AUTO) private Integer id; private String username; private String password; private String phone; private Byte disable; private String salt; private LocalDateTime lastLoginTime; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime; }
全局异常捕获
创建用户为授权的异常类
public class AuthorizationError extends RuntimeException { public AuthorizationError(String msg) { super(msg); } }创建异常捕获控制器
import com.caigouzi1.webServer.exception.AuthorizationError; import com.caigouzi1.webServer.model.vo.ResultVO; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class ErrorController { @ExceptionHandler(AuthorizationError.class) @ResponseBody public ResponseEntity<ResultVO<Object>> handlerAuthorizationError(Exception ex) { // 返回数据并将http状态码设置为401 return new ResponseEntity<>(ResultVO.fail(ex.getMessage()), HttpStatus.UNAUTHORIZED); } }