SpringSecurity(基本使用)
第一部分(基本使用)
第一、了解SpringSecurity
SpringSecurity是一个开源的、可用于分布式系统的权限控制框架,它的核心功能有两个:认证和授权
所谓的认证,其实就是常见的短信认证、账户密码认证、语音认证等。
所谓的授权,其实就是常见的认证之后需要对该用户权限进行查询,查看该用户的权限,根据权限实现对不同资源的控制。
第二、基于内存的认证策略
基于内存的认证逻辑是SpringSecurity的第一种认证方式,他的核心思想就是自定义认证对象,将其封装为UsersDetails对象,然后放在内存中,执行登录逻辑的时候,只需要进行比对即可。- 内存认证逻辑
@Configuration public class SpringSecurityConfig { /** * 内存的认证逻辑 * @return */ @Bean public UserDetailsService userDetailsService() { // 1、创建一个内存认证对象 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); // 2、创建内存对象 UserDetails user1 = User.withUsername("huidou").password("huidou").authorities("admin").build(); UserDetails user2 = User.withUsername("liziying").password("liziying").authorities("admin").build(); // 3、将内存认证对象放在内存中 manager.createUser(user1); manager.createUser(user2); // 4、返回认证逻辑 return manager; } }同时还要编写密码编码器逻辑(SpringSecurity框架使用了密码加密的方式避免了黑客的密码比对攻击)
/** * 创建一个不编码的密码编码器 * @return */ @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); }测试结果:
成功登录!
但是这样的方式,也只能用于测试认证功能,在真实项目中是不可以的,所以,就可以使用基于数据库的方式实现用户的认证逻辑。
第三、基于数据库的认证策略
基于数据库的认证逻辑的原理是:会发现,在这里进行登录逻辑的认证的时候,它并没有通过controller层,直接执行自己的登录逻辑(也就是service层),是因为它的认证逻辑实现了UsersDetailsService接口实现类loadUserByUsername方法。
项目的登录认证逻辑只能存在一个,也就是如何实现UsersDetailsService接口的话,就需要关闭内存逻辑。
- 登陆逻辑代码
/** * @Description: 用户登录的认证逻辑 * @Author: huidou 惠豆 * @CreateTime: 2022/6/23 10:46 */ @Service public class UserServiceImpl implements UserDetailsService { @Autowired private UsersMapper usersMapper; /** * 根据前端传递的用户名进行查找,但是这样的话,就意味着数据库的用户名必须unique唯一 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1、封装查找条件 QueryWrapper<Users> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("username",username); // 2、查询对象信息 Users users = usersMapper.selectOne(queryWrapper); // 3、判断用户信息是否存在 if (users == null) { return null; } // 4、封装为UsersDetails对象 UserDetails userDetails = User.withUsername(users.getUsername()).password(users.getPassword()).authorities("admin").build(); return userDetails; } }
第四、密码编码器与解析器
- PasswordEncoder密码编码器
@Test public void PasswordEncoder() { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String salt = "salt"; String passwordEncoder = bCryptPasswordEncoder.encode("huidou" + salt); System.out.println(passwordEncoder); } 该方式也是可以使用加盐操作实现对密码复杂化,降低攻击成功机率。huidou加密之后的密码密文就是:$2a$10$NBOHiODtPZE54MXvhUIjn.mJycO/Pd/encCHb8oyNuzK3m4719ODS
- PasswordDecoder密码解析器
// 第一个参数是:原密码+盐值 第二个参数是:加密之后的密文 该方***进行对密文进行比对,看是否一致。 boolean matches = bCryptPasswordEncoder.matches("huidou" + salt, passwordEncoder); System.out.println(matches);
第五、自定义登录页面
实现自定义登录页面的原因就是因为在真实项目中,是不会使用SpringSecurity所提供的默认的登陆页面,所以这个时候就需要对SpringSecurity进行自定义配置。
自定义登陆页面,需要考虑的比较多,例如:
-
表单里的登录请求
-
表达里的登录请求参数
-
表单登录成功跳转的页面
-
表单登录失败的请求
-
表单登录成功的请求
-
对必要的请求进行放行
-
对静态资源进行放行
-
关闭csrf跨域防护
/** * 对请求资源进行控制 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); }
/** * 对web静态资源进行放行 * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { super.configure(web); }
/** * 对请求资源进行控制 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { // 对表单请求进行控制 http.formLogin() .usernameParameter("username") // 登录参数1 .passwordParameter("password") // 登录参数2 .loginPage("/login.html") // 登录页面 .successForwardUrl("/index") // 登陆成功跳转请求 .failureUrl("/fail") // 登录失败跳转请求 .loginProcessingUrl("/login"); // 表单提交路径 // 对请求路径进行控制 http.authorizeRequests() .antMatchers("/login.html").permitAll() // 对登录请求进行放行 .antMatchers("/fail").permitAll() // 对登录失败请求进行放行 .anyRequest().authenticated(); // 其余的请求都进行控制 // 关闭csrf跨域防护 http.csrf().disable(); } /** * 对web静态资源进行放行 * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring() .antMatchers("/css/**") .antMatchers("/js/**") .antMatchers("/img/**"); }Controller页面跳转控制器
/** * @Description: 页面跳转控制器 * @Author: huidou 惠豆 * @CreateTime: 2022/6/23 15:22 */ @Controller public class CommonController { @RequestMapping("/{page}") public String page(@PathVariable String page) { return page; } }测试结果:
第六、登录成功之后处理器
前面已经成功的配置了自定义登录逻辑,下面就可以对登陆成功之后做一个自定义的处理器,该处理器的作用就是用于实现登录之后做一些日志的记录等。
想要做一个登陆成功的处理器,需要自定义一个处理器类,并且实现一个接口AuthenticationSuccessHandler,重写其中的方法onAuthenticationSuccess
/** * @Description: 认证成功的处理器 * @Author: huidou 惠豆 * @CreateTime: 2022/6/23 15:30 */ @Slf4j public class MyLoginSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 做一些操作 UserDetails principal = (UserDetails) authentication.getPrincipal(); log.info("登录成功之后做一些日志的打印,登陆人信息: " + principal.getUsername()); // 登录成功之后的重定向 response.sendRedirect("/index"); } }修改SpringSecurity的http请求配置
// .successForwardUrl("/index") // 登陆成功跳转请求(注释起来) .successHandler(new MyLoginSuccessHandler()) // 登录成功之后的处理器
测试结果:
进行登录成功时候,在控制台查看输出日志信息:
登录成功之后做一些日志的打印,登陆人信息: baizhan第七、登录失败时候处理器
登陆失败处理器就是用于实现登陆失败之后,做一些分析处理、日志记录等
想要做一个登陆成功的处理器,需要自定义一个处理器类,并且实现一个接口AuthenticationSuccessHandler,重写其中的方法AuthenticationFailureHandler
/** * @Description: 登陆失败处理器 * @Author: huidou 惠豆 * @CreateTime: 2022/6/23 15:54 */ @Slf4j public class MyLoginFailureHandler implements AuthenticationFailureHandler { // 自定义登陆失败处理器 @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { log.info("登录失败,记录日志,分析错误原因"); // 登录失败重定向 response.sendRedirect("/fail"); } }修改SpringSecurity的http请求配置
// .failureUrl("/fail") // 登录失败跳转请求
测试结果:
进行登录失败时候,在控制台查看输出日志信息:
第八、SpringSecurity会话管理
实现SpringSecurity会话,这个功能的主要作用就是:当我在登录完成之后,就可以在页面上显示我的个人登录信息。/** * @Description: 会话管理 * @Author: huidou 惠豆 * @CreateTime: 2022/6/23 16:12 */ @RestController public class MyController { /** * 实现会话管理,实现获得用户信息 * @return */ @RequestMapping("/user") public String getUsername() { SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); UserDetails userDetails = (UserDetails) authentication.getPrincipal(); return userDetails.getUsername(); } }测试结果:
第九、退出登录实现
退出登录功能实现,用于实现在SpringSecurity安全框架中,实现安全的退出,也就是对于自己的登录会话状态进行安全的销毁删除,同时退出请求必须是 /logout- 修改index页面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"> <head> <meta charset="UTF-8"> <title>首页页面</title> </head> <body> 首页页面 <a href="/logout">立即退出</a> </body> </html>
第十、退出登录成功之后的处理器
实现退出登录之后的处理器的主要目的是:可以在退出邓丽之前做一些自定义的资源释放等。/** * @Description: 成功退出处理器 * @Author: huidou 惠豆 * @CreateTime: 2022/6/23 16:24 */ @Slf4j public class MyLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { log.info("成功退出处理器"); log.info("在成功退出之前,做一些资源的释放操作"); response.sendRedirect("/login.html"); } }
- 修改SpringSecurity配置
// 配置退出登录的逻辑 http.logout() .logoutSuccessHandler(new MyLogoutSuccessHandler());
测试结果:
点击立即退出之后,后台退出处理器就会进行逻辑执行。
第十一、SpringSecuirty实现Remember-Me功能
SpringSecurity的Remember-Me功能就是用于项目第一次登录之后,可以保存自己的session时间,以便下一次规定的时间以内能直接免密登录。
- Remember-Me的配置类
/** * @Description: 记住我功能配置 * @Author: huidou 惠豆 * @CreateTime: 2022/6/23 16:51 */ @Configuration public class RememberMConfig { @Autowired private DataSource dataSource; /** * 实现记住我配置 * @return */ @Bean public PersistentTokenRepository getPersistentTokenRepository() { // 实例化一个JdbcTokenRepositoryImpl持久层对象 JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); // 将数据源database放在持久层对象中 jdbcTokenRepository.setDataSource(dataSource); // 项目第一次启动的时候,需要将其创建表,非第一次的话,就需要进行关闭,否则报错。 jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; } }
- 修改SpringSecurity配置
@Autowired private PersistentTokenRepository repository; @Autowired private UserDetailsService userDetailsService; // 配置记住我功能 http.rememberMe() .tokenValiditySeconds(60) // session过期时间,单位是:秒 .tokenRepository(repository) // 持久层对象 .userDetailsService(userDetailsService); // 用户认证逻辑
- 修改前端页面login
<!doctype html> <html lang="zh" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>登录</title> <link href="../static/css/styles.css" rel="stylesheet" > </head> <body> <div class="htmleaf-container"> <div class="wrapper"> <div class="container"> <h1>Welcome</h1> <form class="form" action="/login" method="post"> <!-- 设置一个隐藏域 --> <!-- <input type="hidden" name="_csrf" th:value="${_csrf.token}" th:if="${_csrf}">--> <input type="text" placeholder="用户名" name="username"> <input type="password" placeholder="密码" name="password"> <input type="checkbox" name="remember-me" value="true">记住我 <button type="submit" id="login-button">登录</button> </form> </div> </div> </div> </body> </html>需要设置一个复选框checkbox,name的属性值必须是:remember-me value是:true
测试结果:
登录成功之后,关闭浏览器,一份钟以内免密登录,一分钟以外需要密码登录。