SpringSecurity学习总结

文章目录

    • 1.基本概念
      • 1.1 认证
      • 1.2 会话
      • 1.3 授权
      • 1.4 授权数据模型
      • 1.5 RBAC
    • 2.框架简介
    • 3.框架理解
      • 3.1 入门案例
      • 3.2 基本原理
      • 3.3 重要接口
      • 3.4 web项目权限方案
        • 3.4.1 设置登录系统的账号和密码
        • 3.4.2 实现数据库认证来完成用户登录
        • 3.4.3 自定义哪些请求需要登录认证
        • 3.4.4 基于角色或权限进行访问控制
        • 3.4.5 自定义403页面
        • 3.4.6 认证授权注解使用
        • 3.4.7 用户注销
        • 3.4.8 自动登录

1.基本概念

1.1 认证

系统为什么要认证?

认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。

认证︰用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。

常见的用户身份认证方式有︰用户名密码登录,二维码登录,手机短信登录,指纹认证等方式。

1.2 会话

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。

session认证:

用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的sesssion_id 存放到 cookie 中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了

SpringSecurity学习总结_第1张图片

token认证:

用户认证成功后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份

SpringSecurity学习总结_第2张图片

两者比较:

  • session保存在服务端,大量的用户进行登录操作,数据会存放大量的数据;会增加服务器开销
  • token保存在客户端,不保存在数据库;不会增加服务器开销,性能更好

1.3 授权

为什么要授权?

认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,授权是在认证通过后发生的,控制不同的用户能够访问不同的资源

授权:授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问

1.4 授权数据模型

主体—权限—资源

主体(用户id、账号、密码、…)

权限(权限id、权限标识、权限名称、资源名称、资源访问地址、…)

角色(角色id、角色名称、…)

角色和权限关系(角色id、权限id、…)

主体(用户)和角色关系(用户id、角色id、…)

1.5 RBAC

RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限

  • 基于角色的访问控制
  • 基于资源的访问控制(推荐)

2.框架简介

1.SpringSecurity是什么

SpringSecurti基于Spring框架,提供了一套Web应用安全性的完整解决方案,一般来说,Web应用的安全性包括用户认证(Authenticataion)和用户授权(Authorization)两个部分,这两点也是Spring Security重要核心功能

(1)用户认证:用户是否能登录

(2)用户授权:用户是否有权限去做某些事情

2.SpringSecurity与Shiro对比

SpringSecurity特点:

(1)和Spring无缝整合

(2)全面的权限控制

(3)专门为Web开发而涉及

(4)重量级(缺点)

Shiro特点:

(1)轻量级

(2)通用性

总结: spring security功能强于Shiro,但比Shiro复杂

3.框架理解

3.1 入门案例

  • 第一步:创建spring boot工程

  • 第二步:引入相关依赖

     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
     </dependency>
    
  • 第三步:编写controller进行测试

    @RestController
    @RequestMapping("/test")
    public class TestController {
        @GetMapping("hello")
        public String test() {
            return "hello,spring security";
        }
    }
    

SpringSecurity登录默认用户名:user
登录密码:启动控制台中找到

3.2 基本原理

SpringSecurity本质是一个过滤器链,有很多过滤器

通过查看源码,可以发现:

  • FilterSecurityInterceptor:是一个方法级的权限过滤器,基本位于过滤链的最底部
  • ExceptionTranslationFilter:是个异常过滤器,用来处理再认证授权过程中抛出的异常
  • UsernamePasswordAuthenticationFilter:对/login的POST请求做拦截,校验表单中用户名,密码

3.3 重要接口

1.UserDetailsService接口:

当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。

