目录
结构
security会对所有进入系统的请求进行拦截,校验每个请求是否有权限访问他所要的资源,由Filter实现的安全框架
在初始化Spring Security时创建SpringSecurityFilterChain的过滤器,其类型为FilterChainProxy,他实现了Filter,外部请求会经过此类。
- AuthenticationManager(认证管理器)
- AcessDecisionManager(决策管理器)
流程说明:
所有请求:
1.在请求开始时**SecurityContextPersistenceFilter** 从SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。
2.在请求完成后将SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除 securityContextHolder所持有的 SecurityContext;
登陆流程:
- 但路径为登陆路径时,用户的登陆信息会被UsernamePasswordAuthenticationFilter中的UsernamePasswordAuthenticationToken封装成Authentication
- 然后将封装好的Authentication传给AuthenticationManager认证管理器进行认证
- 认证失败,到AuthenticationFailureHandler 登录失败处理器处理
- 认证成功则AuthenticationManager返回一个Authentication实例(包含的权限信息, 身份信息,细节信息通常要把密码轻触)
- SecurityContextHolder.getContext().setAuthentication(…)方法 保存SecurityContextHolder 安全上下文
- AuthenticationManager 实现认证的实现类时ProviderManager,他是一个List链式结构,存放了多种认证方式。AuthenticationProvider实现类为 DaoAuthenticationProvider,它的内部又维护着一UserDetailsService负责UserDetails的获取。最终 AuthenticationProvider将UserDetails填充至Authentication。
普通的登陆:
当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。
登出:
当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 **logoutSuccessHandler** 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。
未登录状态:
有些uri被人直接输入网址时,而又没有身份,可以继承**AuthenticationEntryPoint**自定义未登录结果返回
异常状态:
ExceptionTranslationFilter: 能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常: AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。
权限与配置
1.权限
1.UserDetails
Security 中的用户接口,其存储的就是用户信息 ,我们自定义用户类要实现该接口。
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
方法含义如下:
- getAuthorites:获取用户权限,本质上是用户的角色信息。
- getPassword: 获取密码。
- getUserName: 获取用户名。
- isAccountNonExpired: 账户是否过期。
- isAccountNonLocked: 账户是否被锁定。
- isCredentialsNonExpired: 密码是否过期。
- isEnabled: 账户是否可用。
2.GrantedAuthority
Security 中的用户权限接口,自定义权限需要实现该接口:
public class MyGrantedAuthority implements GrantedAuthority {
private String authority;
}
- authority 表示权限字段
需要注意的是在 config 中配置的权限会被加上 ROLE_ 前缀
比如我们的authorizeRequests().antMatchers("/test").hasRole("test"),配置了一个 test 权限但我们存储的权限字段 (authority)应该是 ROLE_test 。
3.UserDetailsService
Security 中的用户 Service,自定义用户服务类需要实现该接口:
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
return ....;
}
}
loadUserByUsername的作用在上文中已经说明,就是根据用户名查询用户对象
4.SecurityContextHolder
用户在完成登录后 Security 会将用户信息存储到这个类中,之后其他流程需要得到用户信息时都是从这个类中获得,用户信息被封装成 SecurityContext ,而实际存储的类是 SecurityContextHolderStrategy ,默认的SecurityContextHolderStrategy 实现类是 ThreadLocalSecurityContextHolderStrategy 它使用了ThreadLocal来存储了用户信息。
SecurityContextHolder.getContext().setAuthentication(…)
对于使用 token 鉴权的系统,我们就可以验证token后手动填充SecurityContextHolder,填充时机只要在执行投票器之前即可,或者干脆可以在投票器中填充,然后在登出操作中清空SecurityContextHolder。
5.Authentication
可以理解为authentication就是一组用户名密码信息 ,他也是一个接口
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
接口有4个get方法,分别获取
- Authorities:填充的是用户角色信息。
- Credentials:填充的是密码。
- Details:用户信息。
- Principal: 其填充的是用户名。
因此可以推断其实现类有这4个属性。这几个方法作用如下:
- getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。
- getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。
- getDetails: 获取用户的额外信息,(这部分信息可以是我们的用户表中的信息)
- getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails (UserDetails也是一个接口,里边的方法有getUsername,getPassword等)。
- isAuthenticated: 获取当前 Authentication 是否已认证。
- setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。
2.配置
在 WebSecurityConfigurerAdapter 这个类里面可以完成上述流程图的所有配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**/*.html", "/resources/**/*.js");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login_page")
.passwordParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/sign_in")
.permitAll()
.and().authorizeRequests().antMatchers("/test").hasRole("test")
.anyRequest().authenticated().accessDecisionManager(accessDecisionManager())
.and().logout().logoutSuccessHandler(new MyLogoutSuccessHandler())
.and().csrf().disable();
http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
http.addFilterAfter(new MyFittler(), LogoutFilter.class);
}
}
1. configure(AuthenticationManagerBuilder auth)
1.AuthenticationManager 的建造器,配置 AuthenticationManagerBuilder 会让Security 自动构建一个 AuthenticationManager
2.如果想要使用该功能你需要配置一个 UserDetailService 和 PasswordEncoder。
3.UserDetailsService 用于在认证器中根据用户传过来的用户名查找一个用户,PasswordEncoder 用于密码的加密与比对,我们存储用户密码的时候PasswordEncoder.encode() 加密存储,在认证器里会调用 PasswordEncoder.matches() 方法进行密码比对。如果重写了该方法,Security 会启用 DaoAuthenticationProvider 这个认证器,该认证就是先调用 UserDetailsService.loadUserByUsername 然后使用 PasswordEncoder.matches() 进行密码比对,如果认证成功成功则返回一个 Authentication 对象。
configure(WebSecurity web)
这个配置方法用于配置静态资源的处理方式,可使用 Ant 匹配规则。
configure(HttpSecurity http)
http
.formLogin()
.loginPage("/login_page")//登录页请求路径
.usernameParameter("username")//用户名属性名
.passwordParameter("password")//密码属性名
.loginProcessingUrl("/sign_in")//登录请求路径
.permitAll()//任意用户可访问。
1.登陆
//成功登陆进入的页面,必须时post
.successForwardUrl("/toMain")
//不能与successForwardUrl共存
//.successHandler(new MyAuthenticationSucessHandler("/main.html"))
.failureForwardUrl("/toError")
//不能与failureForwardUrl一起用
//.failureHandler(new ForwardAuthenticationFAilureHandler("http://app.dodoge.me/"))
2.设置权限
http
.authorizeRequests()
.antMatchers("/test").hasRole("test")//设定了访问/test要有test权限
.anyRequest()//表示所有请求
.authenticated()//表示已登录用户才能访问
.accessDecisionManager(accessDecisionManager());//表示绑定在 url 上的鉴权管理器
http.authorizeRequests()
//.regexMatchers(HttpMethod.GET,"/demo").permitAll()//限制只有get请求才能进入/demo
.antMatchers("/main1.html").access("hasRole('abc')")//角色赋予
//.antMatchers("/main1.html").hasIpAddress("127.0.0.1")//限制ip地址进入
3.登出相关配置
http
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new MyLogoutSuccessHandler())
4.鉴权失败
http
.exceptionHandling()
.accessDeniedHandler(new MyAccessDeniedHandler());
5.加入自定义的Filter
http.addFilterAfter(new MyFittler(), LogoutFilter.class);
http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
- addFilterBefore 加在对应的过滤器之前,
- addFilterAfter 加在对应的过滤器之后,
- addFilterAt 加在过滤器同一位置
注:调用 addFilterAt 方法插入的 Filter ,会在这个位置上的原有 Filter 之前执行
6.记住我功能
http.rememberMe()
//定义生效时间,单位秒
.tokenValiditySeconds(60)
//修改remem-me的固定名字
//.rememberMeParameter()
//自定义逻辑
.userDetailsService(userDetailsService)
.tokenRepository(persistentTokenRepository);//持久化操作让token
定义构造AccessDecisionManager的方法并在配置类中调用,配置参考 configure(HttpSecurity http) 说明:
public AccessDecisionManager accessDecisionManager(){
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(
new MyExpressionVoter(),
new WebExpressionVoter(),
new RoleVoter(),
new AuthenticatedVoter());
return new UnanimousBased(decisionVoters);
}
投票管理器会收集投票器投票结果做统计,最终结果大于等于0代表通过;每个投票器会返回三个结果:-1(反对),0(通过),1(赞成)。
借鉴:
工作原理:(48条消息) Spring Security的工作原理_-------江湖-------的博客-CSDN博客_springsecurity原理