ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringSecurity学习笔记

2022-07-23 14:32:07  阅读:163  来源: 互联网

标签:springframework SpringSecurity 学习 笔记 org import security new public


1、SpringSecurity学习笔记

SpringSecurity底层本质是一个过滤器链

 

FilterSecurityInterceptor:是一个方法级的过滤器,位于过滤器链的最底部

ExceptionTranslationFilter:异常过滤器,用来处理认证授权过程中抛出的异常

UsernamePasswordAuthenticationFilter:对/login的POST请求做拦截,校验表单中的用户名和密码。

 

1、两个重要接口

1、UserDetailsService:从数据库查询用户名和密码的过程

  • 创建类继承UsernamePasswordAuthenticationFilter,重写它的三个方法

  • 创建类实现UserDetailsService,编写查询数据库的过程,返回一个User对象,这个User对象是SpringSecurity提供的一个对象

 

2、PasswordEncoder:数据加密的接口,一般用于返回User对象密码的加密

 

2、认证

1、设置登录用户名和密码

  • 第一种:application.yml配置文件中配置

     spring:
      security:
        user:
          name: admin
          password: admin123
  • 第二种:通过配置类配置

     @Configuration
     public class SecurityConfig extends WebSecurityConfigurerAdapter {
         @Override
         protected void configure(AuthenticationManagerBuilder auth) throws Exception {
             // 对密码进行加密
             BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
             String password = encoder.encode("admin123");
     ​
             auth.inMemoryAuthentication()
                     // 登录的用户名
                    .withUser("admin")
                     // 登录的密码
                    .password(password)
                     // 角色
                    .roles("admin");
        }
     ​
         @Bean
         public PasswordEncoder passwordEncoder(){
             return new BCryptPasswordEncoder();
        }
     }
  • 第三种:自定义编写实现类

    • 编写UserDetailsService的实现类

       package com.zq.security.service;
       ​
       import org.springframework.beans.factory.annotation.Autowired;
       import org.springframework.security.core.GrantedAuthority;
       import org.springframework.security.core.authority.AuthorityUtils;
       import org.springframework.security.core.userdetails.User;
       import org.springframework.security.core.userdetails.UserDetails;
       import org.springframework.security.core.userdetails.UserDetailsService;
       import org.springframework.security.core.userdetails.UsernameNotFoundException;
       import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
       import org.springframework.security.crypto.password.PasswordEncoder;
       import org.springframework.stereotype.Service;
       ​
       import java.util.List;
       ​
       @Service
       public class MyUserDetailsService implements UserDetailsService {
       ​
           @Autowired
           private PasswordEncoder passwordEncoder;
       ​
           @Override
           public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
               List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
               return new User("admin",passwordEncoder.encode("admin123"),authorities);
          }
       }
       ​
    • 编写配置类使用我们自定义的UserDetailsService

       package com.zq.security.config;
       ​
       import org.springframework.beans.factory.annotation.Autowired;
       import org.springframework.context.annotation.Bean;
       import org.springframework.context.annotation.Configuration;
       import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
       import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
       import org.springframework.security.core.userdetails.UserDetailsService;
       import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
       import org.springframework.security.crypto.password.PasswordEncoder;
       ​
       @Configuration
       public class CustomerSecurityConfig extends WebSecurityConfigurerAdapter {
       ​
           @Autowired
           private UserDetailsService userDetailsService;
       ​
           @Override
           protected void configure(AuthenticationManagerBuilder auth) throws Exception {
               auth.userDetailsService(userDetailsService)
                      .passwordEncoder(passwordEncoder());
          }
       ​
           @Bean
           public PasswordEncoder passwordEncoder(){
               return new BCryptPasswordEncoder();
          }
       }

 

2、通过数据库完成用户认证

1、mybatis-plus依赖

 <!--Mybatis-Plus-->
 <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
 </dependency>

2、实体类Users.java

 import lombok.Data;
 ​
 import java.io.Serializable;
 ​
 @Data
 public class Users implements Serializable {
     private Integer id;
 ​
     private String username;
 ​
     private String password;
 }
 ​

3、数据库接口UsersMapper.java

 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.zq.security.pojo.Users;
 ​
 public interface UsersMapper extends BaseMapper<Users> {
 }

