概述
SpringSecurity是基于Spring的安全框架,作用和shiro一样,用于认证和授权
SpringSecurity和shiro对比
- SpringSecurity
和Spring无缝结合
全面的权限控制
专门为Web开发而设计(旧版本不能脱离Web环境,新版本对整个框架进行了分层抽取,分成了核心模块和Web模块,单独引入核心模块就可以脱离Web环境)
重量级
- shiro
轻量级,shiro主张的理念是把复杂的事情变简单。针对性能又更高要求的互联网应用又更好表现
通用性(不局限于Web环境)
在Web环境下特定的需求(如特定的认证方式),需要手动编写代码定制,代码量大
- 在SSM中整合SpringSecurity相对于shiro来说更加麻烦,shiro虽然功能没有spring强大但在许多场景也够用了,而springboot提供的自动化配置方案省去了spring security的大量配置;所以一般常见的安全管理技术栈是SSM+Shiro;SpringBoot+SpringSecurity
入门案例
- 引入依赖spring-boot-starter-security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 编写controller进行测试
@RestController
@RequestMapping("security")
public class HelloWorld {
@GetMapping("test1")
public String test1(){
return "hello security!!";
}
}
运行起来后,在控制台会有密码的输出,访问该controller会跳转到login页面,账号默认为user
基本原理
SpringSecurity本质是一个过滤器链,每个过滤器都实现对应的功能,3个重要过滤器:
- FilterSecurityInterceptor:一个方法级的权限过滤器,基本位于过滤器链的最底层
- ExceptionTranslationFilter:异常过滤器,用于处理在认证授权过程中抛出的异常
- UsernamePasswordAuthenticationFilter:对/login的POST请求进行拦截,校验表单中的用户名和密码
过滤器的加载流程
- 用户通过SpringSecurity配置过滤器DelegatingFilterProxy(SpringBoot自动配置了),该过滤器的doFilter方法调用了自身的initDelegating方法,在initDelegating方法中加载了FilterChainProxy的bean
- FilterChainProxy的doFilter方法调用了自身的doFilterInternal方法,通过迭代器将所有需要加载的过滤器加载到过滤器链中
自定义开发的两个重要接口
UserDetailsService
当什么也没有配置的时候,账号和密码都是由SringSecurity自动生成的。而在实际项目中账号和密码都是从数据库查来的,所以要自定义认证逻辑;查询数据库得到用户名和密码就由UserDetailsService接口来实现,类似Shiro中的Realm
实现流程:
- 创建类集成UsernamePasswordAuthenticationFilter,重写三个方法
- 创建类实现UserDetailsService编写查询数据过程,返回User(UserDetails接口的实现)对象,这个User对象是Security提供的对象(包含用户名、密码、权限,以及用户状态)
PasswordEncoder
存储在数据库的密码不会是用户注册时输入的原始密码,而是加密过的;在认证的时候就需要对用户登录时输入的原始密码进行加密,该操作就由PasswordEncoder实现;security在对加密的密码进行认证时会去找用户配置的PasswordEncoder的实现
这个接口用于密码存储的加密,有三个方法:
-
String encode(CharSequence rawPassword);
:用于对前端接收到的密码进行编码 -
boolean matches(CharSequence rawPassword, String encodedPassword);
:将经过编码的密码和数据库查到的密码进行匹配 -
default boolean upgradeEncoding(String encodedPassword) {return false;}
:为了安全,如果需要重复编码提高安全性则返回true,默认实现返回false
SpringSecurity认证
设置登录的用户名和密码
- 通过配置文件
spring:
security:
user:
name: laowa
password: 123456
- 通过配置类,继承自WebSercurityConfigurerAdapter方法,重写它的configure方法
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//对密码进行加密
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String password = encoder.encode("123456");
auth.inMemoryAuthentication().withUser("laowa").password(password);
}
/**
* 配置对应加密方式的编码对象
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
- 自定义编写实现类实现UserDetailsService接口
@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//开发中这里进行数据库的访问
return new User("laowa3",new BCryptPasswordEncoder().encode("123456"),
AuthorityUtils.commaSeparatedStringToAuthorityList("role"));
}
}
自定义登录页面
- 在配置类中重写cofigure(HttpSecurity http)方法
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//自定义登录页面
.loginPage("/static/login.html")
//定义登录请求的路径
.loginProcessingUrl("/user/login")
//定义默认登陆成功后跳转的路径(在用户登录时有效,如果用户访问请求被拦截到登录页面则会跳转到用户访问的请求),permitAll表示任何用户都有权限
.defaultSuccessUrl("security/test1").permitAll()
//不需要认证就能访问的页面
.and().authorizeRequests().antMatchers("/","/security/test2","user/login").permitAll()
//所有请求都可以访问
.anyRequest().authenticated()
//关闭csrf防护
.and().csrf().disable();
}
用户注销
//设置注销路径和注销成功后跳转的路径
http.logout().logoutUrl("/logout")
.logoutSuccessUrl("/security/test2").permitAll();
自动登录功能
实现原理
操作步骤
- 创建数据库表,也可以让框架来创建,建表语句在JdbcTokenRepositoryImpl类中
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null);
- 配置类中注入数据源,配置数据库操作对象
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//是否在启动时自动创建表
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
- 开启自动登录
//添加自动登录的处理
http.and().rememberMe().tokenRepository(persistentTokenRepository())
//设置token有效时间
.tokenValiditySeconds(60)
//设置登录的业务逻辑类
.userDetailsService(userDetailsService)
- 前端页面登录表单添加复选框,name为remember-me
SpringSecurity授权
控制访问
- hasAuthority:如果当前主体具有**参数指定的权限(一个参数)**则返回true,否则返回false
- hasAnyAuthority:如果当前主体有任何提供的权限(多个权限参数,满足一个即可),给定的作为一个逗号分隔的字符串列表的化,返回true
- hasRole:如果当前主体具有参数指定的一个角色(为主体添加角色需要加前缀ROLE_)
- hasAnyRole:如果当前主体具有参数列表中的任意一个角色
自定义403
- 页面
//没有权限跳转到该页面
http.exceptionHandling().accessDeniedPage("403.html");
- 处理器
//实现接口AccessDeniedHandler,处理ajax请求
http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
常用注解
-
@EnableGlobalMethodSecurity(securedEnabled=true)
:用于启动类或配置类上,表示启用security的其他注解 -
@Secured
:用于controller的处理器方法上,参数value为角色集(注意需要加ROLE_前缀);表示这个资源只有拥有设置的角色的用户才能访问 -
@PreAuthorize
:在用户进入方法前进行权限的校验,参数可以为http判断权限和角色的四个方法的字符串hasRole、hasAnyRole、hasAuthor、hasAnyAuthor -
@PostAuthorize
:在方法执行之后进行权限的校验,与@PreAuthorize类似 -
@PostFilter
:对处理器方法的返回值及逆行过滤 -
@PreFilter
:对处理器方法的传入参数进行过滤
底层原理
认证流程
- UsernameAuthentationFilter调用父类AbstractAuthenticationProcessingFilter的doFilter方法
- 父类调用子类方法attemptAuthentication进行认证的过程
- 用户名和密码构建UsernamePasswordAuthenticationToken对象的过程
this.getAuthenticationManager().authenticate(authRequest);
认证过程(父接口AuthenticationManager调用子类ProviderManager实现的authenticate方法)
授权流程
权限访问的流程主要由ExceptionTranslationFilter和FilterSecurityInterceptor两个过滤器完成
- ExceptionTranslationFilter
- FilterSecurityInterceptor
认证信息与session的绑定