我们在使用spring sec的时候,一般会继承WebSecurityConfigurerAdapter类,然后选择覆盖protected void configure(AuthenticationManagerBuilder auth)和protected void configure(HttpSecurity http)方法
@Autowired
private AuthenticationProviderService authenticationProvider;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SCryptPasswordEncoder sCryptPasswordEncoder() {
return new SCryptPasswordEncoder();
}
//登录认证
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider);
}
//登录拦截模式配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/**").authenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.successHandler(new MySavedRequestAwareAuthenticationSuccessHandler())
.failureHandler(new SimpleUrlAuthenticationFailureHandler())
.and()
.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))
.and()
.exceptionHandling().authenticationEntryPoint(new Http401AuthenticationEntryPoint("login first"))
.accessDeniedHandler(new AccessDeniedHandlerImpl());
}
Spring Security 中用户对象相关类结构图
一般登录的拦截 一般重写 protected void configure(HttpSecurity http) 方法,登录用户的认证我们一般,重写 protected void configure(AuthenticationManagerBuilder auth) 方法
登录用户的认证
简要说明
AuthenticationManager :认证是由 AuthenticationManager 来管理的,但是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider。AuthenticationManager 中可以定义有多个 AuthenticationProvider;
AuthenticationProvider :认证管理的逻辑实现类,当我们需要实现自己的认证逻辑的时候需要实现此接口,重写 public Authentication authenticate(Authentication authentication) 方法;
UserDetailsService :用户操作相关的接口类,当我们想要从数据库中查询用户的信息的时候需要实现实现此接口查询用户信息;
认证是由 AuthenticationManager 来管理的,但是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider。AuthenticationManager 中可以定义有多个 AuthenticationProvider。当我们使用 authentication-provider 元素来定义一个 AuthenticationProvider 时需要实现AuthenticationProvider接口,如果没有指定对应关联的 AuthenticationProvider 对象,Spring Security 默认会使用 DaoAuthenticationProvider。DaoAuthenticationProvider 在进行认证的时候需要一个 UserDetailsService 来获取用户的信息 UserDetails,其中包括用户名、密码和所拥有的权限等。所以如果我们需要改变认证的方式,我们可以实现自己的 AuthenticationProvider;如果需要改变认证的用户信息来源,我们可以实现 UserDetailsService。
实现了自己的 AuthenticationProvider 之后,我们可以在配置文件中这样配置来使用我们自己的 AuthenticationProvider。其中 myAuthenticationProvider 就是我们自己的 AuthenticationProvider 实现类对应的 bean。
<security:authentication-manager>
<security:authentication-provider ref="myAuthenticationProvider"/>
</security:authentication-manager>
实现了自己的 UserDetailsService 之后,我们可以在配置文件中这样配置来使用我们自己的 UserDetailsService。其中的 myUserDetailsService 就是我们自己的 UserDetailsService 实现类对应的 bean。
<security:authentication-manager>
<security:authentication-provider user-service-ref="myUserDetailsService"/>
</security:authentication-manager>
使用 jdbc-user-service 获取
在 Spring Security 的命名空间中在 authentication-provider 下定义了一个 jdbc-user-service 元素,通过该元素我们可以定义一个从数据库获取 UserDetails 的 UserDetailsService。jdbc-user-service 需要接收一个数据源的引用。
<security:authentication-manager>
<security:authentication-provider>
<security:jdbc-user-service data-source-ref="dataSource"/>
</security:authentication-provider>
</security:authentication-manager>
Spring Security 中认证的对象相关类结构图 ,具体关系如下图
代码实现
重写用户认证逻辑需要实现接口AuthenticationProvider ,重写 Authentication authenticate(Authentication authentication) 方法
package com.lagou.springsecurity.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import com.lagou.springsecurity.user.model.CustomUserDetails;
@Service
public class AuthenticationProviderService implements AuthenticationProvider {
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private SCryptPasswordEncoder sCryptPasswordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
//根据用户名从数据库中获取CustomUserDetails
CustomUserDetails user = userDetailsService.loadUserByUsername(username);
//根据所配置的密码加密算法来分别验证用户密码
switch (user.getUser().getPasswordEncoderType()) {
case BCRYPT:
return checkPassword(user, password, bCryptPasswordEncoder);
case SCRYPT:
return checkPassword(user, password, sCryptPasswordEncoder);
}
throw new BadCredentialsException("Bad credentials");
}
@Override
public boolean supports(Class<?> aClass) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
}
private Authentication checkPassword(CustomUserDetails user, String rawPassword, PasswordEncoder encoder) {
if (encoder.matches(rawPassword, user.getPassword())) {
return new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities());
} else {
throw new BadCredentialsException("Bad credentials");
}
}
}
重写用户的查询逻辑需要实现接口UserDetailsService
package com.lagou.springsecurity.service;
import com.lagou.springsecurity.domain.User;
import com.lagou.springsecurity.repository.UserRepository;
import com.lagou.springsecurity.user.model.CustomUserDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.function.Supplier;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public CustomUserDetails loadUserByUsername(String username) {
Supplier<UsernameNotFoundException> s =
() -> new UsernameNotFoundException("Username" + username + "is invalid!");
User u = userRepository.findUserByUsername(username).orElseThrow(s);
return new CustomUserDetails(u);
}
}
补充说明
Spring Security 中的用户对象
Spring Security 中的用户对象用来描述用户并完成对用户信息的管理,涉及UserDetails、GrantedAuthority、UserDetailsService 和 UserDetailsManager这四个核心对象。
UserDetails:描述 Spring Security 中的用户。
GrantedAuthority:定义用户的操作权限。
UserDetailsService:定义了对 UserDetails 的查询操作。
UserDetailsManager:扩展 UserDetailsService,添加了创建用户、修改用户密码等功能。
这四个对象之间的关联关系如下图所示,显然,对于由 UserDetails 对象所描述的一个用户而言,它应该具有 1 个或多个能够执行的 GrantedAuthority:
Spring Security 中的四大核心用户对象
结合上图,我们先来看承载用户详细信息的 UserDetails 接口,如下所示:
public interface UserDetails extends Serializable {
//获取该用户的权限信息
Collection<? extends GrantedAuthority> getAuthorities();
//获取密码
String getPassword();
//获取用户名
String getUsername();
//判断该账户是否已失效
boolean isAccountNonExpired();
//判断该账户是否已被锁定
boolean isAccountNonLocked();
//判断该账户的凭证信息是否已失效
boolean isCredentialsNonExpired();
//判断该用户是否可用
boolean isEnabled();
}
通过 UserDetails,我们可以获取用户相关的基础信息,并判断其当前状态。同时,我们也可以看到 UserDetails 中保存着一组 GrantedAuthority 对象。而 GrantedAuthority 指定了一个方法用来获取权限信息,如下所示:
public interface GrantedAuthority extends Serializable {
//获取权限信息
String getAuthority();
}
UserDetails 存在一个子接口 MutableUserDetails,从命名上不难看出后者是一个可变的 UserDetails,而可变的内容就是密码。MutableUserDetails 接口的定义如下所示:
interface MutableUserDetails extends UserDetails {
//设置密码
void setPassword(String password);
}
如果我们想要在应用程序中创建一个 UserDetails 对象,可以使用如下所示的链式语法:
UserDetails user = User.withUsername("jianxiang")
.password("123456")
.authorities("read", "write")
.accountExpired(false)
.disabled(true)
.build();
Spring Security 还专门提供了一个 UserBuilder 对象来辅助构建 UserDetails,使用方式也类似:
User.UserBuilder builder =
User.withUsername("jianxiang");
UserDetails user = builder
.password("12345")
.authorities("read", "write")
.accountExpired(false)
.disabled(true)
.build();
在 Spring Security 中,针对 UserDetails 专门提供了一个 UserDetailsService,该接口用来管理 UserDetails,定义如下:
public interface UserDetailsService {
//根据用户名获取用户信息
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
而 UserDetailsManager 继承了 UserDetailsService,并提供了一批针对 UserDetails 的操作接口,如下所示:
public interface UserDetailsManager extends UserDetailsService {
//创建用户
void createUser(UserDetails user);
//更新用户
void updateUser(UserDetails user);
//删除用户
void deleteUser(String username);
//修改密码
void changePassword(String oldPassword, String newPassword);
//判断指定用户名的用户是否存在
boolean userExists(String username);
}