最近发现新版本的springsecurity的配置和以前的版本有所区别,这里做一下总结。
1、首先,看一下springsecurity的核心配置类

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig

配置类的开头需要加入上述两个注解

2、这是核心配置方法

@Autowired
private LoginSuccessHandler loginSuccessHandler;

@Autowired
private LoginFailureHandler loginFailureHandler;

@Autowired
private CustomLogoutHandler logoutHandler;

@Autowired
private UnauthorizedEntryPoint authorizedEntryPoint;

@SneakyThrows
@Bean
public SecurityFilterChain filterChain(HttpSecurity http){
    http.csrf().disable()
            .authorizeRequests().anyRequest().authenticated()
            .and()
            .formLogin().loginPage("/login").usernameParameter("userName").passwordParameter("password").permitAll()
            .successHandler(loginSuccessHandler).failureHandler(loginFailureHandler)
            .and()
            .logout().deleteCookies("JSESSIONID").logoutUrl("/logout")
            .addLogoutHandler(logoutHandler)
            .and()
            .exceptionHandling()
            .authenticationEntryPoint(authorizedEntryPoint)
            .and()
            .cors().configurationSource(corsConfigurationSource());
    return http.build();
}

private CorsConfigurationSource corsConfigurationSource(){
    UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource();
    CorsConfiguration corsConfiguration=new CorsConfiguration();
    corsConfiguration.addAllowedHeader("*");
    corsConfiguration.addAllowedMethod("*");
    corsConfiguration.addAllowedOrigin("*");
    source.registerCorsConfiguration("/**",corsConfiguration);
    return source;
}

其中,filterChain方法里面定义了最核心的配置信息,包括登录接口的请求路径,用户名、密码的参数名,登录成功的处理类的对象,登录失败的处理类对象,退出的接口路径,退出时删除cookie,退出的处理类对象,用户未登录的处理类对象,还包括处理跨域的配置等。

3、核心配置类还包括如下配置

@Bean
public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

@Bean
public HttpSessionIdResolver sessionIdResolver(){
    return new  HeaderHttpSessionIdResolver("sessionid");
}

配置了密码加密所需的bean。与springsession整合,配置了获取sessionid使用的请求头。

4、定义一个类,继承UserDetails,用于保存和获取用户信息

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SecurityUser implements UserDetails {

    private Integer userId;

    private String userName;

    private String password;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return userName;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

5、定义一个类继承UserDetailsService,重写loadUserByUsername方法

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IUserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userService.getUserByUserName(username);
        if (user==null){
            throw new UsernameNotFoundException("用户名不存在!");
        }
        SecurityUser securityUser=new SecurityUser(user.getUserId(),user.getUserName(),user.getPassword());
        return securityUser;
    }
}

这里面的逻辑就是,根据用户名查询用户信息,如果结果为空,就抛异常,如果不为空,就把用户信息保存到securityUser里面,并返回这个对象。登录的时候,会调用这个方法。注意,这个类需要配置成bean才会被调用。

6、登录成功的处理类,需要继承AuthenticationSuccessHandler接口,并重写onAuthenticationSuccess方法

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private IAuthorityService authorityService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
        List<Authority> authorityList =authorityService.getByUserId(securityUser.getUserId());
        List<GrantedAuthority> grantedAuthorityList = authorityList.stream()
                .map(authority -> new SimpleGrantedAuthority(authority.getCode()))
                .collect(Collectors.toList());
        SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(securityUser,securityUser.getPassword(),grantedAuthorityList));
        String sessionid = request.getSession().getId();
        ResponseUtil.out(response, Result.success(sessionid));
    }
}

这里面的逻辑是,获取当前已认证的用户信息,保存在securityUser对象里面。根据用户id查询用户关联的接口权限码,保存到SimpleGrantedAuthority对象里面,然后使用查询到的权限集合,对当前用户进行授权。后面要判断当前用户是否有权限访问对应的接口,用户权限就是从这里来的。下一步,就是获取当前的sessionid,作为响应数据传给前端,前端再将sessionid做本地存储,后面发请求需要在请求头带上这个sessionid。

7、登录失败的处理类,需要继承AuthenticationFailureHandler接口,重写onAuthenticationFailure方法

@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        ResponseUtil.out(response, Result.fail("用户名或密码错误!"));
    }
}

登录失败后,就响应失败的json数据

8、退出的处理类,需要继承LogoutHandler接口,重写logout方法

@Component
public class CustomLogoutHandler implements LogoutHandler {
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        ResponseUtil.out(response, Result.success());
    }
}

这里分享一个工具类,用于通过response对象,响应json数据

public class ResponseUtil {

    @SneakyThrows
    public static void out(HttpServletResponse response, Result result){
        ObjectMapper mapper=new ObjectMapper();
        response.setStatus(HttpStatus.OK.value());
        response.setContentType("text/json;charset=UTF-8");
        mapper.writeValue(response.getWriter(),result);
    }
}

9、用户未登录导致授权失败的处理类

@Component
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        ResponseUtil.out(response, new Result(ResultCode.UNAUTHORIZED,"你还未登录"));
    }
}

10、定义全局异常处理类,处理没有权限、用户名不存在等异常信息,并以json格式向前端响应这些信息

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(AuthenticationException.class)
    public Result handleAuthenticationException(AuthenticationException e){
        return Result.fail(e.getMessage());
    }

    @ExceptionHandler(AccessDeniedException.class)
    public Result handleAccessDeniedException(AccessDeniedException e){
        return Result.fail("没有权限");
    }
}

11、在接口上添加权限注解@PreAuthorize

@PostMapping
@PreAuthorize("hasAuthority('user:save')")
public Result save(@RequestBody User user){
 try {
     userService.saveUser(user);
     return Result.success();
 }catch (AdminException e){
     return Result.fail(e.getMessage());
 }
}