SpringSecurity(1)

264 阅读5分钟

这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

一、Spring Security 概述

1.1 框架介绍

Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套Web 应用安全性的完整解决方案。

一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。 (1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。说人话就是登录认证 (2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。判断是普通用户还是管理员基于功能访问权限

Spring Security其实就是用filter,多请求的路径进行过滤。 (1)如果是基于Session,那么Spring-security会对cookie里的sessionid进行解析,找到服务器存储的sesion信息,然后判断当前用户是否符合请求的要求。 (2)如果是token,则是解析出token,然后将当前请求加入到Spring-security管理的权限信息中去

1.2 认证与授权实现思路

如果系统的模块众多,每个模块都需要就行授权与认证,所以我们选择基于token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中,Spring-security解析header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问

1、用户名密码认证成功,获取用户的权限值;并以用户名为Key,权限列表为Value的形式存储在Redis缓存中

2、根据用户名相关信息返回token,浏览器将token记录到cookie中,没请求都会在请求头中携带token

3、请求中,Spring-security会解析请求头中的token,并获取用户名;

4、根据用户名为key,去redis中获取value值权限列表

5、根据权限列表来判断用户是否有权限访问

二、Demon 讲解

2.1 自定义登录逻辑

配置密码加密类

@Configuration
public class SecurityConfig {

    /**
     *  设置密码加密
     * @return
     */
    @Bean
    public PasswordEncoder getPw(){
        return new BCryptPasswordEncoder();
    }
}

自己定义登录逻辑。

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;
    private String username = "admin";
    private String password = "123";

    // 重写 了 userDetailsService 的登录逻辑
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 1、比较用户名
        if(!username.equals(s)) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        // 2、比较密码(注册时已经加密过密码) ,这里取出来的是数据库中的加密密码,放入到user 中,security 会自动比较
        String newPassword = passwordEncoder.encode(password);

        // 3、逗号分割的权限
        return new User(username,newPassword, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
    }
}

2.2 自定义登录页面

虽然 SpringSecurity 给我们提供了登录页面,但我们可以自定义登录页面,修改配置即可.

image-20210828144650982

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().loginPage("/login.html");
    }

这是登录就会跳转到自定义的页面,但这是会存在一个问题,所有的页面都可以访问。需要做限制。

   @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单提交
        http.formLogin().loginPage("/login.html");

        // 授权
        http.authorizeRequests()
                // 所有的请求都必须认证才能访问,必须登录
                .anyRequest().authenticated();
    }

此时重定向次数过多,因为在跳转login.html 的时候也会被限制,所以需要方向 login.html,不需要认证.

image-20210828145039973

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单提交
        http.formLogin().loginPage("/login.html")
        // 必须和表单提交的接口一样,执行自定义逻辑
        .loginProcessingUrl("/login")
        .successForwardUrl("/login");

        // 授权
        http.authorizeRequests()
                // 放行 login.html,不需要认证
                .antMatchers("/login.html").permitAll()
                // 所有的请求都必须认证才能访问,必须登录
                .anyRequest().authenticated();
        // 关掉 csrf 防护
        http.csrf().disable();
    }

2.3 登录失败跳转

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单提交
        http.formLogin().loginPage("/login.html")
        // 必须和表单提交的接口一样,执行自定义逻辑
        .loginProcessingUrl("/login")
        .successForwardUrl("/login")
        .failureForwardUrl("/toError");
        

        // 授权
        http.authorizeRequests()
                // 放行 login.html,不需要认证
                .antMatchers("/login.html").permitAll()
                .antMatchers("/error.html").permitAll()
                // 所有的请求都必须认证才能访问,必须登录
                .anyRequest().authenticated();
        // 关掉 csrf 防护
        http.csrf().disable();
    }

2.4 设置请求的账户和密码的参数名

登录的账户和密码必须是 password 和 username 。

image-20210828150624222

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单提交
        http.formLogin().loginPage("/login.html")
        // 必须和表单提交的接口一样,执行自定义逻辑
        .loginProcessingUrl("/login")
        .successForwardUrl("/login")
        .failureForwardUrl("/toError")
        .usernameParameter("user123")
        .passwordParameter("pass123");


        // 授权
        http.authorizeRequests()
                // 放行 login.html,不需要认证
                .antMatchers("/login.html").permitAll()
                .antMatchers("/error.html").permitAll()
                // 所有的请求都必须认证才能访问,必须登录
                .anyRequest().authenticated();
                
        // 关掉 csrf 防护
        http.csrf().disable();
    }

2.5 自定义登录成功处理器

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private String url;

    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }

    // 重定向可以跳转到百度
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.sendRedirect(url);
    }
}

自定义登录成功处理器

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private String url;

    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }

    // 重定向可以跳转到百度
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.sendRedirect(url);
    }
}

这时就可以看到登录成功了。

类似的自定义登录失败处理器类似。

2.6 方法讲解

  • anyRequest 是放最后面的,所有的任何请求放后面,特殊的请求放前面。
  • antMatchers:放行静态资源,不需要认证
  • regexMatchers:匹配正则表达式
  • permitAll():允许所有
  • authenticated:所有都要认证
  // 授权
        http.authorizeRequests()
                // 放行 login.html,不需要认证
                .antMatchers("/login.html").permitAll()
                .antMatchers("/error.html").permitAll()
                .regexMatchers(".+[.]png").permitAll()
                // 所有的请求都必须认证才能访问,必须登录
                .anyRequest().authenticated();
      

三、项目开发

image-20210310212510840