SpringSecurity原理(二)——认证

开始

SpringSecurity原理(一)——初探 SpringSecurity原理(二)——认证 SpringSecurity原理(三)——授权 SpringSecurity原理(四)——过滤器 SpringSecurity原理(五)——扩展与配置

我们接着之前的认证接着说,我们先通过一个简单的示例,来说明一下,不通过配置,如何通过从数据库读取用户信息,然后进行认证(Authentication)。

如果下面的示例中有一些不太清楚的概念,也不要着急,我们先看示例,其中涉及到的概念后面详细介绍。

项目结构基本没有变: 项目结构

首先我们需要实现UserDetailsService,来获取用户相关的信息封装类UserDetails。

import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.stereotype.Service;


@Service
public class MyUserDetailService implements UserDetailsService {

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//		SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("admin");
        // 这里可以访问数据库、缓存等获取用户信息,然后构建为User,User是UserDetails的实现类
		return User.builder()
				.username("bob")//用户名
                //密码 111111 通过BCryptPasswordEncoder加密之后的密文
				.password("$2a$10$344aKAgXr17q7u.8l5i7Cu8wUJr/cxBIniLsVtf/WwFrPx0khY62K")
				.authorities("admin")//权限信息
				.build();
	}
}

UserDetailsService只做一件事情,就是获取UserDetails,主要是用户名、密码和权限。可以使用Spring Security为我们提供的实现类User,也可以自己实现UserDetails,按自己需求封装。

有了怎样获取UserDetails的方法,我们当然还要通过SecurityConfig配置的AuthenticationManagerBuilder告诉Spring Security。

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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import vip.oschool.uinion.security.MyUserDetailService;

import javax.annotation.Resource;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Resource
	MyUserDetailService myUserDetailService;

	@Bean
	BCryptPasswordEncoder bCryptPasswordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(myUserDetailService);
	}
}

注意,我们同时还添加了一个BCryptPasswordEncoder,这是一个密码加密器,因为一般情况数据库存放的都是密码的密文,所以,我们还要告诉Spring Security,我们数据库的密码的加密方式是什么,这样Spring Security才能把从客户端接收到的密码使用同样的方式加密,然后做认证。

当然,你也可以通过下面的方式来设置密码加密器:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(myUserDetailService).passwordEncoder(new BCryptPasswordEncoder());
}

下面我们把配置文件中的用户名和密码注释掉,重新启动,然后就可以通过我们在UserDetailsService实现中设置的用户bob和密码111111来完成认证。

server:
  port: 8081

#spring:
#    security:
#      user:
#        name: tim
#        password: 111111

Principal

一个比较抽象的概念,用于唯一标识实体的类。

例如,对于用户来说:用户id、手机号、邮箱就可以作为用户的principal来标志用户。

Credentials

简单点理解,就可以看做是密码。

GrantedAuthority

先知道是:角色与权限,具体的后面详细介绍。

UserDetails

用户实体的接口,通过这个接口可以获取:

  • getPassword: 获取密码
  • getUserName: 获取用户名
  • isEnabled: 账户是否可用
  • isAccountNonExpired: 账户是否过期
  • isAccountNonLocked: 账户是否被锁定
  • isCredentialsNonExpired: 密码是否过期
  • getAuthorites:获取用户权限,本质上是用户的角色信息

Spring Security中是:org.springframework.security.core.userdetails.UserDetails

Spring Security提供了一个默认的实现类:org.springframework.security.core.userdetails.User

UserDetailsService

这个接口只有一个目的,就是获取UserDetails,例如,我们需要从数据库获取用户,就需要实现这个接口,并把实现类注入到Spring容器中。

只需要获取UserDetails,认证的工作Spring Security自己获通过用户名和密码来验证,中间可能还涉及一下密码处理的过程。

Authentication

用于存储用户认证详细信息:principal、权限GrantedAuthority等。

自定义的Authentication可以实现Authentication接口,也可以直接继承AbstractAuthenticationToken。

Authentication认证继承结构

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

public class JwtAuthenticationToken extends AbstractAuthenticationToken {

    public JwtAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return null;
    }
}

AuthenticationProvider

AuthenticationProvider,顾名思义,认证的提供者,就是用于执行特定类型的身份认证的接口。

