新版本SpringSercurity配置AuthenticationManager
- WebSecurityConfigurerAdapter在新版本已经过时,不能再通过重写方法来获取AuthenticationManager对象
- 新版本中直接传参数new出来个AuthenticationManager对象即可
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Resource
private UserDetailsService userDetailsService; //实现好UserDetailsService
@Bean
public AuthenticationManager authenticationManager() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return new ProviderManager(authenticationProvider);
}
}
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private SchoolManagerService schoolManagerService;
@Resource
private RoleService roleService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<SchoolManager> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
SchoolManager schoolManager = schoolManagerService.getOne(queryWrapper);
ThrowUtils.throwIf(schoolManager == null, ErrorCode.PARAMS_ERROR, "用户名不存在");
LoginUser loginUser = new LoginUser();
BeanUtils.copyProperties(schoolManager, loginUser);
//设置权限列表
List<String> permissionList = roleService.getPermissionByRoleId(schoolManager.getRoleId());
loginUser.setAuthorities(permissionList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
return loginUser;
}
}
新版本SpringSercurity设置白名单
- 这里形参的http会报错,不管他就行
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 设置白名单,注意不要加/api
.antMatchers("/doc.html",
"/favicon.ico",
"/v2/api-docs",
"/swagger-resources/**",
"/webjars/**", //以上是放行静态资源,以便knife4j正常运行
"/manager/login")
.anonymous()
//除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
//将session身份验证过滤器放到UsernamePasswordAuthenticationFilter之前
http.addFilterBefore(sessionAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
集成了SpringSercurity后的登陆校验过滤器
//这个过滤器的作用就是在验证账号密码是否正确前,确定是否需要重新认证,如果为空就说明需要重新认证
//不为空就标记为已认证,不需要再认证了
@Component
public class SessionAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpSession session = request.getSession();
LoginUser loginUser = (LoginUser)session.getAttribute(ManagerConstant.USER_LOGIN);
//如果loginManager为空,有可能是用户登录态过期
//那么这次请求有可能是登陆请求
if (loginUser == null) {
//放行,看看后面是否可以通过登陆时验证账号密码的过滤器
filterChain.doFilter(request, response);
return;
}
//调用三个参数的构造器是因为这个构造器会设置super.setAuthenticated(true);表示本次请求已进行身份验证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
//存入SecurityContextHolder容器中,后面过滤器会从这个容器中获取user对象
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request,response);
}
}
捕获并处理SpringSercurity中校验和授权的异常并处理
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
//捕获认证时异常
@ExceptionHandler(AuthenticationException.class)
public BaseResponse<?> authenticationExceptionHandler(AuthenticationException e) {
log.error("AuthenticationServiceException", e);
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, e.getMessage());
}
//捕获授权时异常
@ExceptionHandler(AccessDeniedException.class)
public BaseResponse<?> accessDeniedExceptionHandler(AccessDeniedException e) {
log.error("AccessDeniedException", e);
return ResultUtils.error(ErrorCode.FORBIDDEN_ERROR, e.getMessage());
}
}
登录态存储
-
有状态应用:cookie+session+spring session redis
- 如果有状态应用使用 JWT+Redis 来存储用户登陆态,JWT完全可以用一个UUID来代替
- spring session redis是一个分布式Session,只需要引入依赖,然后正常使用ServletAPI即可,他会自动将Session信息存到Redis中
-
无状态应用:JWT
Session过期时间
spring:
servlet:
session:
# 开启分布式 session(须先配置 Redis)
store-type: redis
# 30 天过期 2592000 单位秒
timeout: 1
server:
servlet:
session:
#非分布式session过期时间单位秒
timeout: 1800
数据校验
- 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- 加好注解
@Data
public class LoginManagerRequest implements Serializable {
private static final long serialVersionUID = 2647726180879097414L;
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "密码不能为空")
private String userPassword;
}
- 在Controller中加好@Vaild注解
@PostMapping("/login")
public BaseResponse<LoginManagerVO> login(@RequestBody @Valid LoginManagerRequest) {
//正常写代码
}
- 配置好全局异常处理器,只返回message里的错误信息,不然会报错一个乱七八糟的对象给前端
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
//捕获数据校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public BaseResponse<?> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
FieldError fieldError = e.getBindingResult().getFieldError();
if (fieldError != null) {
String errorMessage = fieldError.getDefaultMessage();
return ResultUtils.error(ErrorCode.PARAMS_ERROR, errorMessage);
} else {
// 如果没有找到字段错误,可以检查ObjectError中的错误
ObjectError objectError = e.getBindingResult().getGlobalError();
if (objectError != null) {
String errorMessage = objectError.getDefaultMessage();
return ResultUtils.error(ErrorCode.PARAMS_ERROR, errorMessage);
}
}
// 如果以上都没有匹配到错误信息,则返回通用错误信息
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "请求参数错误");
}
}