一、什么是认证和授权
1.1、什么是认证
进入移动互联网时代,大家每天都在刷手机,常用的软件有微信、支付宝、头条,抖音等,下边拿微信来举例子说明认证相关的基本概念,在初次使用微信前需要注册成为微信用户,然后输入账号和密码即可登录微信,输入账号和密码登录微信的过程就是认证。 系统为什么要认证? 认证是为了保护系统的隐私数据与资源,用户的身份合法,方可访问该系统的资源。 认证︰用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法 方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录,二维码登录,手机短信登录,指纹认证,人脸识别等方式。
1.2、什么是会话
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。
1.2.1、基于session的认证
传统项目[前端和后端代码都在一个工程下]。
它的交互流程是,用户认证成功后,在服务端生成用户相关的数据【user对象】保存在session(当前会话setAttribute(key,user)中,发给客户端的sesssion_id存放到 cookie中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id 也就无效了。---有状态的方式。
1.2.2、基于Token的认证
token令牌模式---JWT生成
它的交互流程是,用户认证成功后,服务端生成一个token【唯一字符串】发给客户端,客户端可以放到 cookie 或localStorage等存储中,每次请求时带上token,服务端收到token通过验证后即可确认用户身份。---在服务端没有存储token的内容。只在客户端存储
基于session的认证方式由servlet规范定制,服务端要存储session信息需要占用内存资源,客户端需要支持cookie;基于token的方式则一般不需要服务端存储token,并且不限制客户端的存储方式。如今移动互联网时代更多类型的客户端[pC,android,IOS,]需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于token的方式更适合。
1.3、什么是授权
还拿微信来举例子,微信登录成功后用户即可使用微信的功能,比如,发红包、发朋友圈、添加好友等,没有绑定银行卡的用户是无法发送红包的,绑定银行卡的用户才可以发红包,发红包功能、发朋友圈功能都是微信的资源即功能资源,用户拥有发红包功能的权限才可以正常使用发送红包功能,拥有发朋友圈功能的权限才可以便用发朋友圈功能,这个根据用户的权限来控制用户使用资源的过程就是授权。
1.3.1、为什么要授权
认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,授权是在认证通过后发生的,控制不同的用户能够访问不同的资源。 授权:授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。
在实际开发中:认证和授权设计到的表结构:
用户---登录后---角色----权限----操作哪些接口资源
我们在一期项目时,也可以人为的完成认证和授权功能。但是比较麻烦。而且我们考虑的因素肯定没有别人多,所以我们可以使用别人的安全框架 来完成认证和授权功能。而这里常用的安全框架有: shiro 和 springsecurity. shiro这个框架之前班级讲解。 springsecurity[这个多。因为spring全家桶] 。
1.4、springsecurity
Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于 Spring 的应用程序的事实标准。
Spring Security 是一个专注于为 Java 应用程序提供身份验证和授权的框架。与所有 Spring 项目一样,Spring Security 的真正强大之处在于它可以轻松扩展以满足自定义需求.
Springsecurity它就是一个身份认证和授权的框架。----单单的使用是非常简单的---但是如果你想往深的理解它,比较复杂。
二、快速使用Springsecurity
整合springboot
(1)定义一个资源类
@RestController public class HelloController { @GetMapping("/hello") public String hello(){ System.out.println("hello==================="); return "快速入门springsecurity"; } }
(2)启动服务并访问该资源
它跳转到登录界面,原因:你使用了springsecurity启动依赖,它就会加载很多过滤器。当你访问某个资源时,没有登录。则UsernamePasswordAuthenticationFilter 跳转到登录界面。而且该界面是springsecurity提供了。 默认的账户为user 密码在控制台
三、自定义账户和密码
通过上面的代码,我们知道springsecurity帮你生成了一个默认的账户和密码。我们可以自定义账户和密码。 再到后面我们会使用数据库中的账户和密码。
(1)创建一个配置类并集成WebSecurity
//注意:如果你的springboot版本过高,则该WebSecurityConfigurerAdapter过时。 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { //构建认证的账户和权限 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //基于内存构建自定义账户和密码----【后面会基于数据库】 auth.inMemoryAuthentication() //账户 .withUser("admin") //密码 .password("123456") //该账户具有的角色 .roles("admin") .and() .withUser("ykq") .password("123456") .roles("test"); } }
![]()
输入你构建的 账户和密码发现无法登录, 原因: springsecurity的密码要求必须加密。你输入的密码和springsecurity加过密的密码是不一致的。 解决办法: 就是你输入的密码也要使用密码加密器来进行加密,这样才能密码匹配。
四、密码加密器
public class Test {
public static void main(String[] args) {
//PasswordEncoder:它是密码加密器的父容器。它下面有很多实现类。
//第一个方法: encode:加密。
//第二个方法: matches:匹配。
PasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
String encode1 = passwordEncoder.encode("123456");
String encode2 = passwordEncoder.encode("123456");
String encode3 = passwordEncoder.encode("123456");
/*
$2a$10$qvFsAAqdKwaofY/HpcyxVOicxEJ6HW9gbCUwxSsnQkil0dUAhurhS
$2a$10$t8uN186zqYb9scKEHraUiemINCKi3nusyz1TutHCtEjZJ5haIeRSG
$2a$10$SdE6bG9meaa/GWcokOIXBOrHA08PuCtGDjYhwNvPV4cpABQP/Dlk.
发现:同一个原文经过加密后,密文不同。每次加密得到的密文不一样。只要他们使用的是同一个加密器,那么就可以判断是否相同。
安全行比较高。
*/
System.out.println(encode1);
System.out.println(encode2);
System.out.println(encode3);
boolean matches1 = passwordEncoder.matches("123456", "$2a$10$qvFsAAqdKwaofY/HpcyxVOicxEJ6HW9gbCUwxSsnQkil0dUAhurhS");
boolean matches2 = passwordEncoder.matches("123456", "$2a$10$t8uN186zqYb9scKEHraUiemINCKi3nusyz1TutHCtEjZJ5haIeRSG");
boolean matches3 = passwordEncoder.matches("123456", "$2a$10$SdE6bG9meaa/GWcokOIXBOrHA08PuCtGDjYhwNvPV4cpABQP/Dlk.");
System.out.println(matches1);
System.out.println(matches2);
System.out.println(matches3);
}
}
五、获取当前用户信息
当前用户--登录的账户信息。security认证通过后,会把当前用户具有的信息SecurityContext类中。你可以把它当作session对象。
@RestController @RequestMapping("user") public class UserController { @GetMapping("info") public Authentication info(){ //获取security上下文对象-- SecurityContext securityContext = SecurityContextHolder.getContext(); //用户的认证信息都封装到Authentication类中 Authentication authentication = securityContext.getAuthentication(); return authentication; } }
六、授权的功能
如果当前用户具有某个权限,方可访问对应的资源。授权是发生再认证后。
admin: user:insert user:query user:delete user:update
ykq: user:query user:export
//构建认证的账户和权限. @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //基于内存构建自定义账户和密码----【后面会基于数据库】 auth.inMemoryAuthentication() //账户 .withUser("admin") //密码--数据库 // .password("$2a$10$qvFsAAqdKwaofY/HpcyxVOicxEJ6HW9gbCUwxSsnQkil0dUAhurhS") .password(passwordEncoder.encode("123456")) //该账户具有的角色 .roles("admin") //该账户分配权限 .authorities("user:delete","user:insert","user:update","user:query") .and() .withUser("ykq") // .password("$2a$10$SdE6bG9meaa/GWcokOIXBOrHA08PuCtGDjYhwNvPV4cpABQP/Dlk.") .password(passwordEncoder.encode("123456")) .roles("test") .authorities("user:query","user:export"); }
我们上面的代码模拟了两个账户,并为这两个账户分配了相应的权限。 这些权限可以操作哪些资源。需要人为的绑定。
@GetMapping("delete") public String delete(){ return "用户删除的操作"; } @GetMapping("insert") public String insert(){ return "用户添加的操作"; } @GetMapping("update") public String update(){ return "用户修改的操作"; } @GetMapping("query") public String query(){ return "用户查询的操作"; } @GetMapping("export") public String export(){ return "用户导出的操作"; }
修改security类
@Override protected void configure(HttpSecurity http) throws Exception { //放行登录表单资源 http.formLogin().permitAll(); //资源绑定 http.authorizeRequests() //访问哪些资源时,需要具有的权限。 .antMatchers("/user/delete").hasAuthority("user:delete") .antMatchers("/user/insert").hasAuthority("user:insert") .antMatchers("/user/query").hasAuthority("user:query") .antMatchers("/user/update").hasAuthority("user:update") .antMatchers("/user/export").hasAuthority("user:export"); //其他请求必须认证后才能访问 http.authorizeRequests().anyRequest().authenti cated(); }
七、使用注解完成权限的绑定
上面我们是通过代码的形式,对权限和资源进行了绑定关系。这样会非常麻烦。 我们security就提高了注解的方式。
修改配置类
开启security的注解
再相应的资源方法上使用权限注解
@GetMapping("delete") @PreAuthorize(value = "hasAuthority('user:delete')") public String delete(){ return "用户删除的操作"; } @GetMapping("insert") @PreAuthorize(value = "hasAuthority('user:insert')") public String insert(){ return "用户添加的操作"; } @GetMapping("update") @PreAuthorize(value = "hasAuthority('user:update')") public String update(){ return "用户修改的操作"; } @GetMapping("query") @PreAuthorize(value = "hasAuthority('user:query')") public String query(){ return "用户查询的操作"; } @GetMapping("export") @PreAuthorize(value = "hasAuthority('user:export')") public String export(){ return "用户导出的操作"; }
八、零散的知识
@Override
protected void configure(HttpSecurity http) throws Exception {
//放行登录表单资源
http.formLogin()
.loginProcessingUrl("/login")
//登录成功后跳转的路径。默认跳转之前的路径。 success提交方式必须为post提交
.successForwardUrl("/success")
.permitAll();
//设置权限不足时,跳转的路径
http.exceptionHandling().accessDeniedPage("/fail");
//资源绑定
// http.authorizeRequests()
// //访问哪些资源时,需要具有的权限。
// .antMatchers("/user/delete").hasAuthority("user:delete")
// .antMatchers("/user/insert").hasAuthority("user:insert")
// .antMatchers("/user/query").hasAuthority("user:query")
// .antMatchers("/user/update").hasAuthority("user:update")
// .antMatchers("/user/export").hasAuthority("user:export");
//其他请求必须认证后才能访问
http.authorizeRequests().anyRequest().authenticated();
}
九、查看security认证的源码流程
UsernamePasswordAuthoricationFilter账户密码认证过滤器。
这里的UserDetailsService是
如果想使用自定义,则可以自建创建一个类并实现UserDeatailService接口,并把该类交于容器来管理。
十、基于数据库完成认证
10.1、创建一个类并实现UserdetailService接口
@Service
public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("执行了自己根据账户查询用户信息====================");
return null;
}
}
10.2、修改security配置类