1. 概述
-
springSecurity是基于Spring生态圈,用于提供 安全访问控制 的解决方案框架。SpringSecurity的安全管理有两个重要概念:认证(Authentication)和 授权(Authorization);
-
springBoot对SpringSecurity进行了整合支持,并提供了通用的自动化配置,从而实现了SpringSecurity中包含的多数功能;
2. 认证
springSecurity登录认证主要涉及两个重要接口,UserDetailService 和 UserDetails;
-
UserDetailService接口主要定义了一个方法
loadUserByUsername(String userName)
用于完成用户信息的查询。userName就是用户登录名,登录认证时,需要自定义一个实现类来实现UserDetailService接口,完成数据库查询,该接口返回UserDetails接口的对象; -
UserDetails接口主要用来封装认证成功的用户信息,需要自定义一个用户对象来实现UserDetails接口,即UserDetailService返回的用户信息;
1)认证步骤
1. 添加依赖
:spring-boot-starter-security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.自定义UserDetails类
:当实体对象字段不满足要求时需自定义UserDetails接口的实现类;
@Data
@TableName("sys_user")
public class User implements Serializable, UserDetails {
//省略原有的属性......
/**
* 帐户是否过期(1 未过期,0已过期)
*/
private boolean isAccountNonExpired = true;
/**
* 帐户是否被锁定(1 未过期,0已过期)
*/
private boolean isAccountNonLocked = true;
/**
* 密码是否过期(1 未过期,0已过期)
*/
private boolean isCredentialsNonExpired = true;
/**
* 帐户是否可用(1 可用,0 删除用户)
*/
private boolean isEnabled = true;
/**
* 权限列表
*/
@TableField(exist = false)
Collection<? extends GrantedAuthority> authorities;
}
3.自定义UserDetailsService类
:主要用于从数据库查询用户信息;
1) 编写UserService接口
public interface UserService extends IService<User> {
/**
* 根据用户名查询用户信息
*/
User findUserByUserName(String userName);
}
2)编写UserService接口实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
/**
* 根据用户名查询用户信息
*/
@Override
public User findUserByUserName(String userName) {
//创建条件构造器
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
//用户名
queryWrapper.eq("username",userName);
//返回查询记录
return baseMapper.selectOne(queryWrapper);
}
}
3)自定义UserDetailsService类
import com.manong.entity.User;
import com.manong.service.UserService;
......
@Component
public class CustomerUserDetailsService implements UserDetailsService {
@Resource
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//调用根据用户名查询用户信息的方法
User user = userService.findUserByUserName(username);
//如果对象为空,则认证失败
if (user == null) {
throw new UsernameNotFoundException("用户名或密码错误!");
}
return user;
}
}
4.创建登录认证成功处理器
:认证成功后需要返回JSON数据、菜单权限等;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.manong.entity.SysUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
......
/**
* 登录认证成功处理器类
*/
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//设置客户端的响应的内容类型
response.setContentType("application/json;charset=UTF-8");
//获取当登录用户信息
SysUser user = (SysUser) authentication.getPrincipal();
//消除循环引用
String result = JSON.toJSONString(user, SerializerFeature.DisableCircularReferenceDetect);
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
5.创建登录认证失败处理器
:认证失败后需要返回JSON数据给前端判断;
/**
* 用户认证失败处理类
*/
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
//设置客户端响应编码格式
response.setContentType("application/json;charset=UTF-8");
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
String message = null; //提示信息
int code = 500; //错误编码
//判断异常类型
if(exception instanceof AccountExpiredException){
message = "账户过期,登录失败!";
}else if(exception instanceof BadCredentialsException){
message = "用户名或密码错误,登录失败!";
}else if(exception instanceof CredentialsExpiredException){
message = "密码过期,登录失败!";
}else if(exception instanceof DisabledException){
message = "账户被禁用,登录失败!";
}else if(exception instanceof LockedException){
message = "账户被锁,登录失败!";
}else if(exception instanceof InternalAuthenticationServiceException){
message = "账户不存在,登录失败!";
}else{
message = "登录失败!";
}
//将错误信息转换成JSON
String result = JSON.toJSONString(Result.error().code(code).message(message));
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
6. 创建匿名用户访问无权限时处理器
:匿名用户访问时,需要返回JSON数据;
/**
* 匿名用户访问资源处理器
*/
@Component
public class AnonymousAuthenticationHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
//设置客户端的响应的内容类型
response.setContentType("application/json;charset=UTF-8");
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
//消除循环引用
String result = JSON.toJSONString(
Result.error().code(600).message("匿名用户无权限访问!"),
SerializerFeature.DisableCircularReferenceDetect
);
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
7.创建认证用户访问无权限时处理器
:无权限访问时,需要返回JSON数据;
/**
* 认证用户访问无权限资源时处理器
*/
@Component
public class CustomerAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
//设置客户端的响应的内容类型
response.setContentType("application/json;charset=UTF-8");
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
//消除循环引用
String result = JSON.toJSONString(
Result.error().code(700).message("无权限访问,请联系管理员!"),
SerializerFeature.DisableCircularReferenceDetect
);
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
8.配置SpringSecurity配置类
:把上面自定义的处理器交给SpringSecurity;
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private CustomerUserDetailsService customerUserDetailsService;
@Resource
private LoginSuccessHandler loginSuccessHandler;
@Resource
private LoginFailureHandler loginFailureHandler;
@Resource
private AnonymousAuthenticationHandler anonymousAuthenticationHandler;
@Resource
private CustomerAccessDeniedHandler customerAccessDeniedHandler;
/**
* 注入加密处理类
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 认证
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//登录前进行过滤
http.formLogin()
.loginProcessingUrl("/api/user/login")
// 设置登录验证成功或失败后的的跳转地址
.successHandler(loginSuccessHandler).failureHandler(loginFailureHandler)
// 禁用csrf防御机制
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/user/login").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(anonymousAuthenticationHandler)
.accessDeniedHandler(customerAccessDeniedHandler)
.and().cors();//开启跨域配置
}
/**
* 配置认证处理器
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customerUserDetailsService).passwordEncoder(passwordEnco der());
}
}