实现自己去查数据库的自定义认证步骤

  1. 写个类继承UsernamePasswordAuthenticationFilter,并重写该类的attemptAuthentication方法
  2. 然后在attemptAuthentication方面里边得到用户输入的用户名和密码,如果认证成功,则调用AbstractAuthenticationProcessingFilter类的successfulAuthentication,如果认证失败,则调用AbstractAuthenticationProcessingFilter类的unsuccessfulAuthentication
  3. 但查数据库里的用户名和密码则在UserDetailsService写你查数据库的过程
  4. 创建类继承UsernamePasswordAuthenticationFilter,重写三个方法attemptAuthentication,successfulAuthentication,unsuccessfulAuthentication
  5. 创建类实现UserDetailsService,编写查询查询数据库过程,返回User对象,这个User对象是SpringSecurity提供的对象

2.PasswordEncoder接口: 数据机密接口,用于返回User对象里面的密码

3.4 web项目权限方案

3.4.1 设置登录系统的账号和密码
  • 方式一:通过配置文件

    spring.security.user.name=admin
    spring.security.user.password=admin
    
  • 方式二:通过配置类

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
            //密码进行加密操作
            String password = passwordEncoder.encode("123");
            auth.inMemoryAuthentication().withUser("xin").password(password).roles("admin");
        }
    
        //创建BCryptPasswordEncoder对象,否则报错:没有匹配关系
        @Bean
        PasswordEncoder password() {
            return new BCryptPasswordEncoder();
        }
    }
    
  • 方式三:自定义编写实现类

    1. 编写配置类

      @Service("userDetailsService")
      public class MyUserDetailsService implements UserDetailsService {
          @Override
          public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
              List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
              return new User("zx", new BCryptPasswordEncoder().encode("123"), auths);
          }
      }
      
    2. 编写实现类,返回User对象,User对象有用户名密码和权限

      @Configuration
      public class SecurityConfig2 extends WebSecurityConfigurerAdapter {
          @Autowired
          private UserDetailsService userDetailsService;
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.userDetailsService(userDetailsService).passwordEncoder(password());
          }
          //创建BCryptPasswordEncoder对象,否则报错:没有匹配关系
          @Bean
          PasswordEncoder password() {
              return new BCryptPasswordEncoder();
          }
      }
      
3.4.2 实现数据库认证来完成用户登录
  • 整合mybatisplus完成数据库操作

  • 创建表和数据

  • 实体类

  • 整合mybatisplus制作mapper

  • 配置文件添加数据库配置

  • 完善登录实现类

    @Service("userDetailsService")
    public class MyUserDetailsService implements UserDetailsService {
    
        @Autowired
        private UsersMapper usersMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //调用usersMapper方法,根据用户名查询数据库
            QueryWrapper<Users> wrapper = new QueryWrapper<>();
            wrapper.eq("username", username);
            Users users = usersMapper.selectOne(wrapper);
            //判断
            if (users == null) {
                throw new UsernameNotFoundException("用户名不存在!");
            }
    
            List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
            return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths);
        }
    }
    
  • 启动类添加扫描器

  • 添加配置类

3.4.3 自定义哪些请求需要登录认证
  • 修改配置类

    @Configuration
    public class SecurityConfig2 extends WebSecurityConfigurerAdapter {
    
        ...
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin()  //自定义自己编写的登录页面
                .loginPage("/login.html") //登录页面设置
                .loginProcessingUrl("/user/login")  //登录访问路径
                .defaultSuccessUrl("/test/index").permitAll() //登录成功,跳转路径
                .and().authorizeRequests()
                	.antMatchers("/", "/test/hello", "/user/login").permitAll()//设置哪些路径可以直接访问,不需要认证
                .anyRequest().authenticated()
                .and().csrf().disable(); //关闭csrf防护
        }
    }
    
  • 创建相关页面,controller
    自定义登录页面:login.html

      DOCTYPE html>
         <html lang="en">
         <head>
             <meta charset="UTF-8">
             <title>Titletitle>
         head>
         <body>
             <form action="/user/login" method="post">
                 用户名:<input type="text" name="username">
                 <br/>
                 密码:<input type="password" name="password">
                 <br/>
                 <input type="submit" value="login">
             form>
         body>
         html>
    

    注意:页面提交方式必须为 post 请求,所以上面的页面不能使用,用户名,密码必须为username,password
    原因:在执行登录的时候会走一个过滤器UsernamePasswordAuthenticationFilter
    表单的action配置的uri不需要配置controller(SpringSecurity默认帮我们做好了),同时这个uri必须和loginProcessingUrl方法里的值保持一致