我们如果要自定义认证,就需要实现这个接口,例如,现在很多前后端分离项目使用JWT方式来鉴权,那每个请求怎样校验Token呢?

我们就可以创建一个JWTAuthenticationProvider实现AuthenticationProvider,来完成JWT token的认证工作。

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;

public class JwtAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        return null;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.isAssignableFrom(JwtAuthenticationToken.class);
    }
}

然后可以通过下面的方式把我们自定义的AuthenticationProvider注入。

@EnableWebSecurity()
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
  @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider())
                .authenticationProvider(jwtAuthenticationProvider());
    }
}

前面,我们使用的UserDetailService为什么能生效,是因为做了下面的配置:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailService);
}

认证管理器构建者AuthenticationManagerBuilder的userDetailsService会创建一个DaoAuthenticationProvider,DaoAuthenticationProvider使用DelegatingPasswordEncoder的是一个代理,默认使用的是BCryptPasswordEncoder。

AuthenticationManager与ProviderManager

AuthenticationManager,顾名思义,是用来管理Authentication,其实是管理AuthenticationProvider。

ProviderManager是AuthenticationManager的实现类,它持有一个AuthenticationProvider列表,用来完成不同的认证。

它还持有一个AuthenticationManager的引用,作为其父类。

一般来说我们使用ProviderManager就够了,一般也只需要一个AuthenticationProvider。

如果要配置多个,可以在SecurityConfig(继承了WebSecurityConfigurerAdapter)重写authenticationManager。

 @Override
protected AuthenticationManager authenticationManager() {
    // ProviderManager可以设置多个AuthenticationProvider
    ProviderManager authenticationManager = new ProviderManager(Arrays.asList());
    return authenticationManager;
}

PasswordEncoder

简单的说就是用于对密码进行加密,存储用户密码的明文绝对不是什么好的习惯,这是对用户的不负责任,也是项目存在风险。

所以,我们一般都会对用户的密码进行加密之后再存储,通常的做法是加盐然后使用sha256再md5之类的算法。

使用Spring Security就需要我们自己实现这些过程了,直接配置PasswordEncoder就可以了。

Spring Security为PasswordEncoder提供了下面一些实现类。

DelegatingPasswordEncoder

即委托密码编码器,兼容多种不同加密方式存储密码。主要用于新旧数据的加密方式的兼容,做到平滑迁移,例如旧数据使用NoOpPasswordEncoder,新数据使用BCryptPasswordEncoder加密。

BCryptPasswordEncoder

基于bcrypt算法的编码器,为了使其更能抵御密码破解,bcrypt故意降低了速度,与其他自适应单向功能一样,应将其调整为大约1秒钟,以验证系统上的密码。BCryptPasswordEncoder的默认实现使用强度10。

Argon2PasswordEncoder

基于 Argon2算法的编码器,Argon2是一种故意慢速的算法,需要大量内存。 与其他自适应单向功能一样,应将其调整为大约1秒钟,以验证系统上的密码。 Argon2PasswordEncoder的当前实现需要BouncyCastle。

Pbkdf2PasswordEncoder

基于 PBKDF2算法的编码器,为了克服密码破解问题,PBKDF2是一种故意缓慢的算法。 与其他自适应单向功能一样,应将其调整为大约1秒钟,以验证系统上的密码。 当需要FIPS认证时,此算法是一个不错的选择。

SCryptPasswordEncoder

基于scrypt算法的编码器, 为了克服自定义硬件scrypt上的密码破解问题,这是一种故意缓慢的算法,需要大量内存。 与其他自适应单向功能一样,应将其调整为大约1秒钟,以验证系统上的密码。

添加测试用户

我们测试的时候,想要添加测试用户,但是又不想太麻烦的去实现UserDetailsService,也可以通过下面的方式:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
				.withUser("tim")
				.password("111111").roles("admin")
				.and()
				.withUser("allen")
				.password("222222")
				.roles("user");
	}
}

进入inMemoryAuthentication方法,我们可以看到上面的方式等价于:

@Bean
public UserDetailsService userDetailsService() {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("tim").password("111111").roles("admin").build());
    manager.createUser(User.withUsername("allen").password("222222").roles("user").build());
    return manager;
}

所以,本质上还是相当于创建了一个UserDetailsService,只不过数据保存在内存中。

文档

Spring Security官方文档

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章