SpringSecurity学习笔记
约 2153 字大约 7 分钟
认证
判断用户是否合法
会话
避免每次操作都进行认证,将用户数据保存在会话中,通常基于session或token实现。


授权
认证通过后,控制不同用户访问不同的资源。(细粒度划分用户隐私)。
Subject
主体--用户或程序
Resource
资源--菜单、界面、按钮、方法等功能资源,数据库数据资源(实体资源)
Permission
权限/许可--对资源的查询、修改、添加权限
RBAC
基于角色的访问控制(RoleBaseAccessControl)
if(主体.hasRole(管理员)){
查询操作;
}else{
无权访问;
}基于资源的访问控制(ResourceBaseAccessControl)
if(主体.hasPermission(查询操作)){
开始查询;
}else{
无权访问;
}SpringSecurity
SpringSecurity的工作流程
当SpringSecurity初始化时会创建一个名为SpringSecurityFilterChain的Servlet过滤器链(由很多过滤器组成),类型为org.springframework.security.web.FilterChainProxy这里有很多个实现了javax.servlet.Filter接口的过滤器,所有外部请求都会通过这个类链进行过滤。这些过滤器会调用AuthenticationManager类进行认证,调用AccessDecisionManager类校验用户权限。具体流程如下
主要包括以下几种过滤器
SecuirtyContextPersistenceFilter
整个拦截过程的入口(第一个和最后一个拦截器)
UsernamePasswordAuthenticationFilter
处理表单用户名密码认证,在其内部存在两个处理器,分别是AuthenticationSuccessHandler登陆成功后的处理器和AuthenticationFailureHandler登录失败处理器,可以根据需要进行修改。
FilterSecuirtyInterceptor
用于保护web资源,使用AccessDecisionManager对当前用户进行权限校验
ExceptionTranslationFilter
用于捕获所有来自FilterChain的异常,并进行处理。默认只处理AuthenticationException和AccessDeniedException两个异常,其他的异常会继续抛出。
SpringSecurity认证
认证流程

