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();
    }
测试结果:


输入:huidou、huidou

成功登录!

但是这样的方式,也只能用于测试认证功能,在真实项目中是不可以的,所以,就可以使用基于数据库的方式实现用户的认证逻辑。

第三、基于数据库的认证策略

基于数据库的认证逻辑的原理是:


会发现,在这里进行登录逻辑的认证的时候,它并没有通过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跨域防护

实现SpringSecurity的配置,配置类需要继承WebSecurityConfigurerAdapter类,重写其中的两个方法分别是:
    /**
     * 对请求资源进行控制
     * @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

测试结果:

登录成功之后,关闭浏览器,一份钟以内免密登录,一分钟以外需要密码登录。



全部评论

相关推荐

牛客771574427号:恭喜你,华杰
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务