文章目录
1.基本概念
1.1 认证
系统为什么要认证?
认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。
认证︰用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。
常见的用户身份认证方式有︰用户名密码登录,二维码登录,手机短信登录,指纹认证等方式。
1.2 会话
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。
session认证:
用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的sesssion_id 存放到 cookie 中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了
token认证:
用户认证成功后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份
两者比较:
- session保存在服务端,大量的用户进行登录操作,数据会存放大量的数据;会增加服务器开销
- token保存在客户端,不保存在数据库;不会增加服务器开销,性能更好
1.3 授权
为什么要授权?
认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,授权是在认证通过后发生的,控制不同的用户能够访问不同的资源
授权:授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问
1.4 授权数据模型
主体—权限—资源
主体(用户id、账号、密码、…)
权限(权限id、权限标识、权限名称、资源名称、资源访问地址、…)
角色(角色id、角色名称、…)
角色和权限关系(角色id、权限id、…)
主体(用户)和角色关系(用户id、角色id、…)
1.5 RBAC
RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限
- 基于角色的访问控制
- 基于资源的访问控制(推荐)
2.框架简介
1.SpringSecurity是什么
SpringSecurti基于Spring框架,提供了一套Web应用安全性的完整解决方案,一般来说,Web应用的安全性包括用户认证(Authenticataion)和用户授权(Authorization)两个部分,这两点也是Spring Security重要核心功能
(1)用户认证:用户是否能登录
(2)用户授权:用户是否有权限去做某些事情
2.SpringSecurity与Shiro对比
SpringSecurity特点:
(1)和Spring无缝整合
(2)全面的权限控制
(3)专门为Web开发而涉及
(4)重量级(缺点)
Shiro特点:
(1)轻量级
(2)通用性
总结: spring security功能强于Shiro,但比Shiro复杂
3.框架理解
3.1 入门案例
-
第一步:创建spring boot工程
-
第二步:引入相关依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
第三步:编写controller进行测试
@RestController @RequestMapping("/test") public class TestController { @GetMapping("hello") public String test() { return "hello,spring security"; } }
SpringSecurity登录默认用户名:user
登录密码:启动控制台中找到
3.2 基本原理
SpringSecurity本质是一个过滤器链,有很多过滤器
通过查看源码,可以发现:
- FilterSecurityInterceptor:是一个方法级的权限过滤器,基本位于过滤链的最底部
- ExceptionTranslationFilter:是个异常过滤器,用来处理再认证授权过程中抛出的异常
- UsernamePasswordAuthenticationFilter:对/login的POST请求做拦截,校验表单中用户名,密码
3.3 重要接口
1.UserDetailsService接口:
当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。
实现自己去查数据库的自定义认证步骤
- 写个类继承UsernamePasswordAuthenticationFilter,并重写该类的attemptAuthentication方法
- 然后在attemptAuthentication方面里边得到用户输入的用户名和密码,如果认证成功,则调用AbstractAuthenticationProcessingFilter类的successfulAuthentication,如果认证失败,则调用AbstractAuthenticationProcessingFilter类的unsuccessfulAuthentication
- 但查数据库里的用户名和密码则在UserDetailsService写你查数据库的过程
- 创建类继承UsernamePasswordAuthenticationFilter,重写三个方法attemptAuthentication,successfulAuthentication,unsuccessfulAuthentication
- 创建类实现UserDetailsService,编写查询查询数据库过程,返回User对象,这个User对象是SpringSecurity提供的对象
2.PasswordEncoder接口: 数据机密接口,用于返回User对象里面的密码
3.4 web项目权限方案
3.4.1 设置登录系统的账号和密码
-
方式一:通过配置文件
spring.security.user.name=admin spring.security.user.password=admin
-
方式二:通过配置类
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); //密码进行加密操作 String password = passwordEncoder.encode("123"); auth.inMemoryAuthentication().withUser("xin").password(password).roles("admin"); } //创建BCryptPasswordEncoder对象,否则报错:没有匹配关系 @Bean PasswordEncoder password() { return new BCryptPasswordEncoder(); } }
-
方式三:自定义编写实现类
-
编写配置类
@Service("userDetailsService") public class MyUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); return new User("zx", new BCryptPasswordEncoder().encode("123"), auths); } }
-
编写实现类,返回User对象,User对象有用户名密码和权限
@Configuration public class SecurityConfig2 extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(password()); } //创建BCryptPasswordEncoder对象,否则报错:没有匹配关系 @Bean PasswordEncoder password() { return new BCryptPasswordEncoder(); } }
-
3.4.2 实现数据库认证来完成用户登录
-
整合mybatisplus完成数据库操作
-
创建表和数据
-
实体类
-
整合mybatisplus制作mapper
-
配置文件添加数据库配置
-
完善登录实现类
@Service("userDetailsService") public class MyUserDetailsService implements UserDetailsService { @Autowired private UsersMapper usersMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //调用usersMapper方法,根据用户名查询数据库 QueryWrapper<Users> wrapper = new QueryWrapper<>(); wrapper.eq("username", username); Users users = usersMapper.selectOne(wrapper); //判断 if (users == null) { throw new UsernameNotFoundException("用户名不存在!"); } List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths); } }
-
启动类添加扫描器
-
添加配置类
3.4.3 自定义哪些请求需要登录认证
-
修改配置类
@Configuration public class SecurityConfig2 extends WebSecurityConfigurerAdapter { ... @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防护 } }
-
创建相关页面,controller
自定义登录页面:login.html<!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>
注意:页面提交方式必须为 post 请求,所以上面的页面不能使用,用户名,密码必须为username,password
原因:在执行登录的时候会走一个过滤器UsernamePasswordAuthenticationFilter
🔣表单的action配置的uri不需要配置controller(SpringSecurity默认帮我们做好了),同时这个uri必须和loginProcessingUrl方法里的值保持一致
3.4.4 基于角色或权限进行访问控制
-
第一个方法:hasAuthority方法(某个权限设置)
如果当前的主题具有指定的权限,则返回true,否则返回false-
在配置类设置当前访问地址有哪些权限
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() //自定义自己编写的登录页面 ... //当前登录用户,只有具有admins权限才可以访问这个路径 .antMatchers("/test/index").hasAuthority("admins") ... }
-
在userDetailService,把返回user对象设置权限
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins"); return new User(users.getUsername(), new BCryptPasswordEncoder().encode(users.getPassword()), auths);
-
-
第二个方法:hasAnyAuthority 方法 (设置多个权限)
.antMatchers("/test/index").hasAnyAuthority("admins,manager")
-
第三个方法:hasRole 方法 (单个角色)
如果当前主体具有指定的角色,则返回true.antMatchers("/test/index").hasRole("sale") List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale"); //springsecurity 会自动添加ROLE_前缀
-
第四个方法:hasAnyRole 方法(多个角色)
3.4.5 自定义403页面
-
修改访问配置类
@Override protected void configure(HttpSecurity http) throws Exception { //配置没有权限访问跳转自定义页面 http.exceptionHandling().accessDeniedPage("/unauth.html"); ... }
-
添加对应控制器
3.4.6 认证授权注解使用
使用注解先要开启注解功能:在启动类或配置类上添加注解
@EnableGlobalMethodSecurity(securedEnabled = true)
-
@Secured:用户具有某个角色,可以访问方法,相当于配置类中的hasRole()方法,参数要以 ROLE_开头
-
在控制器方法上添加注解:
@GetMapping("update") @Secured({"ROLE_sale","ROLE_manager"}) public String update() { return "hello,spring update"; }
-
userDetailsService设置用户角色
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
-
-
@PreAuthorize:进入方法前的权限验证
@GetMapping("update") //@Secured({"ROLE_sale","ROLE_manager"}) @PreAuthorize("hasAnyAuthority('admins')") public String update() { return "hello,spring update"; }
-
@PostAuthorize:执行方法后再进行权限验证,适合验证带有返回值的权限
@PostAuthorize("hasAnyAuthority('admins')")
-
@PreFilter:传入方法数据进行过滤
-
@PostFileter:方法返回数据进行过滤
3.4.7 用户注销
-
在配置类中添加退出的配置
//退出 http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
3.4.8 自动登录
流程:
具体实现:
-
创建数据库表 persistent_logins(非必要,可以在配置类中设置自动创建)
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; }
-
配置类配置自动登录
.and().rememberMe().tokenRepository(persistentTokenRepository())//设置数据库操作对象 .tokenValiditySeconds(60)//设置有效时长,单位为秒 .userDetailsService(userDetailsService)
-
在登录页面中添加复选框
<input type="checkbox" name="remember-me">自动登录
注意:name属性值一定要是remember-me