SpringSecurity基本原理
SpringSecurity本质
SpringSecurity本质是一个过滤器链:SpringSecurity底层有很多的过滤器。
底层有以下三个比较重要的过滤器:
FilterSecurityInterceptor
:是一个方法级的权限过滤器,基本位于过滤器的最底部。
ExceptionTranslationFilter
:是个异常过滤器,用来处理在认证授权过程中抛出的异常。
UsernamePasswordAuthenticationFilter
:对/login的POST请求做拦截,校验表单中用户名和密码。
SpringSecurity过滤器方式
使用
SpringSecurity
需要先配置过滤器
SpringBoot
帮助我们做了这一步。(ps:想了解可以自行了解)
开发过程中2个重要的接口
UserDetailService
接口:
实际开发中,我们的账号密码都是从数据库中查出来的,所以我们就需要通过自定义逻辑控制认证逻辑。这就需要我们去实现UserDetailService
接口。
过程:
- 创建一个类继承
UsernamePasswordAuthenticationFilter
==>重写attempAuthentication
方法,校验成功则调用successfulAuthentication
,失败则调用unsuccessfulAuthentication
- 创建类实现
UserDetailService
接口,编写查询数据库的过程,返回User对象,这个对象就是安全框架提供的对象
PasswordEncoder
接口:
数据加密接口,用于User对象里面密码加密
WEB权限方案
认证
设置登录的用户名和密码
第一种方式:通过配置文件
第二种方式:通过配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//加密
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode = passwordEncoder.encode("123");
auth.inMemoryAuthentication().withUser("lucy").password(encode).roles("admin");
}
//必需
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
第三种方式:自定义编写实现类
1.创建配置类,设置使用哪个UserDetailService
实现类
@Configuration
public class SecurityConfig 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();
}
}
2.编写实现类,返回user对象,User对象有用户名密码和操作权限
@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User("mery",new BCryptPasswordEncoder().encode("123"),auths);
}
}
整合Mybatis-Plus完成数据库操作
@Repository
public interface UsersMapper extends BaseMapper<Users> {
}
@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//调用mapper里面的方法去查数据库
QueryWrapper<Users> usersQueryWrapper = new QueryWrapper<>();
usersQueryWrapper.eq("username",username);
Users user = usersMapper.selectOne(usersQueryWrapper);
//判断 没有则认证为空
if (user == null) {
throw new UsernameNotFoundException("用户名不存在!");
}
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(user.getUsername(),
new BCryptPasswordEncoder().encode("123"),auths);
}
}
启动类:
@SpringBootApplication
@MapperScan("com.mu.SpringSecurity.mapper")
public class SpringSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityApplication.class, args);
}
}
授权
自定义设置登录页面
1.在配置类种实现相关的配置
@Configuration
public class SecurityConfig 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();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() //自定义编写的页面
.loginPage("login.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/test/index") //登录访问路径
.permitAll()
.and().authorizeRequests()
.antMatchers("/","/test/hello","user/login") //设置哪些路径可以直接访问,不需要认证
.permitAll()
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
}
@RestController
@RequestMapping("/test")
public class Test {
@GetMapping("/hello")
public String hello(){
return "hello";
}
@GetMapping("/index")
public String index(){
return "hello,index";
}
}
2.创建相关的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
用户名:<input type="text" name="username">
<br/>
密码:<input type="password" name="password">
<br/>
<input type="submit" value="login">
</form>
</body>
</html>
基于角色或权限进行访问控制
1基于权限进行访问控制
1.1.hasAuththority方法
如果当前的主体具有指定的权限,则返回true,否则返回false
1.在配置类设置当前访问地址有哪些权限
2.在UserDetailsService中,把返回User对象设置权限
(ps
:如没有权限,则会报403错误,)
该方法适用于当前主体具有指定权限.当主题有多个权限的时候,就不行了,就需要我们使用hasAnyAuthority
方法.
1.2.hasAnyAuthority方法
针对多个权限或多个角色
当前的主体中只要有admins,manager其中一个权限就可以访问
2基于角色进行访问控制
2.1.hasRole方法
如果用户具备给定角色就允许访问,否则出现403.
如果当前主题具有指定的角色,则返回true.
底层源码:
这里的hasRole("sale")的返回值就是ROLE_sale.
然后再在MyUserDetailsService中,添加权限
而在配置文件中则不需要添加这个"ROLE_"前缀,因为上述的底层代码会自动添加与之进行匹配.
2.2.hasAnyRole方法
类似hasAnyAuthority
方法,是针对用户拥有多个角色.
给用户添加角色:
修改配置文件:
自定义403页面
编写前端页面:
unauth.html
<body>
<h1>对不起,您没有访问权限!</h1>
</body>
在配置文件中直接配置:
http.exceptionHandling().accessDeniedPage("/unauth");
添加对应控制器:
@GetMapping("/unauth")
public String accessDenyPage(){
return "unauth";
}
注解使用
@Secured:
判断是否有这个角色,有这个角色可以访问方法,另外需要注意的是这里匹配的字符串需要添加前缀"ROLE_"。
1、在启动类/配置类上先开启注解功能!
@EnableGlobalMethodSecurity(securedEnabled=true)
@SpringBootApplication
@MapperScan("com.mu.SpringSecurity.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityApplication.class, args);
}
}
2、在Controller的方法上使用这个注解
@GetMapping("update")
@Secured({"ROLE_sale","ROLE_manager"})
public String update(){
return "hello,update";
}
3、在MyUserDetailService种设置用户的角色
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
@PreAutorize
进入方法前进行方法的验证。
1、在启动类上进行开启
@EnableGlobalMethodSecurity(prePostEnabled = true)
2、在方法上添加注解
@PreAuthorize("hasAnyAuthority('admins')")
@GetMapping("update")
public String update(){
return "hello,update";
}
@PostAuthorize
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限.
1、先开启注解功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
2、在方法上面加上该注解
@PostAuthorize("hasAnyAuthority('admin')")
@GetMapping("update")
public String update(){
return "hello,update";
}
用户注销
1、在配置类中添加退出配置
http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll
记住我(自动登录)功能实现
功能实现
1.创建数据库表
CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.配置类,注入数据源,配置操作数据库对象
//注入数据源
@Autowired
private DataSource dataSource;
// 配置对象
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
// 自动创建表
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
3.配置类配置自动登录
.and().rememberMe().tokenRepository(persistentTokenRepository()) //设置记住我
.tokenValiditySeconds(60) //设置有效时长,单位秒
.userDetailsService(userDetailsService)
4.在登录页面添加复选框
<input type="checkbox" name="remember-me">自动登录
(ps:这里的name属性必须为"remember-me",不然系统无法找到)
CSRF理解
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-clickattacksession riding,通常缩写为 CSRFXSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的.
案例
在登录页面添加一个隐藏域:
<input type="hidden"th:if="${_csrf}!=null"th:value="${_csrf.token}"name="_csrf"/>
关闭安全配置的类中的csrf
http.csrf().disable();