SpringSecurity
1. 快速入门
-
pom.xml中要有这个配置
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
写一个简单的controller
@RestController @RequestMapping("/test") public class TestController { @GetMapping("hello") public String add(){ return "hello"; } }
-
运行程序,控制台会出现密码
-
在浏览器中输入路径,会发现出现了这个页面
- 输入用户名user,以及刚才的密码即可登录,跳转到我们真正的页面
2. 两个重要的接口
- UserDetailsService接口:查询数据库用户名和密码过程
- 创建类继承UsernamePasswordAuthenticationFilter,重写三个方法
- 创建类实现UserDetailService,编写查询数据过程,返回User对象,这个User对象是安全框架提供的对象。
- PasswordEncoder接口:实现密码加密,用于返回User对象里面的密码加密
3. 认证和授权
3.1认证入门(3种方式)
(1)通过配置文件
spring.security.user.name=root
spring.security.user.password=sasa
(2)通过配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//加密
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String password = bCryptPasswordEncoder.encode("sasa");
auth.inMemoryAuthentication().withUser("root").password(password).roles("admin");
}
//对象加密需要用到这个对象,因此要new出来
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
}
(3)自定义编写实现类(最常用)
- 创建配置类,设置使用哪个UserDetailService实现类
- 编写实现类,返回User对象,对象中有用户名、密码和操作权限
@Configuration
public class MySecurityConfig 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();
}
}
@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//模拟数据库查询
List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User("root",new BCryptPasswordEncoder().encode("sasa"),auth);
}
}
(4)认证的实例(MyBatisPlus)
-
引入依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
-
创建数据库
创建users表,有id、username、password第三个字段
-
创建users表对应的实体类
@Data @NoArgsConstructor @AllArgsConstructor public class Users { private Integer id; private String username; private String password; }
-
配置数据库
spring.datasource.url=jdbc:mysql://localhost:3306/springsecuritydemo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=sasa spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-
整合MyBatisPlus
@Repository public interface UserMapper extends BaseMapper<Users> {}
-
在MyUserDetailsService调用mapper方法查询数据库进行用户认证
@Service("userDetailsService") public class MyUserDetailService implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //查询数据库 QueryWrapper<Users> wrapper = new QueryWrapper<>(); wrapper.eq("username",username); Users user = userMapper.selectOne(wrapper); //判断 if(user == null){ throw new UsernameNotFoundException("用户名不存在"); } List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auth); } }
-
在启动类上开启扫描
@SpringBootApplication @MapperScan("com.nnutyh.springsecurity.mapper") public class SpringsecurityApplication { public static void main(String[] args) { SpringApplication.run(SpringsecurityApplication.class, args); } }
3.2页面且无需认证即可访问
-
在配置类中实现相关的配置
@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防护 }
-
页面创建
<!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="登录"> </form> </body> </html>
-
编写Controller
@RestController @RequestMapping("/test") public class TestController { @GetMapping("/hello") public String add(){ return "hello"; } @GetMapping("/index") public String index(){ return "index"; } }
3.3基于角色或权限进行访问控制
授权
-
hasAuthority方法:如果当前的主体具有指定的权限,返回true,否则返回false
-
在配置类中设置当前访问地址有哪些权限
//当前登录用户,只有具有admins权限才能访问这个路径 http.formLogin().antMatchers("/test/index").hasAuthority("admins")
-
在UserDetailService,把返回的 User对象设置权限
//这里的admins要与config中相同,才能访问 List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("admins"); return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auth);
-
-
hasAnyAuthority方法:多个权限,多个角色,用这个
-
示例
http.formLogin() .antMatchers("/test/index").hasAnyAuthority("admins","manager")
-
在UserDetailService,与上同
-
-
hasRole方法:如果用户具备给定角色就允许访问,否则403
角色与权限的关系是多对多
-
示例
http.formLogin().antMatchers("test/index").hasRole("sale")
-
在UserDetailService,有区别,因为在源码中,创建Role时,会在你输入之前加上ROLE_,即输入的sale,最终变化为ROLE_sale
List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale"); return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auth);
-
-
hasAnyRole方法:表示用户具备任何一个条件都可以访问(与上同)
3.4 自定义403页面
-
创建页面
-
在配置类中
@Override protected void configure(HttpSecurity http) throws Exception { http.exceptionHandling().accessDeniedPage("/unauth.html"); }
3.5 注解的使用
-
@Secured
判断用户是否具有角色,注意,要加前缀
ROLE_
使用条件:在启动类上添加@EnableGlobalMethodSecurity(securedEnabled=true)
@GetMapping("/update") @Secured({"ROLE_admin","ROLE_sale"}) public String update(){ return "hello,update"; }
-
@PreAuthorize
进入方法之前进行权限验证
使用条件:在启动类上添加@EnableGlobalMethodSecurity(prePostEnabled=true)
@GetMapping("/update") @PreAuthorize("hasAnyAuthority('admins')") public String update(){ return "hello,update"; }
-
@PostAuthorize
方法执行之后进行验证
使用条件:在启动类上添加@EnableGlobalMethodSecurity(prePostEnabled=true)
没有权限,执行方法体,返回403
@GetMapping("/update") @PostAuthorize("hasAnyAuthority('admins')") public String update(){ return "hello,update"; }
-
@PostFilter
方法返回数据进行过滤,比如只返回用户为admin1的数据,其余的不返回
-
@PreFilter
传入方法数据进行过滤,比如只传入id大于1的数据
4. 用户注销
在配置类中添加退出的配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
}
5. 实现记住我
-
目前的方法
(1)cookie技术
(2)安全框架机制实现自动登录
-
原理分析
当认证成功后,会向浏览器中存储cookie,并把cookie和用户信息存到数据库中。
再次进行访问,获取cookie信息,拿cookie去数据库查询,查询对应信息即可认证成功。
-
具体实现
-
创建数据库
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;
-
修改配置类
注入数据源,配置操作数据库对象
//注入数据源 @Autowired private DataSource dataSource; //配置对象 @Bean public PersistentTokenRepository persistentTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); return jdbcTokenRepository; }
-
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .rememberMe() .tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(60)//单位秒 .userDetailsService(userDetailsService) }
-
在登录页面添加复选框
checkbox名字必须为
remember-me
<form action="/user/login" method="post"> 用户名:<input type="text" name="username"> <br> 密码:<input type="password" name="password"> <br> <input type="checkbox" name="remember-me">自动登录 <br> <input type="submit" value="登录"> </form>
-
效果
-
6.CSRF
跨站请求伪造(Cross-site request forgery),网页读取cookie去请求本没有权限的内容。
如何配置
-
在页面添加隐藏域
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
原理
- 生成csrfToken保存到HttpSession或Cookie中
- 访问时拿token与Session或Cookie中的token进行对比,如果相同就通过。