9.1 Spring Security简介
Spring Security 是基于Spring AOP和Servlet规范中的Filter实现的安全框架,能够在Web请求级别和方法调用级别处理身份认证和授权。
Spring Security 从两个角度来解决安全性问题。它使用Servlet 规范中的Filter的保护Web请求并限制URL级别的访问。Spring Security 通过使用Spring AOP 保护方法调用——借助于对象代理和使用通知,能够确保只有具备适当权限的用户才能访问安全保护的方法。
9.1.1 Speing Security的模块
9.1.2 过滤Web请求
通过web.xml或者AbstractSecurityWebApplicationInitializer的子类配置 DelegatingFilterProxy,都会拦截发往应用中的请求,并将请求委托给ID为 springSecurityFilterChain 的bean。
9.1.3 简单的安全性配置
@Configuration
@EnableWebSecurity // ->启用web应用的安全性功能
public class SecurityConfig extends WebSecurityConfigurerAdapter{
}
使用SpringMVC开发,则用 @EnableWebMvcSecurity替代@EnableWebSecurity,通过重载 WebSecurityConfigurerAdapter的是哪个configure()方法来配置Web安全性。
方法 | 描述 |
---|---|
configure(WebSecurity) | 通过重载,配置Spring Security的Filter链 |
configure(HttpSecurity) | 通过重载,配置如何通过拦截器保护请求 |
configure(AuthenticationManagerBuilder) | 通过重载,配置user-detail服务 |
默认的configure(HttpSecurity) 代码
prorected void configure(HttpSecurity http) throws Exception{
http
.authrizeRequests()
.anyRequest().authenicated() // 所有进入应用的HTTP请求都要验证
.and()
.formLogin().add() // 支持基于表单的登录以及HTTP Basic方式的认证
.httpBasic();
}
//单没有用户存储支撑认证过程,没有用户存储,实际上就等于没有用户,没人能登录成功
- 配置用户存储
- 指定哪些请求需要认证,哪些请求不需要认证,以及所需要的权限;
- 提供一个自定义的登录界面,替代原来简单的默认登录页。
9.2 选择查询用户信息的服务
9.2.1 基于内存的用户存储
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication() //启用内存用户存储
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER","ADMIN");
}
}
配置用户详细信息的方法
方法 | 描述 |
---|---|
accountExpired(boolean) | 定义账号是否已经过期 |
accountLocked(boolean) | 定义账号是否已经锁定 |
and() | 用来连接配置 |
authorities(GrantedAuthority…) | 授予某个用户一项或多项权限 |
authorities(List<? extendsGrantedAuthority…> ) | 授予某个用户一项或多项权限 |
authorities(String…) | 授予某个用户一项或多项权限 |
credentialsExpired(boolean) | 定义凭证是否已经过期 |
disabled(boolean) | 定义账号是否已被禁用 |
password(String) | 定义用户的密码 |
roles(String…) | 授予某个用户一项或多项角色 |
以上方法在学习实现过程中,发现了已经有几个地方出现了更改
1.@EnbaleWebMvcSecurity已被启用 -> 改为@EnableWebSecurity
2.执行测试之后出现异常
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:250) ~[spring-security-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:198) ~[spring-security-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$LazyPasswordEncoder.matches(WebSecurityConfigurerAdapter.java:592) ~[spring-security-config-5.2.2.RELEASE.jar:5.2.2.RELEASE]
测试成功的代码
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) //启用内存用户存储
.withUser("user").password(new BCryptPasswordEncoder().encode("password")).roles("USER").and()
.withUser("admin").password(new BCryptPasswordEncoder().encode("password")).roles("USER","ADMIN");
}
}
9.2.2 基于数据库表进行认证
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"select user_id,user_pwd,true from t_user where user_id=?"
)
.authoritiesByUsernameQuery(
"select user_id,'ROLE_USER' from t_user where user_id=?"
)
.passwordEncoder(NoOpPasswordEncoder.getInstance());
//.passwordEncoder(new BCryptPasswordEncoder());
}
}
注意 passwordEncoder( )这个方法,Spinrg实战第4版给讯息:Spring Security的加密模块包括了三个这样的实现:BCryptPasswordEncoder、NoOpPasswordEncoder和StandardPasswordEncoder,也可以自己实现特定的加密解密算法。
不管使用那一个密码转码器,都需要劣迹一点,数据库中的密码是永远不会解码的。所采取的的策略与之相反,用户在登录时输入的密码会按照相同的算法进行转码,然后再与数据库中已经转码过的密码进行对比。
简单的说就是 如果你数据库中存的是明文密码,即假设123456,那是不需要这个属性的,使用NoOpPasswordEncoder!但是这个“无”加码器与后面的那个标准解码器在新版本的Spring Security已经被弃用了,但是出于安全考虑的启用,官方推荐不要使用明文存储密码 。
所以要么使用启用的No··Encoder 要么在数据库存储的数据就采用算法进行加密
9.2.3 基于LDAP进行认证
9.2.4 配置自定义的服务
如果内置的用户存储无法通用认证需求时,才有配置自定义的必要,假设用户存储在NoSQL中
public class SpitterUserService implements UserDetailsService {
@Autowired
private SpitterRepository spitterRepository; //注入
@Override
public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
Spitter spitter=spitterRepository.findByUserId(userId); //查找Spitter
if(spitter!=null){
List<GrantedAuthority> authorities=new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_SPITTER")); //创建权限列表
return new User( //返回User
spitter.getUserId(),
spitter.getUserPwd(),
authorities);
}
throw new UserIdNotFoundException();
}
}
}
@Autowired
SpitterRepository spitterRepository;
@Override
protected void configure(AuthenticationManagerBuilder auth) throw Exception{
auth.userDetailsService(new SpitterUserService(spitterRepository));
}
通过实现UserDetailsService 可以不管用户数据在哪,只会查找Spitter对象,甚至可以伪造一个,也不用关心底层所使用的数据存储,只是获得Spitter对象,并用它来创建User(User是UserDetails的具体实现)