目录
密码编码器
授权决策
AffirmativeBased
ConsensusBased
UnanimousBased
授权
web授权
HttpSecurity常用方法及说明
方法授权
会话控制
会话超时
密码编码器
Spring Security为了适应多种多样的加密类型,又做了抽象,DaoAuthenticationProvider通过 PasswordEncoder接口进行密码的对比,而具体的密码对比细节取决于实现:
可以看到,PasswordEncoder接口中一共三个方法:
- encod方法:该方法用来给明文密码进行加密
- matches方法:该方法用来进行密码对比
- upgradeEncoding方法:该方法用来判断当前密码是否需要升级,默认返回false表示不需要升级
Spring Security提供很多内置的PasswordEncoder,能够开箱即用,使用某种PasswordEncoder只需要进行如下声明即可,如下:
@Bean
public PasswordEncoder passwordEncoder(){
// 定义明文比较
return NoOpPasswordEncoder.getInstance();
}
PasswordEncoder中常见的实现类:
- BCryptPasswordEncoder:使用BCrypt强哈希函数加密密码。
- NoOpPasswordEncoder:不对密码进行加密,即明文存储密码。
- Pbkdf2PasswordEncoder:使用PBKDF2强哈希函数加密密码。
- SCryptPasswordEncoder:使用SCrypt强哈希函数加密密码。
- StandardPasswordEncoder:使用SHA-256哈希函数加密密码。
授权决策
AccessDecisionManager采用投票的方式来确定是否能够访问受保护资源。
通过上图可以看出,AccessDecisionManager中包含的一系列AccessDecisionVoter将会被用来对Authentication 是否有权访问受保护对象进行投票,AccessDecisionManager根据投票结果,做出最终决策。
- ACCESS_GRANTED表示同意
- ACCESS_DENIED表示拒绝
- ACCESS_ABSTAIN表示弃权
- AffirmativeBased
- ConsensusBased
- UnanimousBased
AffirmativeBased
其逻辑是:
- 只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;
- 如果全部弃权也表示通过;
- 如果没有一个人投赞成票,但是有人投反对票,则将抛出AccessDeniedException。
Spring security 默认使用的是 AffirmativeBased 。
ConsensusBased
- 如果赞成票多于反对票则表示通过。
- 反过来,如果反对票多于赞成票则将抛出AccessDeniedException。
- 如果赞成票与反对票相同且不等于0,并且属性allowIfEqualGrantedDeniedDecisions的值为true(值默认为true),则表示通过,否则将抛出异常AccessDeniedException。
- 如果所有的AccessDecisionVoter都弃权了,则将视参数allowIfAllAbstainDecisions的值(值默认为false)而定,如果该值 为true则表示通过,否则将抛出异常AccessDeniedException。
UnanimousBased
它的逻辑与另外两种实现有点不一样,另外两种会一次性把受保护对象的配置属性全部传递 给AccessDecisionVoter进行投票,而UnanimousBased会一次只传递一个ConfifigAttribute给 AccessDecisionVoter进行投票。这也就意味着如果我们AccessDecisionVoter的逻辑是只要传递进来的 ConfifigAttribute中有一个能够匹配则投赞成票,但是放到UnanimousBased中其投票结果就不一定是赞成了。 UnanimousBased的逻辑具体来说是这样的:
- 如果受保护对象配置的某一个ConfifigAttribute被任意的AccessDecisionVoter反对了,则将抛出AccessDeniedException。
- 如果没有反对票,但是有赞成票,则表示通过。
- 如果全部弃权了,则将视参数allowIfAllAbstainDecisions的值而定,true则通过,false则抛出 AccessDeniedException。
Spring Security也内置一些投票者实现类如RoleVoter、AuthenticatedVoter和WebExpressionVoter等,可以自行查阅资料进行学习。
授权
授权的方式包括 web授权和方法授权,web授权是通过 url拦截进行授权,方法授权是通过 方法拦截进行授权。他 们都会调用accessDecisionManager进行授权决策,若为web授权则拦截器为FilterSecurityInterceptor;若为方法授权则拦截器为MethodSecurityInterceptor。如果同时通过web授权和方法授权则先执行web授权,再执行方法授权,最后决策通过,则允许访问资源,否则将禁止访问。
类关系如下:
web授权
通过给 http.authorizeRequests() 添加多个子节点来定制需求到我们的URL,如下代码:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()//屏蔽CSRF控制,即spring security不再限制CSRF
.authorizeRequests()
.antMatchers("/r/r1").hasAuthority("p1")//将资源与权限绑定
.antMatchers("/r/r2").hasAuthority("p2")//将资源与权限绑定
.antMatchers("/r/r3").hasRole("管理员")//将资源与角色绑定
.antMatchers("/r/r4").hasAnyAuthority("p1","p2")//将资源与权限数组绑定
.antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
.anyRequest().permitAll()//除了/r/**,其它的请求可以访问
.and()
.formLogin()//允许表单登录
.loginPage("/login-view")//指定我们自己的登录页,spring security以重定向方式跳转到/login-view
.loginProcessingUrl("/login")//指定登录处理的URL,也就是用户名、密码表单提交的目的路径
.successForwardUrl("/login-success");//指定登录成功后的跳转URL
// 自定义会话控制
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
// 自定义退出
http.logout()
.logoutUrl("/logout")// 退出的url
.logoutSuccessUrl("/login-view");// 退到/login-view
}
}
注:规则的顺序是重要的,更具体的规则应该先写
HttpSecurity常用方法及说明
方法 | 说明 |
---|---|
authenticated() | 保护URL,需要用户登录 |
permitAll() | 指定URL无需保护,一般应用与静态资源文件 |
hasRole(String role) | 限制单个角色访问,角色将被增加 “ROLE_” .所以”ADMIN” 将和 “ROLE_ADMIN”进行比较. |
hasAuthority(String authority) | 限制单个权限访问 |
hasAnyRole(String… roles) | 允许多个角色访问 |
hasAnyAuthority(String… authorities) | 允许多个权限访问 |
access(String attribute) | 该方法使用 SpEL表达式, 所以可以创建复杂的限制 |
hasIpAddress(String ipaddressExpression) | 限制IP地址或子网 |
openidLogin() | 用于基于 OpenId 的验证 |
headers() | 将安全标头添加到响应 |
cors() | 配置跨域资源共享( CORS ) |
sessionManagement() | 允许配置会话管理 |
portMapper() | 允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443 |
jee() | 配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理 |
x509() | 配置基于x509的认证 |
rememberMe | 允许配置“记住我”的验证 |
authorizeRequests() | 允许基于使用HttpServletRequest限制访问 |
requestCache() | 允许配置请求缓存 |
exceptionHandling() | 允许配置错误处理 |
securityContext() | 在HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用 |
servletApi() | 将HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用 |
csrf() | 添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用 |
logout() | 添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success” |
anonymous() | 允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS” |
formLogin() | 指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面 |
oauth2Login() | 根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证 |
requiresChannel() | 配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射 |
httpBasic() | 配置 Http Basic 验证 |
addFilterAt() | 在指定的Filter类的位置添加过滤器 |
方法授权
从Spring Security2.0版 本开始,它支持服务层方法的安全性的支持。主要有@PreAuthorize,@PostAuthorize, @Secured三类注解。
首先要开启Spring Security的 Secured、pre、Post 注解,在配置类上添加此注解
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
然后在controller中根据需求使用对资源授权,实例如下:
@RestController
public class LoginController {
@RequestMapping(value = "/login-success",produces = {"text/plain;charset=UTF-8"})
public String loginSuccess(){
//提示具体用户名称登录成功
return " 登录成功";
}
/**
* 测试资源1
* @return
*/
@GetMapping(value = "/r/r1",produces = {"text/plain;charset=UTF-8"})
@PreAuthorize("hasAuthority('p1')")//拥有p1权限才可以访问
public String r1(){
return " 访问资源1";
}
/**
* 测试资源2
* @return
*/
@GetMapping(value = "/r/r2",produces = {"text/plain;charset=UTF-8"})
@PreAuthorize("hasAuthority('p2')")//拥有p2权限才可以访问
public String r2(){
return " 访问资源2";
}
}
其@PreAuthorize中的参数与HttpSecurity的IPA相差无几
会话控制
我们可以通过以下选项准确控制会话何时创建以及Spring Security如何与之交互:
机制 | 描述 |
---|---|
always | 如果没有session存在就创建一个 |
ifRequired | 如果需要就创建一个Session(默认)登录时 |
never |
SpringSecurity
将不会创建
Session
,但是如果应用中其他地方创建了
Session
,那么
Spring
Security
将会使用它。
|
stateless | SpringSecurity将绝对不会创建Session,也不使用Session |
通过在继承WebSecurityConfigurerAdapter类来进行如下配置:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
}
}
- 若选用never,则指示Spring Security对登录成功的用户不创建Session了,但若你的应用程序在某地方新建了 session,那么Spring Security会用它的。
- 若使用stateless,则说明Spring Security对登录成功的用户不会创建Session了,你的应用程序也不会允许新建 session。并且它会暗示不使用cookie,所以每个请求都需要重新进行身份验证。这种无状态架构适用于REST API及其无状态认证机制。
会话超时
可以再sevlet容器中设置Session的超时时间,如下设置Session有效期为3600s;
spring boot 配置文件:
server.servlet.session.timeout=3600s
session超时之后,可以通过Spring Security 设置跳转的路径。
http.sessionManagement()
.expiredUrl("/login‐view?error=EXPIRED_SESSION")
.invalidSessionUrl("/login‐view?error=INVALID_SESSION");
安全会话cookie
- httpOnly:如果为true,那么浏览器脚本将无法访问cookie
- secure:如果为true,则cookie将仅通过HTTPS连接发送
server.servlet.session.cookie.http‐only=true
server.servlet.session.cookie.secure=true