4、自定义UserDetailsService

 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.zq.security.mapper.UsersMapper;
 import com.zq.security.pojo.Users;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 ​
 import java.util.List;
 ​
 @Service
 public class MyUserDetailsService implements UserDetailsService {
 ​
     @Autowired
     private UsersMapper usersMapper;
 ​
     @Autowired
     private PasswordEncoder encoder;
 ​
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         // 根据表单传过来的用户名从数据库查询用户信息
         QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
         queryWrapper.eq("username",username);
         Users users = usersMapper.selectOne(queryWrapper);
 ​
         // 当用户名不存在时抛出异常
         if (null == users){
             throw new UsernameNotFoundException("用户名不存在");
        }
 ​
         List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
         // 将从数据库查询的用户名和密码封装到User并进行返回
         return new User(users.getUsername(),encoder.encode(users.getPassword()),authorities);
    }
 }

5、config配置类

 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 ​
 @Configuration
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
 ​
     @Autowired
     private UserDetailsService userDetailsService;
 ​
     @Override
     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }
 ​
     @Bean
     public PasswordEncoder passwordEncoder(){
         return new BCryptPasswordEncoder();
    }
 }

 

3、自定义登录页面和白名单

1、config配置类

 @Configuration
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
 ​
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         // 自定义自己编写登录页面
         http.formLogin()
                 // 登录页面的路径
                .loginPage("/login.html")
                 // 点击登录访问的路径
                .loginProcessingUrl("/user/login")
                 // 登录成功后跳转的路径
                .defaultSuccessUrl("/user/index").permitAll()
                 // 哪些访问路径不需要认证可以直接访问
                .and().authorizeRequests()
                .antMatchers("/hello","/user/login").permitAll()
                 // 任何路径都能访问
                .anyRequest().authenticated()
                 // 关闭csrf防护
                .and().csrf().disable();
    }
 }
 ​

 

3、授权

1、hasAuthority方法:当前用户具有指定的权限,有则返回true,否则返回false

  • 第一步:配置类指定权限

     // 表示当前用户访问这个路径必须有admin权限
     .antMatchers("/user/index").hasAuthority("admin")
  • 第二步:返回User对象里面设置当前登录对象相对应的权限

     // 给予当前用户admin的权限
     List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
     return new User("username",new BCryptPasswordEncoder().encode("password"),authorities);

注意:此方法只能针对一个权限

 

2、hasAnyAuthority方法:当前用户具有指定权限的任意一种则返回true,否则返回false

  • 配置类指定权限

     // 只要有admin或root任意一个权限就能访问
     .antMatchers("/user/index").hasAnyAuthority("admin","root")

 

3、hasRole方法:当前用户具有指定角色才能允许访问

  • 第一步:配置类配置

     // 当前登录用户必须拥有manage这个角色才能访问
     .antMatchers("/user/index").hasRole("manage")
  • 第二步:hasRole()原码分析:

     public ExpressionInterceptUrlRegistry hasRole(String role) {
      return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
      }
     ​
     // 可以看出源码返回角色时默认加了前缀ROLE_
     private static String hasRole(String role) {
      Assert.notNull(role, "role cannot be null");
      Assert.isTrue(!role.startsWith("ROLE_"),
      () -> "role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
      return "hasRole('ROLE_" + role + "')";
      }
  • 第三步:给登录角色配置权限

     // 因为原码在角色的前面加了前缀ROLE_,所以我们在分配角色的时候也需要加上这个前缀
     List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_manage");
     return new User(users.getUsername(),encoder.encode(users.getPassword()),authorities);

 

4、hasAnyRole方法:拥有任意一个角色才能访问

  • 第一步:配置类配置

     // 当前登录用户拥有manage或admin任意一个角色才能访问
     .antMatchers("/user/index").hasAnyRole("admin","manage")
  • 第二步:给登录角色配置权限

     List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin");
     return new User(users.getUsername(),encoder.encode(users.getPassword()),authorities);

     

4、自定义403没有权限访问页面

1、配置类中配置

 @Configuration
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
 ​
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         // 没有权限时跳转的页面路径
         http.exceptionHandling().accessDeniedPage("/unauth.html");
 }

 

5、常用注解的使用

1、@Secured("ROLE_role1,ROLE_role1")

指定角色才能访问,使用在Controller的方法上

注意:使用该注解必须在主启动类上添加@EnableGlobalMethodSecurity(securedEnabled = true)

 @GetMapping("/update")
 @Secured({"ROLE_update","ROLE_sale"})
 public String update(){
    return "hello update";
 }

 

2、@PreAuthorize("hasAnyAuthority('menu:system')")

注解进入方法前的权限验证,@PreAuthorize可以将登录用户的角色或权限传入方法中

