内存认证
了解即可 真实开发中不会使用
UserDetailsService
实现UserDetailsService可以自定义认证逻辑
在实际开发中,认证逻辑是需要自定义控制的。将UserDetailService接口的实现类放入到Spring容器即可自定义认证逻辑。InMemoryUserDetailsManager 就是 UserDetailsService 接口的一个实现类,它将登录页传来的用户名密码和内存中用户名密码做匹配认证。当然我们也可以自定义 UserDetailsService 接口的实现类。
UserDetailsService的实现类必须重写loadUserByUsername方法,该方法定义了具体的认证逻辑,参数username是前端传来的用户名,我们需要根据传来的用户名查询到该用户(一般是从数据库),并将查询到的用户封装成一个UserDetails对象,该对象是Spring Security 提供的用户对象,包含用户名,密码,权限。Spring Security会根据UserDetails对象的密码和客户端提供的密码进行比较,相同的认证通过,不相同则认证失败。
数据库认证
创建UserDetailsService的实现类,编写自定义认证逻辑
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
// 自定义认证逻辑
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1.构造查询条件
QueryWrapper<Users> wrapper = new QueryWrapper<Users().eq("username",username);
// 2.查询用户
Users users = usersMapper.selectOne(wrapper);
// 3.封装为UserDetails对象
UserDetails userDetails = User
.withUsername(users.getUsername())
.password(users.getPassword())
.authorities("admin")
.build();
// 4.返回封装好的UserDetails对象
return userDetails;
}
}
PasswordEncoder
实际开发中会在数据库存放加密后的密码,而不是原密码,所以需要使用密码解析器才能将加密密码和明文密码做对比。
Spring Security中的密码解析器是 PasswordEncoder 。
Spring Security官方推荐的密码解析器是 BCryptPasswordEncoder 。
@SpringBootTest
public class PasswordEncoderTest {
@Test
public void testBCryptPasswordEncoder(){
//创建解析器
PasswordEncoder encoder = new BCryptPasswordEncoder();
//密码加密
String password = encoder.encode("baizhan");
System.out.println("加密后:"+password);
//密码校验
/**
* 参数1:明文密码
* 参数2:加密密码
* 返回值:是否校验成功
*/
boolean result = encoder.matches("baizhan","$2a$10$/MImcrpDO21HAP2amayhme8j2SM0YM50/WO8YBH.NC1hEGGSU9ByO");
System.out.println(result);
}
}
在开发中,我们将 BCryptPasswordEncoder 的实例放入Spring容器即可,并且在用户注册完成后,将密码加密再保存到数据库。
//密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
自定义登陆页面
在Spring Security配置类自定义登录页面
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
//Spring Security配置
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义表单登录
http.formLogin()
.loginPage("/login.html") //自定义登录页面
.usernameParameter("username")// 表单中的用户名项
.passwordParameter("password")// 表单中的密码项
.loginProcessingUrl("/login")// 登录路径,表单向该路径提交,提交后自动执行UserDetailsService的方法
.successForwardUrl("/main")//登录成功后跳转的路径
.failureForwardUrl("/fail");//登录失败后跳转的路径
// 需要认证的资源
http.authorizeRequests().antMatchers("/login.html").permitAll() //登录页不需要认证
.anyRequest().authenticated();//其余所有请求都需要认证
//关闭csrf防护
http.csrf().disable();
}
@Override
public void configure(WebSecurity web)throws Exception {
// 静态资源放行
web.ignoring().antMatchers("/css/**");
}
}
- CSRF防护:
CSRF:跨站请求伪造,通过伪造用户请求访问受信任的站点从而进行非法请求访问,是一种攻击手段。 Spring Security为了防止CSRF攻击,默认开启了CSRF防护,这限制了除了GET请求以外的大多数方法。我们要想正常使用SpringSecurity需要突破CSRF防护。
解决方法一:关闭CSRF防护: http.csrf().disable();
解决方法二:突破CSRF防护:
CSRF为了保证不是其他第三方网站访问,要求访问时携带参数名为_csrf值为令牌,令牌在服务端产生,如果携带的令牌和服务端的令牌匹配成功,则正常访问。
<!-- 在表单中添加令牌隐藏域 -->
<input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
会话管理
用户认证通过后,有时我们需要获取用户信息,比如在网站顶部显示:欢迎您,XXX。Spring Security将用户信息保存在会话中,并提供会话管理,我们可以从 SecurityContext 对象中获取用户信息,SecurityContext 对象与当前线程进行绑定。
@RestController
public class MyController {
// 获取当前登录用户名
@RequestMapping("/users/username")
public String getUsername(){
// 1.获取会话对象
SecurityContext context = SecurityContextHolder.getContext();
// 2.获取认证对象
Authentication authentication = context.getAuthentication();
// 3.获取登录用户信息
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return userDetails.getUsername();
}
}
认证成功后的处理方式
登录成功后,如果除了跳转页面还需要执行一些自定义代码时,如:统计访问量,推送消息等操作时,可以自定义登录成功处理器。
- 自定义登录成功处理器
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");
}
}
- 配置登录成功处理器
.successHandler(newMyLoginSuccessHandler()) //登录成功处理器
认证失败之后的处理方式
登录失败后,如果除了跳转页面还需要执行一些自定义代码时,如:统计失败次数,记录日志等,可以自定义登录失败处理器。
- 自定义失败处理器
public class MyLoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
System.out.println("记录失败日志...");
response.sendRedirect("/fail");
}
}
- 配置登录失败处理器 详见文档
退出登录
在系统中一般都有退出登录的操作。退出登录后,Spring Security 进行了以下操作:
- 清除认证状态
- 销毁HttpSession对象
- 跳转到登录页面 配置退出登录的路径
// 退出登录配置和推出后跳转的路径
http.logout()
.logoutUrl("/logout") // 退出登录路径
.logoutSuccessUrl("/login.html") // 退出登录后跳转的路径
.clearAuthentication(true) //清除认证状态,默认为true
.invalidateHttpSession(true); // 销毁HttpSession对象,默认为true
退出成功处理器
见文档
记住我功能
见文档