spring security 配置
- 本文基于spring boot 配置,项目是基于java配置的也可以参考,基于xml的同学可以尝试切换到基于java代码配置。
- 前面几节是一个入门引导,如果没接触过spring security的同学,建议从头开始看。如果有一定了解,需要实际项目应用的同学,可以从 更复杂的自定义配置(实际场景) 实际场景开始看。
添加依赖
我的项目基于spring boot ,只需要在pom文件添加了该依赖,不用进行其他配置,spring将按照默认配置进行全局安全拦截。默认配置下访问任何资源(页面,接口,静态资源等)都需要登陆,未登陆时会弹出登录框。
用户名:user
密码:启动时随机生成的,会输出在控制台
角色: USER
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
相关源码可查看
org.springframework.boot.autoconfigure.security.AuthenticationManagerConfiguration
org.springframework.boot.autoconfigure.security.SecurityProperties.User
配置用户名和密码
可以通过在application.properties文件里配置指定用户名和密码,重启后即可用该用户名和密码登陆,角色依然是USER。
# security
security.user.name=admin
security.user.password=admin
基于内存的用户认证
如果需要配置多个用户,可以通过继承WebSecurityConfigurerAdapter 并重写configure(AuthenticationManagerBuilder auth)方法,定义基于内存的用户认证,可以配置多个用户和分配不同的权限。
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("root").password("root").roles("ADMIN", "USER")
.and()
.withUser("admin").password("admin").roles("ADMIN")
.and()
.withUser("user").password("user").roles("USER");
}
}
接口细粒度权限控制
在上一节中,我们继承了WebSecurityConfigurerAdapter,实现了自定义的用户认证。
现在我们重写configure(HttpSecurity http)方法,实现自定义权限拦截。
注意这个注解 @EnableGlobalMethodSecurity(prePostEnabled = true) 是开启方法级别的权限控制,通过配合权限注解,即可实现细粒度的权限控制。
权限注解参考
- 首先写一个控制器,编写3个接口,第一个允许匿名访问,第二个需要USER角色,第三个需要ADMIN角色
@RestController
@RequestMapping("authority")
public class AuthorityController {
@GetMapping("test")
public String test() {
return "hello anyone";
}
@GetMapping("test2")
@PreAuthorize("hasRole('USER')")
public String test2() {
return "hello user";
}
@GetMapping("test3")
@PreAuthorize("hasRole('ADMIN')")
public String test3() {
return "hello admin";
}
}
- 然后修改上一节中的WebSecurityConfig类,重写 configure(HttpSecurity http)。
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
...
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/authority/test").permitAll()
.anyRequest()
.authenticated()
.and().formLogin()
.and().httpBasic();
}
}
- authorizeRequests() 开启请求验证
- antMatchers("/authority/test") 匹配指定路径,支持通配符**
- permitAll() 允许所有权限访问。
- anyRequest() 匹配所有请求
- authenticated() 进行权限验证
- formLogin() 基于表单验证,spring会生成一个简单的登陆页。
所以这段配置的意思就是,挣对任何一个请求,都需要进行权限验证,"/authority/test"这个路径允许所以人访问,不管有没有权限,其他路径需要授权才能访问,并定义了基于表单的登陆验证。
这一节我们写了3个接口,而我们上一节定义了3个账号。
/authority/test 接口可以匿名访问
/authority/test2 接口需要USER角色
/authority/test3 接口需要ADMIN角色
root 拥有ADMIN,USER角色,可以访问所有接口
USER 只拥有 USER角色,可以访问 test 和 test2
admin 只拥有ADMIN角色,可以访问 test 和 test3
更复杂的自定义配置(实际场景)
以上只是最简单的配置和使用,但是正常我们项目里都需要根据实际需求进行更复杂的用户管理和权限管理。
CSRF又称跨域请求伪造,攻击方通过伪造用户请求访问受信任站点。spring security4.x之后,默认开启CSRF防范。
如果请求类型不是 (GET|HEAD|TRACE|OPTIONS) ,则会判断请求是否携带了token,如果没有就会报如下异常,这会对我们的程序造成一定的麻烦,可以通过配置关掉CSRF
Could not verify the provided CSRF token because your session was not found in spring security
@Override
protected void configure(HttpSecurity http) throws Exception {
...
http.csrf().disable();
}
UserDetailsService 是用来加载用户信息,spring security 在进行权限认证的时候会调用它的 loadUserByUsername() 方法获取用户名,密码,角色权限等信息,进行登录权限认证。如果需要加载一些特定的信息,比如从数据库加载用户角色权限,则可以继承UserDetailsService来达到目的。
这里需要注意,设置用户角色的时候需要添加"ROLE_'前缀
- 自定义UserDetails
public class AdminUserDetails implements UserDetails {
private String username;
private String password;
private boolean enabled;
Set<? extends GrantedAuthority> authorities;
public AdminUserDetails(String username, String password, Set<? extends GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
}
public void setAuthorities(Set<? extends GrantedAuthority> authorities) {this.authorities = authorities;}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {return authorities;}
@Override
public String getPassword() {return password;}
@Override
public String getUsername() {return username;}
@Override
public boolean isAccountNonExpired() {return true;}
@Override
public boolean isAccountNonLocked() {return true;}
@Override
public boolean isCredentialsNonExpired() {return true;}
@Override
public boolean isEnabled() {return enabled;}
public void setEnabled(boolean enabled) { this.enabled = enabled;}
}
- 自定义UserDetailsService
public class AuthorityService implements UserDetailsService {
@Autowired
AdminUserMapper adminUserMapper;
@Autowired
AdminRoleMapper adminRoleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AdminUser adminUser = adminUserMapper.selectByAccount(username);
if (adminUser == null) {
throw new UsernameNotFoundException(username + " not found");
}
List<AdminRole> adminRoles = adminRoleMapper.selectRoleByAccount(username);
AdminUserDetails adminUserDetails = null;
if (CollectionUtils.isNotEmpty(adminRoles)) {
Set<SimpleGrantedAuthority> simpleGrantedAuthorities = new HashSet<>();
adminRoles.forEach(role -> simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName())));
adminUserDetails = new AdminUserDetails(adminUser.getAccount(), adminUser.getPassword(), simpleGrantedAuthorities);
adminUserDetails.setEnabled(adminUser.getStatus());
}
return adminUserDetails;
}
}
- 配置使用自定义UserDetailsService
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService());
}
@Override
@Bean
public UserDetailsService userDetailsService() {
return new AuthorityService();
}
}
spring security自带了的多种加密器,官方推荐使用BCryptPasswordEncoder,是一个非常强大的加密器。
但是有时我们需要使用自定义的加密器。可以实现 PasswordEncoder 重写encode 和matches,然后配置使用自己的加密器,这样在登录时spring会使用该加密器进行密码校验。
- 自定义一个加密器
public class Md5PasswordEncoder implements e10adc3949ba59abbe56e057f20f883e {
//加密密码
@Override
public String encode(CharSequence charSequence) {
return CommonUtils.pwdDigest(charSequence.toString());
}
//校验密码
@Override
public boolean matches(CharSequence charSequence, String encodedPassword) {
return encode(charSequence).equals(encodedPassword);
}
}
- 配置使用自定义的加密器
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
@Bean
PasswordEncoder passwordEncoder() {
return new Md5PasswordEncoder();
}
}
本来想一篇文章写完一个快速的配置教程,但是发现spring security 可以自定义的东西实在是太多,所以我打算拆分成两篇。