3.4.4 基于角色或权限进行访问控制
  • 第一个方法:hasAuthority方法(某个权限设置)
    如果当前的主题具有指定的权限,则返回true,否则返回false

    1. 在配置类设置当前访问地址有哪些权限

       @Override
       protected void configure(HttpSecurity http) throws Exception {
           http.formLogin()  //自定义自己编写的登录页面
                   ...
                   //当前登录用户,只有具有admins权限才可以访问这个路径
                   .antMatchers("/test/index").hasAuthority("admins")
                   ...
       }
      
    2. 在userDetailService,把返回user对象设置权限

      List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
                       return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths);
      
  • 第二个方法:hasAnyAuthority 方法 (设置多个权限)

    .antMatchers("/test/index").hasAnyAuthority("admins,manager")
    
  • 第三个方法:hasRole 方法 (单个角色)
    如果当前主体具有指定的角色,则返回true

     .antMatchers("/test/index").hasRole("sale")
      List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
      //springsecurity 会自动添加ROLE_前缀
    
  • 第四个方法:hasAnyRole 方法(多个角色)

3.4.5 自定义403页面
  • 修改访问配置类

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      //配置没有权限访问跳转自定义页面
      http.exceptionHandling().accessDeniedPage("/unauth.html");
      ...
    }
    
  • 添加对应控制器

3.4.6 认证授权注解使用

使用注解先要开启注解功能:在启动类或配置类上添加注解

@EnableGlobalMethodSecurity(securedEnabled = true)
  1. @Secured:用户具有某个角色,可以访问方法,相当于配置类中的hasRole()方法,参数要以 ROLE_开头

    • 在控制器方法上添加注解:

      @GetMapping("update")
      @Secured({"ROLE_sale","ROLE_manager"})
      public String update() {
      	return "hello,spring update";
      }
      
    • userDetailsService设置用户角色

       List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
      
  2. @PreAuthorize:进入方法前的权限验证

     @GetMapping("update")
       //@Secured({"ROLE_sale","ROLE_manager"})
       @PreAuthorize("hasAnyAuthority('admins')")
       public String update() {
       	return "hello,spring update";
       }
    
  3. @PostAuthorize:执行方法后再进行权限验证,适合验证带有返回值的权限

    @PostAuthorize("hasAnyAuthority('admins')")
    
  4. @PreFilter:传入方法数据进行过滤

  5. @PostFileter:方法返回数据进行过滤

3.4.7 用户注销
  • 在配置类中添加退出的配置

    //退出
      http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
    
3.4.8 自动登录

流程:

具体实现:

  1. 创建数据库表 persistent_logins(非必要,可以在配置类中设置自动创建)

    create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)
    
  2. 配置类,注入数据源,配置操作数据库对象

     //注入数据源
     @Autowired
     private DataSource dataSource;
     //配置对象
     @Bean
     public PersistentTokenRepository persistentTokenRepository() {
         JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
         jdbcTokenRepository.setDataSource(dataSource);
    //        jdbcTokenRepository.setCreateTableOnStartup(true);//自动创建表
         return jdbcTokenRepository;
     }
    
  3. 配置类配置自动登录

    .and().rememberMe().tokenRepository(persistentTokenRepository())//设置数据库操作对象
       .tokenValiditySeconds(60)//设置有效时长,单位为秒
       .userDetailsService(userDetailsService)
    
  4. 在登录页面中添加复选框

     <input type="checkbox" name="remember-me">自动登录
    

    注意:name属性值一定要是remember-me

你可能感兴趣的:(Java开发,java)