spring security6.0版本入门解析

spring Security6.0版本入门解析

一、身份证明(用户的密码存储认证)

1.核心接口:PasswordEncoder

public interface PasswordEncoder {
    
	String encode(CharSequence rawPassword);

	boolean matches(CharSequence rawPassword, String encodedPassword);
    
	default boolean upgradeEncoding(String encodedPassword) {
		return false;
	}

}

这个接口,当我们通过配置类@Bean注入时:

- encode:此方法中可以用于对密码进行自定义的加密
- matches:此方法对密码进行解密后比对,返回结果
- upgradeEncoding:此方法可以有多种操作方式,我本人用此方放来判断需要解密的密码长度是否达标后再进行解密,当然各位可以自己定义一些规则在其中

2.委派密码编码(DelegatingPasswordEncoder类)

加密后密码的格式:

{id}encodedPassword: id就是制定加密的方式,encodedPassword就是加密后的密码,所以整个部分有这个id(令牌)和encodedPassword(密码串文)组成

简单来说,就是当用户创建密码时,我们此时拥有一个加密的集合工厂,用户只要告诉这个工厂我们需要什么加密方式,工厂就已对应方式告诉对应的员工来进行加密,最后返还给用户的这种方式(上代码就明白了).

下面是官方给的列子↘

//用户想要的加密方式:bcrypt
String idForEncode = "bcrypt";

//encoders:可以看做是加密的集合工厂
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("sha256", new StandardPasswordEncoder());

//此时这个工厂将这个用户需求:idForEncode拿到,去:encoders加密的集合工厂中找到,进行对应加密
PasswordEncoder passwordEncoder =new DelegatingPasswordEncoder(idForEncode, encoders);

从上面代码中可以看出,只需要建立对应加密集合库,用户输入对应的加密指定即可.那么这样做的优势如下了:

- 灵活的加密方式:不同的用户存储到数据中,有着不同的加密方式
- 提高了安全性
- 方便日后数据加密的变更迁移

DelegatingPasswordEncoder类:

  • 常用方法

    • //这是他的一个构造方法:1:传入对应的编码code(令牌) 2:编码加密的集合(如上面代码中的map)
      public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) {
      		......
      	}
      
    • //这是他的另一个构造器:1:传入对应的编码code(令牌) 2:编码加密的集合(如上面代码中的map) 3:加密后令牌的前缀 4.加密后令牌的后缀
      //列出:{id}encodedPassword
      public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder,String idPrefix, String idSuffix){
      						......
      			}
      
    • //匹配默认的编码器
      public void setDefaultPasswordEncoderForMatches(PasswordEncoder defaultPasswordEncoderForMatches) {
      		if (defaultPasswordEncoderForMatches == null) {
      			throw new IllegalArgumentException("defaultPasswordEncoderForMatches cannot be null");
      		}
      		this.defaultPasswordEncoderForMatches = defaultPasswordEncoderForMatches;
      	}
      

      setDefaultPasswordEncoderForMatches: 我们加密后的密码是由令牌加密文的格式,就像’{id}adsdsdwwd231’这种组合方式,单么有令牌时,就不知道如何匹配密文,所以用此方法设置当没有令牌时,用那种密码编码器,所以此方法可用于数据库中老密码的迁移更新

    其他方法就是实现PasswordEncoder这个接口的方法(不在详解了)

  • 测试实例

    • @Test
          void testDelegatingPasswordEncoder() {
              //设置密码令牌idForEncode:为pbkdf2@SpringSecurity_v5_8的加密方式
              String idForEncode = "pbkdf2@SpringSecurity_v5_8";
              
              //密码编码库
              Map encoders = new HashMap<>();
              encoders.put("bcrypt", new BCryptPasswordEncoder());
              encoders.put("noop", NoOpPasswordEncoder.getInstance());
              encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
              encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
              encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
              encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
              encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
              encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
              encoders.put("sha256", new StandardPasswordEncoder());
              
              //创建实例将密码令牌和密码编码库放入其中
              DelegatingPasswordEncoder delegatingPasswordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
              
              //加密KFC
              String encode = delegatingPasswordEncoder.encode("KFC");
              
              //输出encode = {pbkdf2@SpringSecurity_v5_8}8565ed891d8007100599707d04db6efe2bcb69ec802bc47a1511a50cc2976dcde227e6bcb10284cdf4ac31a3049d8cd4
              System.out.println("encode = " + encode);
      
              /**
               * 创建第二个类似实例,但是放入的令牌是test而不是pbkdf2@SpringSecurity_v5_8,
               * 然后再匹配KFC与去掉令牌的密文,
               * 因为没有令牌的密文匹配会报错如:IllegalArgumentException,
               * 所以通过setDefaultPasswordEncoderForMatches方法设置,当没有令牌时匹配规则为:
               * Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
               * 所以结果为:matches = true
               */
              Map encodeMap = new HashMap<>();
              encodeMap.put("test", new BCryptPasswordEncoder());
              DelegatingPasswordEncoder passwordEncoder = new DelegatingPasswordEncoder("test", encodeMap);
              passwordEncoder.setDefaultPasswordEncoderForMatches(Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
              String oldPassword = "8565ed891d8007100599707d04db6efe2bcb69ec802bc47a1511a50cc2976dcde227e6bcb10284cdf4ac31a3049d8cd4";
              boolean matches = passwordEncoder.matches("KFC", oldPassword);
              System.out.println("matches = " + matches);
          }
      

二、身份认证

1.页面配置拦截

​ 直接上代码,谁跟你藏着掖着!(咱就是简单举个例子)

package com.xu.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

/**
 * @author xu
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig{
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //配置web授权访问,"/login","/register","/upLogin"这些统统无需权限
        http.authorizeHttpRequests().requestMatchers("/login","/register","/upLogin").permitAll()
                //成功的链接,也就是后续访问的链接放在这个里,我这里是成功页需要USER权限才能访问
                .requestMatchers("/success").hasRole("USER").and()
        /**
         * 自定义登陆页面是loginSelf.html页(这里看你模板引擎怎么配),表单提交的处理链接是"/upLogin"
         * 就是<form action="/upLogin"></form>这样,这个"/upLogin"不需要自己处理,就自己决定
         * usernameParameter和passwordParameter,就是表单提交所带的参数,
         * 这里我是"username"和"password";
         */
                .formLogin().loginPage("/loginSelf").loginProcessingUrl("/upLogin")
                .usernameParameter("username").passwordParameter("password");
        return http.build();
    }
}

