SpringSecurity
Web安全方面的两个主要区域“用户认证(Authentication)”,“用户授权(Authorization)”
1、SpringSecurity特点:
- 和spring无缝整合
- 全面的权限控制
- 专门为web开发而设计
- 旧版本不能脱离web环境使用
- 新版本对整个框架进行了分层抽取,分成了核心模块和web模块,单独引入核心模块就可以脱离web环境
- 重量级
2、Shiro特点
- 轻量级,针对对性能有更高要求的互联网应用有更好的表现
- 通用性
- 好处:不局限与web环境
- 缺陷:在web环境下一些特定的需求需要动手编写代码定制
3、常见的安全管理技术栈的组合
- SSM+Shiro
- springboot/springcloud+springSecurity
4、入门案例
- 创建springboot工程
- 引入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 编写controller进行测试
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/show")
public String show(){
return "hello security";
}
}
5、UserDetailsService接口
当什么都不配置的时候SpringSecurity的账号密码是自动生成的,实际开发中需要去数据库中查询,我们要通过自定义逻辑控制认证逻辑!
需要自定义逻辑时,只需要实现UserDetailsService接口即可
UserDetailsService接口用来查数据库
用法:
* 创建类继承UsernamePasswordAuthenticationFilter,重写attemptAuthentication、successfulAuthentication、unsuccessfulAuthentication三个方法
* 创建类实现UserDetailsService,编写查询数据过程,返回User对象,这个User对象是安全框架提供的对象
6、PasswordEncoder接口
数据加密接口,用于返回User对象里面密码加密
7、设置登录的用户名和密码的三种方式
7.1、通过配置文件
- application.yml
spring:
security:
user:
name: username
password: password
7.2、通过配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 加密
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encodePwd = bCryptPasswordEncoder.encode("123123");
auth.inMemoryAuthentication().withUser("zzl").password(encodePwd).roles("admin");
}
// 加密过程中需要用到PasswordEncoder对象
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
7.3、自定义实现类设置
第一步 创建配置类,设置使用哪一个userDetailsService实现类
@Configuration
public class SecurityConfig3 extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
第二部 编写实现类,返回User对象,User对象有用户名密码和操作权限
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Collection<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User("zzl",new BCryptPasswordEncoder().encode("000000"),auths);
}
}
8、查询数据库完成用户认证
1、整合Mybatis-plus 完成数据库操作
- 引入相关依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2、创建数据库,数据库表
create DATABASE securityDemo;
use securityDemo;
create table users(
id int primary key,
username varchar(30) not null,
password varchar(30) not null
);
insert into users values(1,'zzl','zzl'),(2,'lll','lll');
select * from users;
3、创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users {
private int id;
private String username;
private String password;
}
4、整合Mybatis-plus,创建接口,继承BaseMapper接口
public interface UsersMapper extends BaseMapper<Users> {
}
5、在MyUserDetailsService调用Mapper里的方法查询数据库进行用户认证
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String username ) throws UsernameNotFoundException {
QueryWrapper<Users> wrapper= new QueryWrapper<>();
wrapper.eq("username",username);
Users user = usersMapper.selectOne(wrapper);
if(user == null){
throw new UsernameNotFoundException("用户名不存在!");
}
Collection<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auths);
}
}
6、在启动类上加入@MapperScan注解
9、自定义登录页面
在sercurity配置类中重写**configure(HttpSecurity http)**方法
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login.html") // 自定义登录页面
.loginProcessingUrl("/user/login")//登录页面的数据提交的URL
.defaultSuccessUrl("/test/index").permitAll()//登录成功之后,跳转路径
.and().authorizeRequests()
.antMatchers("/","/user/login","/test/show").permitAll()//设置哪些路径可以直接访问
.anyRequest().authenticated()
.and().csrf().disable();//关闭csrf防护
}
10、基于角色或权限进行访问控制
10.1、hasAuthority方法
如果当前主体有指定的权限,则返回true否则返回false
测试
在sercurity配置类中配置指定路径的访问权限
protected void configure(HttpSecurity http) throws Exception {
...
.antMatchers("/test/index").hasAuthority("admins")//给指定的路径设置指定的权限
...
}
10.2、hasAnyAuthority方法
如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true
protected void configure(HttpSecurity http) throws Exception {
...
.antMatchers("/test/index").hasAnyAuthority("admins","manager")//给指定的路径设置指定的权限
...
}
10.3、hasRole方法
如果当前主体具备指定角色,则返回true
protected void configure(HttpSecurity http) throws Exception {
...
.antMatchers("/test/index").hasRole("sale")//给指定的路径设置指定的角色
...
}
注意在业务层给用户添加角色时,必须以’ROLE_'开头
Collection<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
10.4、hasAnyRole方法
如果当前主体具备指定的任意角色,则返回true
protected void configure(HttpSecurity http) throws Exception {
...
.antMatchers("/test/index").hasAnyRole("sale","role")//给指定的路径设置指定的角色
...
}
11、自定义403没有权限访问页面
在sercurity配置类中配置指定路径的访问权限
protected void configure(HttpSecurity http) throws Exception {
//设置没有访问权限的错误页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
}
12、注解的使用
12.1、@Secured
判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀ROLE_
需要先开启使用注解的功能
@EnableGlobalMethodSecurity(securedEnabled=true)
public class SecuritydemoApplication {
public static void main(String[] args) {
SpringApplication.run(SecuritydemoApplication.class, args);
}
}
在指定的方法添加注解
@Secured({"ROLE_sale2"})
@GetMapping("/index")
public String index(){
return "hello index";
}
12.2、@PreAuthorize
需要先开启使用注解的功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PreAuthorize注解适合进入方法前的权限认证
@PreAuthorize可以将登陆用户的roles/permissions参数传到方法中
12.3、@PostAuthorize
需要先开启使用注解的功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PostAuthorize注解使用的并不多,在方法执行后在进行权限验证,适合验证带有返回值的权限
13、用户注销
在配置类中添加退出映射地址
protected void configure(HttpSecurity http) throws Exception {
...
//用户注销的地址,注销成功后访问的地址
http.logout().logoutUrl("/user/logout").logoutSuccessUrl("/test/index").permitAll();
...
}
14、基于数据库的自动登录
cookie技术是将数据存储到客户端,不安全
创建表
security可以自动创建
//手动创建
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null
);
在security配置类中添加数据源,配置操作数据库对象
// 注入数据源
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
// 自动创建表
// jdbcTokenRepository.setCreateTableOnStartup(true);
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
配置自动登录
@Override
protected void configure(HttpSecurity http) throws Exception {
...
.and().rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60)//设置有效时长
.userDetailsService(userDetailsService)
...
}
在登录界面添加复选框
<form method="post" action="/user/login">
用户名:<input type="text" name="username"/>
密码:<input type="text" name="password"/>
<input type="checkbox" name="remember-me"/>自动登录
<input type="submit" name="login"/>
</form>
15、CSRF理解
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。