SpringBoot集成SpringSecurity
添加pom依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>配置SpringSecurity
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //定义用户信息服务,自定义userDetailService之后可以去掉 // @Bean // public UserDetailsService getUserDetailService(){ // InMemoryUserDetailsManager manager=new InMemoryUserDetailsManager(); // manager.createUser(User.withUsername("tony").password("tony").authorities("p1").build()); // manager.createUser(User.withUsername("jack").password("jack").authorities("p2").build()); // return manager; // } //定义密码编码器 @Bean public PasswordEncoder getPasswordEncoder(){ //字符串比较密码编码器(测试用) //return NoOpPasswordEncoder.getInstance(); //bcrypt密码编码器 return new BCryptPasswordEncoder(); } //定义安全拦截 @Override protected void configure(HttpSecurity http) throws Exception{ http.authorizeRequests() //配置权限,没有权限访问会返回403 .antMatchers("/security/p1").hasAuthority("p1") .antMatchers("/security/p2").hasAuthority("p2") //所有/user/**和/redis/**请求必须认证通过 .antMatchers("/user/**","/redis/**").authenticated() //其他请求可以访问 .anyRequest().permitAll() //允许表单登录 .and().formLogin() //自定义登录成功的地址,post方式 .successForwardUrl("/user/login-success"); } }- 自定义UserDetailService
@Service public class MyUserDetailService implements UserDetailsService { /** * 根据用户名查询用户信息,正常需要数据库查询 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //密码加密,密码加密方式要和密码编码器相匹配,此处使用bcrypt编码 String pass="admin"; String passDB = BCrypt.hashpw(pass, BCrypt.gensalt()); UserDetails userDetails= User.withUsername(username).password(passDB).authorities("p1").build(); return userDetails; } }
SpringSecurity授权
授权流程图

投票决策操逻辑
根据不同的配置,会有不同的投票操作逻辑。AccessDecisionManager接口有三个实现类,AffirmativeBased,ConsensusBased,UnanimousBased,他们的操作逻辑分别是:
AffirmativeBased只要
AccessDecisionVoter的投票结果为ACCESS_GRANTED则统一用户访问;如果全部弃权,也表示通过;如果没有赞成,有反对,则抛出AccessDeniedException异常。SpringSecurity默认使用此中方式。ConsensusBased如果赞成多于反对则通过;反之抛出
AccessDeniedException异常;如果赞成数和反对数相等且不为0,并且
allowlfEqualGrantedDeniedDecisions值为true(默认为true)则通过;反之抛异常;如果所有AccessDecisionVoter都弃权,则根据
allowlfEqualGrantedDeniedDecisions的值确定,true则通过,false不通过。UnanimousBased全部通过才通过,有反对则抛异常。如果全部弃权,根据
allowlfEqualGrantedDeniedDecisions参数确定。
SpringSecurity基本功能设置
自定义登录页面
编辑登录页面
<form id="form" action="/login" method="post"> <span>用户名:</span> <input type="text" name="username"> <br> <span>密码:</span> <input type="password" name="password"> <br> <input type="submit" value="提交"> </form>添加登录页面controller
@Controller @RequestMapping("/page") public class PageController { @GetMapping("/login") public String doLogin(){ return "/login.html"; } }SpringSecurity配置文件配置登录页面
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //定义密码编码器 @Bean public PasswordEncoder getPasswordEncoder(){ //bcrypt密码编码器 return new BCryptPasswordEncoder(); } //定义安全拦截 @Override protected void configure(HttpSecurity http) throws Exception{ http.csrf().disable()//屏蔽csrf .authorizeRequests() //配置权限,没有权限访问会返回403 .antMatchers("/security/p1").hasAuthority("p1") .antMatchers("/security/p2").hasAuthority("p2") //所有/user/**和/redis/**请求必须认证通过 .antMatchers("/user/**","/redis/**").authenticated() //其他请求可以访问 .anyRequest().permitAll() //允许表单登录 .and().formLogin() //定义登录页面 .loginPage("/page/login") //定义登录操作url .loginProcessingUrl("/login") //自定义登录成功的地址,post方式 .successForwardUrl("/user/login-success") .failureForwardUrl("/user/login-fail"); } }将跟地址,重定向到登录页面(可不选)
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("redirect:/page/login"); } }
会话管理
获取用户身份信息
通过SecurityContextHolder获取到Context对象后可以查询用户信息,以获取用户名方法为例:
private String getUsername(){
String username;
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth.getPrincipal();
if(principal==null){
username="匿名用户";
}if(principal instanceof UserDetails){
UserDetails user = (UserDetails) principal;
username=user.getUsername();
}else{
username=principal.toString();
}
return username;
}会话控制
可以通过修改配置信息的方式配置session
@Override
protected void configure(HttpSecurity http) throws Exception{
//配置session
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
}具体参数包括:
| 机制 | 描述 |
|---|---|
| always | 如果没有session就创建 |
| ifRequired | 如果需要就创建session |
| never | 不会创建session,如果SpringSecurity使用到session时还是会创建 |
| stateless | 不会创建session也不会使用session |
默认情况,SpringSecurity会为每个登录成功的用户创建会话。在分布式环境中使用token进行认证,可以将此参数设置为stateless。
授权方式
web授权
基于资源授权
//此url(/security/p1)需p1权限 http.authorizeRequests().antMatchers("/security/p1").hasAuthority("p1");基于角色授权
http.authorizeRequests().antMatchers("/security/p1").hasRole("role_1");
方法授权
使用方法授权需要开启@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)注解,方法授权包括三个注解:@PreAuthorize,@PostAuthorize,@Secured,一般用于controller层
注解说明
//可以匿名访问
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
//具备TELLER权限可以访问
@Secured("ROLE_TELLER")
//前置拦截(方法执行前拦截)
//可以匿名访问
@PreAuthorize("isAnonymous()")
//拥有p1权限可访问
@PreAuthorize("hasAuthority('p1')")
//拥有任何一个权限可访问
@PreAuthorize("hasAnyAuthority('p1','p2')")
//后置拦截(方法执行后拦截),使用方法同前置拦截
@PostAuthrize分布式系统认证方案
系统认证基础
分布式系统认证授权需求
统一认证授权服务
每个系统都有认证服务的需求;针对不同客户端,支持各种认证方式
应用接入认证
安全的系统对接,开放统一的API给第三方
分布式认证可选方案
基于session的认证
每个应用服务都需要在session中存储用户身份信息。访问不同的服务需要复制session到相应的服务中。优点是安全性高。
基于token的认证
服务端不存储认证数据,认证通过后返回给客户端一个token。客户端每次访问服务,都携带token。优点可以实现分布式认证,更适合第三方接入,可减轻服务器存储压力。缺点是token中包含用户信息安全性低,每次访问请求数据量加大。
分布式系统认证架构图

OAuth2.0
OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者(如:微信,QQ)上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容。
OAuth2.0协议认证流程

SpringCloudSecurityOAuth2框架
SpringCloudSecurityOAuth2包含认证和资源两个服务。
- 认证服务(AuthorizationServer)包含对客户端、用户的校验,发布token等功能。AuthorizationEndpoint服务用户认证,默认url为
/oauth/authorize;TokenEndpoint用于访问令牌,默认url为/oauth/token - 资源服务(ResourceServer)对请求中的token解析鉴权。主要通过
OAuth2AuthenticationProcessingFilter校验令牌合法性