前言
Spring security内置的用户存储非常便利,但是当我们的应用需要一些更特殊的功能时,当开箱即用的用户存储无法满足需求的时候,我们就需要创建和配置自定义的的用户 详情服务,最终数据位于关系型数据库中,使用Spring Data respository
一、定义用户实体
如下是一个Boss类
package sia.tacocloud.DAO;
import com.sun.istack.NotNull;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.persistence.*;
import java.util.Arrays;
import java.util.Collection;
@Entity
@Data
@Table(name="boss")
@Component
public class Boss implements UserDetails {
@NotNull()
private String name;
private String password;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getUsername() {
return name;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
该Boss实体类要实现Spring Security 的UserDetails接口,通过该接口我们可以提供给更多信息给框架,比如用户被授予了哪些权限以及用户的帐号是否可用。 如上的getAuthorities0方法应该返回用户被授予权限的一个集合。 各种is...Expired()方法要返回一个boolean值,表明用户的账号是否可用或过期。 对于Boss实体来说,getAthorities()方法只是简单地返回一个集合, 这个集合表明所有的用户都被授予了ROLE_USER权限。至少就现在来说,Taco Cloud没有必要禁用用户,所以所有的is--Expired()方法均返回true,表明用户是处于活跃状态的。
Boss实体定义完之后,我们就可以定义repository 接口了:
二、定义repository 接口
package sia.tacocloud.DAO;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface BossCrudRepository extends CrudRepository<Boss,Integer> {
//启用SQL查询
@Query(value ="select * from boss where name = ?",nativeQuery=true)
Boss findBossByName(String name);
}
在上述的类中自定义一个根据name查找Boss的方法,Spring Data JPA会自动生成这个接口的实现,所以我们直接编写使用该repository的用户详情接口了。
三、创建用户详情服务
Spring Security的UserDetailsService是一个相当简单直接的接口:
public interface UserDetailsService {
UserDetails loadUserByUsername (String username)throws UsernameNotFoundException;
正如我们所看到的,这个接口的实现会得到一个用户的用户名,并且要么返回查找到的UserDetails对象,要么在根据用户名无法得到任何结果的情况下抛出UsernameNotFoundException。
因为我们的Boss类实现了UserDetails ,并且UserRepository提供了findBossByName方法,所以它们非常适合用在UserDetailsService实现类中。
代码如下:
ckage sia.tacocloud.Service;
import org.springframework.beans.factory.annotation.Autowired;
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 sia.tacocloud.DAO.Boss;
import sia.tacocloud.DAO.BossCrudRepository;
@Service
public class BossDIYSecurityService implements UserDetailsService {
private final BossCrudRepository bossCrudRepository;
@Autowired
public BossDIYSecurityService(BossCrudRepository bossCrudRepository){
this.bossCrudRepository = bossCrudRepository;
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Boss bossByNameUse = bossCrudRepository.findBossByNameUse(s);
if(bossByNameUse != null){
return bossByNameUse;
}
throw new UsernameNotFoundException("User '"+s+"' not found");
}
}
BossDIYSecurityService 通过构造器将BossCrudRepository注入进来。然后在loadByUsername方法中,它调用了bossRepository的findBossByName方法来查找,loadByUsername方法有一个简单的规则:它决不能返回null 因此,如果findBossByName返回null,那么loadByUsername方法将会抛出UsernameNotFoundException否则,将会返回查找到的Boss。
同时我们依然需要将这个自定义的用户详情服务与SpringSecurity配置在一起,因此,我们要在configure方法配置
如下:
package sia.tacocloud.Control;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.builders.HttpSecurity;
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 {
private final UserDetailsService userDetailsService;
@Autowired
public SecurityConfig(@Qualifier("bossDIYSecurityService") UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean//加密模式,记住这个验证时是加密验证的,那么存的时候也要加密后再存
public PasswordEncoder encode(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encode());
}
}
}
在这里,我们只是简单地调用userDetailsSeviceC0方法,并将自动装配到SeuityConfig中的UserDetailsService实例传递了进去。 同时还配置了一个密码转码器(使用的强哈希转码),这样在数据库中的密码将是转码过的。我们首先需要声明一个PasswordEncoder类型的bean,然后passwordEncoder方法将它注人到用户详情服务中。 现在,我们已经有了自定义的用户详情服务,它会通过JPA repository 读取用户信息来进行验证。注意的就是数库中的数据的存储不应该是明文存储而是和密码转码器相对应。