注意:使用该注解必须在主启动类上添加@EnableGlobalMethodSecurity(prePostEnabled = true)

  @GetMapping("/insert")
 // @PreAuthorize("hasRole('ROLE_管理员')")
 @PreAuthorize("hasAnyAuthority('menu:system')")
 public String insert(){
   return "hello insert";
 }

 

3、@PostAuthorize("hasAnyAuthority('menu:system')")

这个注解用的并不多,在方法执行后进行权限验证,适合验证带有返回值权限

注意:使用该注解必须在主启动类上添加@EnableGlobalMethodSecurity(prePostEnabled = true)

 @GetMapping("/select")
 @PostAuthorize("hasAnyAuthority('menu:normal')")
 public String select(){
   System.out.println("hello select");
   return "hello select";
 }

 

4、@PostFilter("filterObject.username == 'admin1'")

权限验证之后对返回的数据进行过滤,留下的是admin1的数据

表达式中filterObject引用的是方法返回值List中的某一个元素

 @GetMapping("/list")
 @PostFilter("filterObject.username == 'admin1'")
 @PreAuthorize("hasRole('ROLE_管理员')")
 public List<Users> list(){
    List<Users> users = Arrays.asList(
            new Users(1,"admin1","123"),
            new Users(2,"admin2","456"),
            new Users(1,"admin2","789"),
            new Users(1,"admin1","321"),
            new Users(1,"admin3","135")
    );
    return users;
 }

 

5、@PreFilter("filterObject.id % 2 == 0")

权限验证之前对传入的数据进行过滤,当id是2的倍数的时候则留下

 @GetMapping("/list2")
 @PreFilter("filterObject.id % 2 == 0")
 @PreAuthorize("hasRole('ROLE_sale')")
 public List<Users> list2(List<Users> users){
    return users;
 }

 

6、用户注销

1、配置类中配置

 @Configuration
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
 ​
 @Override
 protected void configure(HttpSecurity http) throws Exception {
     // 用户注销
     http.logout()
          // 用户点注销访问的url
        .logoutUrl("/logout")
          // 注销成功跳转的url
        .logoutSuccessUrl("/user/logout")
        .permitAll();
 }

 

7、基于数据库实现“记住我”

1、原理:

浏览器端:Cookie 储存 加密串

数据库端:加密串 与 用户信息字符串对应

认证时:Cookie 使用 加密串匹配用户信息进行认证

1、第一步:创建数据库表

 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、配置类中进行配置:注入数据源,配置操作数据库对象

 @Configuration
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
 ​
     /**
      * 注入数据源
      */
     @Autowired
     private DataSource dataSource;
 ​
     /**
      * 将数据库操作对象交由spring容器管理
      * @return
      */
     @Bean
     public PersistentTokenRepository persistentTokenRepository(){
         JdbcTokenRepositoryImpl bean = new JdbcTokenRepositoryImpl();
         bean.setDataSource(dataSource);
         // 自动生成表
         // bean.setCreateTableOnStartup(true);
         return bean;
    }
 ​
 }

 

3、配置类进一步配置,设置记住我功能

关键代码:

// 记住我有效时长,以秒为单位 .tokenValiditySeconds(60) .userDetailsService(userDetailsService) // 关闭csrf防护 .and().csrf().disable();

 @Configuration
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
 ​
     @Autowired
     private UserDetailsService userDetailsService;
 ​
     /**
      * 注入数据源
      */
     @Autowired
     private DataSource dataSource;
 ​
     /**
      * 将数据库操作对象交由spring容器管理
      * @return
      */
     @Bean
     public PersistentTokenRepository persistentTokenRepository(){
         JdbcTokenRepositoryImpl bean = new JdbcTokenRepositoryImpl();
         bean.setDataSource(dataSource);
         // 自动生成表
         // bean.setCreateTableOnStartup(true);
         return bean;
    }
     
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         // 自定义自己编写登录页面
         http.formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/user/login")
                .defaultSuccessUrl("/user/index").permitAll()
                 // 开启记住我
                .and().rememberMe().tokenRepository(persistentTokenRepository())
                 // 记住我有效时长,以秒为单位
                .tokenValiditySeconds(60)
                .userDetailsService(userDetailsService)
                 // 关闭csrf防护
                .and().csrf().disable();
    }
 }

 

4、登录页面添加=="记住我"==复选框

  记住我:<input type="checkbox" name="remember-me" title="记住我" />

注意:name必须为remember-me

 

8、CSRF理解

什么是CSRF?

CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。

 

9、微服务权限案例

 

标签:springframework,SpringSecurity,学习,笔记,org,import,security,new,public
来源: https://www.cnblogs.com/zhouqiangshuo/p/16511969.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有