Spring Security(安全校验)
1. 概述
Spring Security是Spring项目组提供的安全服务框架,核心功能包括认证和授权.为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作.
在如今开发模式中,Spring Security已经成为Java程序员必备的一项技术,简化认证和授权开发的首选项,
其核心为认证和授权,我们先来了解一下何为认证和授权,理解其概念.
2. 认证
认证是指系统判断用户的身份是否合法,合法可继续访问,不合法则会拒绝访问.在生活中我们很常见认证的方式,例如人脸识别认证,指纹认证,用户名密码的登录,手机短信的登录等方式
认证的目的是保护系统的隐私数据和资源,用户的身份合法 才能访问资源.
注: SpringSecurity的依赖为(基于Spring Boot)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.1 内存认证
内存认证的概念,及用户的数据存放在内存当中,数据在代码中写死,项目启动便加载到内存当中.代码如下
@Configuration
public class SpringSecurityConfig{
@Bean
public UserDetailsService userDetailsService(){
//1.创建内存管理对象
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//2.创建权限对象
UserDetails user1 = User.withUsername("zhangsan").password("123").authorities("admin").build();
UserDetails user2 = User.withUsername("lisi").password("123").authorities("admin").build();
//3.封装到manager当中
manager.createUser(user1);
manager.createUser(user2);
return manager;
}
}
这种认证方式很明显,不灵活,代码写死,用户信息仅有代码所包含部分.这不符合我们的业务开发,应该连接于数据库.所以Spring Security提供了另一套配置.我们需要创建一个配置类去实现UsersDetailsService接口.(这里我使用的是Mybatis-Plus去操作数据库)
代码如下:
@Service
public class MyUserDetailService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.查询数据库,是否存在对象
Users users = usersMapper
.selectOne(new QueryWrapper<Users>().lambda()
.eq(Users::getUsername, username));
//2.判断用户是否为空
if(users == null){
//2.1.1 为空直接返回
return null;
}
//3.封装成userDetails对象
UserDetails userDetails = User.withUsername(users.getUsername())
.password(users.getPassword())
.authorities("admin")
.build();
//5.返回
return userDetails;
}
}
2.2 密码编码
为了数据的安全性,通常我们存储到数据库的密码是经过转码的,即密文存储,所以我们需要配置转码的配置类,校验时才能检查对应的密码是否一致
Spring Security提供了BCryptPasswordEncoder编码配置类,我们注入一下即可.
/**
* 密码解码配置
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
2.3 认证成功与失败的处理
当认证成功之后,我们可能要对用户做一些信息上的配置或者其他的调整,需要介入java代码,同理失败也得控制用户的信息,防止用户恶意破坏信息.
我们需要配置SecurityFilterChain,由名字也可知道这是经过SpringSecurity框架的执行链.我们可以在这配置我们的业务需求.代码如下(包含前端路径的可自行配置,这里我使用的是thymeleaf,在resource下创建templates目录,编写对应页面就可)
注: 别忘了配置第二步允许静态资源的访问
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
//1.配置登录表单
httpSecurity.formLogin(form ->{
form.loginPage("/login.html") //自定义登录页面
.loginProcessingUrl("/login") //登录路径,表单向该路径提交,提交后自动执行MyUserDetailService的方法
.usernameParameter("username") //表单中用户名项
.passwordParameter("password") //表单中的密码项
.successHandler(new MyLoginSuccessHandler()) //登录成功跳转页面
.failureHandler(new MyLoginFailHandler()); //登录失败跳转页面
});
//2.配置需要认证和不需要认证的资源
httpSecurity.authorizeHttpRequests(resp->{
resp.requestMatchers("/login.html","/fail.html").permitAll(); //不需要认证的资源
resp.requestMatchers("/css/*.css","/js/*.js","/img/**").permitAll(); //静态资源
resp.anyRequest().authenticated(); //其余所有请求都需要认证
});
//3.关闭csrf防护(若不关闭资源无法访问)
httpSecurity.csrf(csrf->{
csrf.disable();
});
}
认证成功的处理器
public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//拿到登录用户的信息
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
System.out.println("用户名:"+userDetails.getUsername());
System.out.println("登录之后的其余操作");
//重定向到主页
response.sendRedirect("/main");
}
}
认证失败的处理器
public class MyLoginFailHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
System.err.println(exception.getMessage());
System.out.println("登录失败");
response.sendRedirect("/fail.html");
}
}
2.4 退出登录
用户需要退出登录时,我们需要清除他的会话信息及授权信息.配置如下(依旧配置在上述配置类SpringSecurityConfig的方法当中)
//配置退出登录功能
httpSecurity.logout(out->{
// out.logoutUrl("/logout")
out.logoutSuccessHandler(new MyLoginLogOutSuccessHandler())
.invalidateHttpSession(true) //清除session记录,默认为true
.clearAuthentication(true); //清除授权记录,默认为true
});
2.5 记住我
在许多登录场景当中,往往会有记住我
这个选项.可以在一段时间内记住用户的登录信息,用户下次访问时便可以不再需要登录.我们可以思考一下怎么去做到这个事情.大家应该都接触过JWT令牌,对token字段我们并不陌生.我们可以在用户使用记住我这个功能时,生成一个Token令牌,保存到数据库当中,下次用户再来访问我们去数据库查询是否有这样一个Token即可.
SpringSecurity帮我们实现了这样一个功能,需要我们配置一下即可.其功能演示大概为:
首先我们得有这样一个存放token的表,利用SpringSecurity配置生成表(别忘了在配置文件中配置DataSource哦)
@Configuration
public class RememberMeConfig {
@Autowired
private DataSource dataSource;
//生成令牌
@Bean
public PersistentTokenRepository getPersistentTokenRepository(){
// 为Spring Security 自带的令牌控制器设置数据源
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
// 开启自动建表,第一次启动时需要,第二次就注释掉
// jdbcTokenRepository.setCreateTableOnStartup(true);
//返回
return jdbcTokenRepository;
}
}
注: 第一次启动运行时可以直接这样写,第二次启动记得把jdbcTokenRepository.setCreateTableOnStartup(true);给注释掉,若忘记啦也没有关系,控制台会报错(表已经存在),根据原因我们再来处理也可
再回到SpringSecurityConfig当中,将这个配置类生效
//注入这两个类
@Autowired
private PersistentTokenRepository persistentTokenRepository;
@Autowired
private MyUserDetailService myUserDetailService;
//配置记住我
httpSecurity.rememberMe(remember->{
remember.tokenRepository(persistentTokenRepository) //配置持久化token仓库
.userDetailsService(myUserDetailService) //配置权限处理对象,即认证逻辑对象
.tokenValiditySeconds(30); //配置token的保持时间,即多久以后失效
});