Spring Security认证

246 阅读3分钟

内存认证

了解即可 真实开发中不会使用

UserDetailsService

实现UserDetailsService可以自定义认证逻辑

在实际开发中,认证逻辑是需要自定义控制的。将UserDetailService接口的实现类放入到Spring容器即可自定义认证逻辑。InMemoryUserDetailsManager 就是 UserDetailsService 接口的一个实现类,它将登录页传来的用户名密码和内存中用户名密码做匹配认证。当然我们也可以自定义 UserDetailsService 接口的实现类。

UserDetailsService的实现类必须重写loadUserByUsername方法,该方法定义了具体的认证逻辑,参数username是前端传来的用户名,我们需要根据传来的用户名查询到该用户(一般是从数据库),并将查询到的用户封装成一个UserDetails对象,该对象是Spring Security 提供的用户对象,包含用户名,密码,权限。Spring Security会根据UserDetails对象的密码和客户端提供的密码进行比较,相同的认证通过,不相同则认证失败。

image.png

数据库认证

创建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

退出成功处理器

见文档

记住我功能

见文档