Spring Security的基本使用
添加依赖
将 spring-boot-starter-security 依赖添加到构建文件中
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
当应用启动的时候,自动配置功能会探测到Spring Security出现在了类路径中,因此它会初始化一些基本的安全配置。
此时,访问接口,会要求进行认证
用户名为:user,密码可以在应用的启动日志中找到
Using generated security password: 6b736fda-e3bb-4085-a191-6794624ff9e5
配置Spring Security
编写配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
Spring Security为配置用户存储提供了多个可选方案:
- 基于内存的用户存储;
- 基于JDBC的用户存储;
- 以LDAP作为后端的用户存储;
- 自定义用户详情服务。
Spring Security为PasswordEncoder接口提供了多个实现:
- BCryptPasswordEncoder:使用bcrypt强哈希加密。
- NoOpPasswordEncoder:不进行任何转码。
- Pbkdf2PasswordEncoder:使用PBKDF2加密。
- SCryptPasswordEncoder:使用scrypt哈希加密。
- StandardPasswordEncoder:使用SHA-256哈希加密。
自定义用户认证
定义用户实体
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
@Entity
@Table(name = "sys_user")
public class SysUser implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "user_id")
private long userId;
@Column(name = "user_name")
private String userName;
@Column(name = "gender")
private String gender;
@Column(name = "account",unique = true,nullable = false)
private String account;
@Column(name = "password",nullable = false)
private String password;
@Column(name = "city")
private String city;
@Column(name = "created_by")
private String createdBy;
@Column(name = "created_time")
private Date createdTime;
@Column(name = "updated_by")
private String updatedBy;
@Column(name = "updated_time")
private Date updatedTime;
@JsonIgnoreProperties("users")
@ManyToMany(targetEntity = SysRole.class)
@JoinTable(name = "sys_user_role",
joinColumns = @JoinColumn(name = "user_id",referencedColumnName = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id",referencedColumnName = "role_id"))
private List<SysRole> roles;
@PrePersist
void createdTime(){
this.createdTime = new Date();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_SYS_ADMIN"));
}
@Override
public String getUsername() {
return this.account;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
User类实现了Spring Security的UserDetails接口。
通过实现UserDetails接口,我们能够提供更多信息给框架,比如用户都被授予了哪些权限以及用户的账号是否可用。
getAuthorities()方法应该返回用户被授予权限的一个集合。各种is…Expired()方法要返回一个boolean值,表明用户的账号是否可用或过期。
创建查询用户的Repository
public interface UserRepository extends CrudRepository<SysUser,Long> {
SysUser findByAccount(String account);
}
创建控制器
/**
* 用户相关操作
*/
@Slf4j
@Controller
@RequestMapping("/user")
public class UserController {
private UserRepository userRepository;
private PasswordEncoder passwordEncoder;
@Autowired
public UserController(UserRepository userRepository,PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@GetMapping("/getUserList")
@ResponseBody
public List<SysUser> getUserList(@AuthenticationPrincipal SysUser sysUser){
log.info("当前登录用户为:" + sysUser.getUsername() );
Iterable<SysUser> all = userRepository.findAll();
List<SysUser> userList = new ArrayList<>();
all.forEach(u -> userList.add(u));
return userList;
}
@GetMapping("/getUserByAccount")
@ResponseBody
public SysUser getUserByAccount(@RequestParam("account") String account){
SysUser sysUser = userRepository.findByAccount(account);
return sysUser;
}
}
创建用户详情服务
Spring Security的UserDetailsService是一个相当简单直接的接口:
public interface UserDetailsService {
UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException;
}
这个接口的实现会得到一个用户的用户名,并且要么返回查找到的 UserDetails 对象,要么在根据用户名无法得到任何结果的情况下抛出 Username NotFoundException。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
SysUser sysUser = userRepository.findByAccount(account);
if (sysUser != null){
return sysUser;
}
throw new UsernameNotFoundException("User account:'"+account+"' not found");
}
}
将这个自定义的用户详情服务与Spring Security配置在一起,并且配置一些安全规则
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public BCryptPasswordEncoder encoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/system/register")
.permitAll()
.antMatchers("/user/**","/role/**")
.hasRole("SYS_ADMIN")
.and()
.formLogin()
.loginProcessingUrl("/login");
}
}
/system/register:允许所有人访问。
/user/** /role/**:只有具备SYS_ADMIN角色的用户才能访问。
测试开始之前,需要在数据库中配置一个角色
在声明请求路径的安全需求时,hasRole()和permitAll()只是众多方法中的两个
了解用户是谁
我们有多种方式确定用户是谁,常用的方式如下:
- 注入Principal对象到控制器方法中;
- 注入Authentication对象到控制器方法中;
- 使用SecurityContextHolder来获取安全上下文;
- 使用@AuthenticationPrincipal注解来标注方法。