最近发现新版本的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());
}
}