这个是最新的6.0版本(编写日期:20230306),不再使用继承WebSecurityConfigurerAdapter来配置,而是使用SecurityFilterChain来进行注入,记得的一点就是必须加上@EnableWebSecurity这个注解,不然你小子找不到HttpSecurity!!!

  • requestMatchers():存放访问的路径
  • permitAll():全部允许访问
  • hasRole():需要权限
  • loginPage():配置登陆页面,这里写了,就把它默认自己自带的给不用
  • loginProcessingUrl():form提交到的处理链接,注释写的很清楚了
  • build();创建返回,记得返回要build哦

当然里面的方法还有很多,自己点进去看看吧,例如hasRole()还有hasAnyRole()可以传入多个参数.

注意:配置了loginPage不用loginProcessingUrl,默认的提交链接就是loginPage的参数,如果值配置loginProcessingUrl那么登陆页面就是默认滴!!!


2.用户授权

上面虽然配置了web的页面访问,那么用户登陆如何处理?怎么判断有没有权限?我又怎么给他权限,怎么存进去?

(炒直接代码)

UserServiceImpl类

package com.xu.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xu.pojo.User;
import com.xu.service.UserService;
import com.xu.mapper.UserMapper;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;

/**
* @author xu
* @description 针对表【user】的数据库操作Service实现
* @createDate 2023-03-02 11:30:20
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
    implements UserService, UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        /**
         * 从数据库查:User是我自己写的类,getOne()是mybatis_plus的中的方法
         * 就通过用户名查出一个用户
         */
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username",username);
        User user = getOne(queryWrapper);
        if (user == null) {
            throw  new UsernameNotFoundException("没有这个用户啊!你小子!");
        }
        /**
         * 创建一个权限集合,随你放多少
         * 最后返回一个org.springframework.security.core.userdetails包下的User
         * 授权就给完了
         * 我这里授权的是USER;
         */
        ArrayList<SimpleGrantedAuthority> arrayList = new ArrayList<>();
        arrayList.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),arrayList);
    }
}

配置类:

package com.xu.config;

import com.xu.service.impl.UserServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;

/**
 * @author xu
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig{
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //配置web授权访问,"/login","/register","/upLogin"这些统统无需权限
        http.authorizeHttpRequests().requestMatchers("/login","/register","/upLogin").permitAll()
                //成功的链接,也就是后续访问的链接放在这个里,我这里是成功页需要USER权限才能访问
                .requestMatchers("/success").hasRole("USER").and()
        /**
         * 自定义登陆页面是loginSelf.html页(这里看你模板引擎怎么配),表单提交的处理链接是"/upLogin"
         * 就是<form action="/upLogin"></form>这样,这个"/upLogin"不需要自己处理,就自己决定
         * usernameParameter和passwordParameter,就是表单提交所带的参数,
         * 这里我是"username"和"password";
         */
                .formLogin().loginPage("/loginSelf").loginProcessingUrl("/upLogin")
                .usernameParameter("username").passwordParameter("password");
        return http.build();
    }
    
    //注入自定义授权
    @Bean
    UserDetailsService userDetailsService(){
        return new UserServiceImpl();
    }
}

看完还是懵逼,我来解释一下:

  • 首先看配置类,用户授权就是在UserDetailsService接口下,所以我们要进行配置
  • 所以呢我们就是实现了这个接口,然后在loadUserByUsername方法中定义了自己的处理逻辑,就是通过用户名从数据库中查出来,在授权进行的一个操作

​ 就是这么个理,有些人说你小子,咋不匹配密码啊?只查用户名?

这个是根据你输入的用户名去查出用户信息,然后创建了这个用户的信息和权限,数据库密码也在其中,然后你输入的密码呢,会再次与数据库的密码进行比对,这里就不用你操心了(关于比对规则看第一小节,也是配置好注入@Bean就行)。


三、总结

6.0.0的版本有很多区别,想花哨的玩还是得去看官方文档,我就大概摸了个入门,各位同志还须自己努力,当然适合不分离,若前后端分离的话,用不上,有更好的处理方式(哭死,自己的是前后端分离),实在看不懂去看源码,备注也有实列,推荐看HttpSecurity这玩意里面,还有就是你不会自定义配置,你就看他接口的实现类是怎么搞的,照猫画虎,不懂的类直接百度,或者看官方文档,直接给他拿捏

  • 14
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值