Spring Security 中文文档 参考手册 中文版 (springcloud.cc)
一 springsecurity
1 SpringSecurity框架简介
- 安全框架:解决系统安全问题的框架。
- apache shire:轻量化。易于使用(配置简单)。功能够用(可满足大部分的需求)。一些特殊的需求无法实现。
- springsecurity:
- 重。难学(配置繁琐)。功能强大。
- spring全家桶的一员,所以和spring项目(springboot项目)集成更加容易。
- springsecurity底层利用的就是spring的4大特性:springbean、spring ioc、di(控制反转,依赖注入)、aop(面向切面编程)。
- 认证:用户是否能登录。
- 授权:用户是否能执行一个动作。
- 权限:
- 前端角度:菜单是否展示、按钮能不能点击
- 后端角度:权限对于后台而言都是一个一个完整的URL,你去控制权限,就是去控制这些URL。
2 快速入门demo(登录成功,跳转到首页)
(1)Please sign in登录页面
- springsecurity默认提供的登录页面。
- 因为springboot项目中添加了springsecurity起步依赖(组件),所以在项目启动后springsecurity默认会拦截所有的请求(而且我没有做任务的配置),要求你登录以后才能访问你具体的地址。比如,拦截项目的访问(localhost:8080,任何页面:login.html、main.html)。
- 默认用户名:user,密码:Using generated security passowrd
- 登录报404、返回报403,后面讲。
- 登录后,输入你想访问的rul即可。
(2)UserDetailsService详解
- 前因
- 只引入springsecurity的起步依赖包,不做任务配置的情况下。springsecurity会自动拦截所有的请求,要求使用它的用户名(admin)密码(随机,控制台打印)去登入。
- 因此我们不可能使用springsecurity,给我们提供的用户名和密码,我们需要使用的是数据库中的用户名和密码。
- 功能“集合”:自定义登录(认证)逻辑
- 神的冰箱:
- UserDetailsService.loadUserByUsername()接口
- 默认实现类1:JdbcUserDetailsManager:用jdbc去实现
- 默认实现类2:UserDetailsManager,专门的一个管理器。
- 默认实现类3:InMemoryUserDetailsManager,基于内存的,即可以通过配置把自定义的用户名和密码放到内存里面。应用场景:在测试阶段使用,在真正做开发的时候很少用到。
- 抛出的异常:UsernameNotFoundException extends AuthenticationException(认证异常)
- 返回值:UserDetails接口
- 返回值实现类:User类(UserDetails的实现)
- 注意:在使用springsecurity以后,要注意项目中关于User实体类的定义了。
- 工作原理:自定义登录(认证)逻辑
- 第一步:自定义UserDetailsService实现类
- 第二步:传用户名,调用UserDetailsService.loadUserByUsername(String userName)
- 第三步:底层去数据库查询
- 第1步:如果用户名为空或找不到用户,抛出异常UsernameNotFoundException
- 第2步:用户名存在为前提,如果密码为空或不一致(是否一致的比较是在UserDetailsService.loadUserByUsername(String userName中进行的),也会抛出异常。
- 注意:数据库中的密码要做加密处理
- 第四步:UserDetailsService.loadUserByUsername()返回UserDetails对象
(3)PasswordEncoder 密码解析器详解
- 前因
- 前端密码:前端给传过来的登录密码是明文的。
- 密码加密:用户注册时,把加好密的密码存入数据库。
- 功能“集合”:
- 功能1:加密密码
- 功能2:解密密码
- “神的”冰箱:
- PasswordEncoder(接口) 密码解析器
- 建议使用的实现在类1:BCryptPasswordEncoder:基于哈希算法的加密(单向)实现类。
- 演示:
-
// 创建解析器PasswordEncoder pw = new BCryptPasswordEncoder ();// 对密码加密String encode = pw . encode ( "123" );System . out . println ( encode );// 判断原字符和加密后内容是否匹配boolean matches = pw . matches ( "1234" , encode );System . out . println ( "===================" + matches );
-
3 认证:http.formLogin()
(1)自定义登录逻辑
- 第一步:在Spring容器中注入PasswordEncoder
- 第二步:实现UserDetailsService.loadUserByUsername()接口
- 第1步:根据username到数据库查询
- 第2步:根据查询到的对象比较密码
- 第3步:返回UserDetails对象
-
return new User(username,password, AuthorityUtils. commaSeparatedStringToAuthorityList("admin,normal"));,给我们的用户准备了2个权限,一个是admin,一个是normal。
-
- 第三步:验证
- 第1步:重启项目
- 现象1:控制台没有自动生成随机密码了。
- 第2步:成功进入404页面
- 注意:404页面是因为我们没有做映射关系,localhost:8080到lmain.html的跳转。
- 第1步:重启项目
(2)自定义登录页面
- 前端页面
- method(请求方式)必须为post
- 用户名(name)必须为username
- 密码(name)必须为password
- 因为在进行登录之前呢,springsecurity会有一个过滤器链(拦截器链)被执行。
- java编码
- 第一步:配置类继承WebSecurityConfigurerAdapter
- 注意:WebSecurityConfigurerAdapter是springsecurity中最重要的一个类,因为我们所有的配置(配置自定义登录页面、定义登录成功 失败 页面、自定义登录成功 失败 Handler)都是要以继承这个类为前提。
- 第二步:重写configure方法,配置自定义登录页面
- 第三步:重启项目
- 第四步:验证
- 第1步:localhost:8080,自定义的用户名密码,404(正确的)
- 第2步:localhost:8080/login.html,可以访问
- 第五步:问题:localhost:8080/main.html,也可以直接访问,一点拦截都没有。一般来说,登录页面是可以放行的,但其它页面(如main.html),只能是经过login.html登录或注册后才能到达的。
- 第六步:解决方案:授权配置,所有请求都需要被认证(先登录(页面))。
- 第七步:重启项目
- 第八步:验证时,又有问题:localhost:8080/login.html进入死循环
- 第九步:解决方案:放行(不拦截)以下页面:"/login.html","/login","/error.html"
- 第十步::重启项目
- 第十一步:验证时,又又有问题:localhost:8080/login.html点击登录以后,没有跳转到main.html。通过System.out.println打印可以看出,根本没有执行我们的自定义登录逻辑:UserDetailsService.loadUserByUsername(String userName)。
- 第十二步:解决方案:
- 第a步:配置自己登录逻辑
- 第b步:配置csrf
- 注意:可以简单地把csrf理解为一个防火墙。
- 第十三步:验证,又又又有问题,login.html登录成功以后,没有做跳转(main.html),直接报404错误。
- 第十四步:解决方案:配置登录成功后跳转的页面。
- 第十五步:验证,又又又又有问题,这加报405
- 第十六步:解决方案:
- 第1步:自定义XxxController.java类post请求对应的post方法
- 第2步:在配置中定,代表团//登录成功后跳转页面,此POST请求地址
- 第一步:配置类继承WebSecurityConfigurerAdapter
- 注意:自定义的XxxController中的"/login"和页面中的“/login”没有半毛钱关系。当登录时会走配置类中的configure()方法,然后是loadUserByUsername(),而不走自定义的XxxController中的"/login"对应的方法。因为请求被springsecurity拦截后,底层做了一些处理。
(3)登录失败页面跳转
(4)自定义登录参数
(5)自定义登录成功处理器
- 我们之前说登录成功要跳转页面的话,要通过我们的XxxController类去实现,因为它要求使用post的方式去跳转。
因为以后我们接触的大部分项目都是前后端分享的项目,页面跳转的控制是由前端去控制的,后端顶多就返回一个json数据给到前端。而且,我们说过了,请求必须是post请求。
需求:如果我们就想用get请求(如重定向)呢?
解决方案:自定义登录成功处理器。
因为有onAuthenti cationSuccess (HttpSe rv le tRequest request,),HttpServletResponse res ponse,Authentication authentication),所以第1个好处是:可以使用重定向(比如重定向到http://www.baidu.com)。第2个好处是:如果我们写的是一个前后端分离的话,我们的页面跳转是靠我们的前端去控制的。这时,我们完全可以通过response返回数据(比如最常见的json数据)。第3个好处是:还可以通过Authentication获取当前用户的认证信息(用户名、密码、凭证信息等)。
(6)自定义登录失败处理器
3 授权/权限/角色:authorizeRequests()、exceptionHandling()、access自定义方法、注解方式
(1)授权类型
- url授权(访问控制)
- 角色授权(访问控制)
- 其它授权(访问控制)
(2)配置授权api
- http.authorizeRequests()
-
http.authorizeRequests() 也支持连缀写法,即点点点,一直点下去。
-
(3)授权公式
-
url 匹配规则 . 权限控制方法
-
url匹配规则
- anyRequest().authenticated():匹配所有(任何)请求。一般放在最后,而且要加上.authenticated()表明认证之后才能访问。为什么要放在最后呢?因为如果放在前面,所有请求都要被认证,放行(antMatchers)的那些代码就不起效果了。
- .antMatchers():url匹配规则,参数是不定向参数,每个参数是一个 ant 表达式,用于匹配 URL规则。2个参数的方法,还可以限定请求方式(get、post等)。
规则如下:? : 匹配一个字符* :匹配 0 个或多个字符** :匹配 0 个或多个目录
案例:
.antMatchers("/js/**","/css/**").permitAll():匹配静态资源。第1个"/js/**",匹配js目录下面的所有文件和目录。同理,"/css/**"。
.antMatchers("/**/*.js").permitAll(): 还有一种配置方式是只要是 .js 文件都放行 -
.regexMatchers():使用正则表达式进行匹配。与.antMatchers()的唯一区别仅仅在于参数不同。 .regexMatchers( ".+[.]js").permitAll():演示所有以.js 结尾的文件都被放行。 注意:2个参数的方法,还可以限定请求(get、post等)。
-
.mvcMatchers():mvcMatchers()适用于配置了 servletPath (http://localhost:8080/xxxx/1.png,表明在yml或properties文件中配置了servletPath(应用名称), 即spring.mvc.servlet.path=/yjxxt )的情况。案例:mvcMatchers("/image/**") . servletPath("/xxxx") .permitAll(),其中的"/xxxx"就是应用名称。注意:很少用,因为复杂。使用antMatchers()和 regexMatchers()也能达到相同的效果,只需要在资源前面加入xxx(应用名称)即可。
-
建议:使用最简单最容易懂的antMatchers()。
-
权限控制方法(具体的访问控制):内置访问控制方法
-
permitAll():放行所有。
-
authenticated():必须要认证后才能访问,认证不成功就跳转到登录页面。
-
anonymous():匿名访问。
-
denyAll():拒绝所有。
-
rememberMe():记住我,可能是3天,可能是5天。
-
fullyAuthenticated():完整的认证。即记住我的方式也要通过用户名和密码认证通过后,才能访问的资源。
-
- 权限控制方法(具体的访问控制):根据权限判断。权限名称严格区分大小写。设置权限的地方是,UserServiceImpl.loadUserByUsername()方法中的:return new User(username,password, AuthorityUtils. commaSeparatedStringToAuthorityList("admin,normal"));
- hasAuthority(String):匹配1个权限
- hasAnyAuthority(String ...):匹配多个权限中的1个即可
- 权限控制方法(具体的访问控制):根据角色判断。角色名称严格区分大小写。设置角色的地方是,UserServiceImpl.loadUserByUsername()方法中的:return new User(username,password, AuthorityUtils. commaSeparatedStringToAuthorityList("ROLE_abc,ROLE_def"));注意:必须是ROLE_开头。而且角色是ROLE_后面的xxx,在判断时springsecurity会自动加上前缀ROLE_。
- hasRole(String):匹配1个角色,如上面的abc角色。
- hasAnyRole(String ...):匹配多个角色的1个,如上面的abc或def角色。
- 权限控制方法(具体的访问控制):根据其它(如IP地址)判断。
- hasIpAddress(String):匹配ip地址。应用场景:系统只允许那几个管理员PC所在的IP地址才能访问。
-
(4)自定义403:http.exceptionHandling()
- 场景
- 出现403错误页面
- 原因
- 权限不够
- 解决方案
- 方案1:对于普通的web项目,我们可以转发或重定向到更友好的页面,显示给用户看。
- 方案2:对于前后端分离的项目中,可以返回json数据给到前端,前端自己给出相应的错误提示。
- 详细步骤:
- 第一步:自定义MyAccessDeniedHandler implements AccessDeniedHandler
- 第1步:实现handle(HttpServletRequest request, HttpServletResponse
response, AccessDeniedException accessDeniedException) 方法
-
第2步:把MyAccessDeniedHandler 注入到spring容器中。
- 第1步:实现handle(HttpServletRequest request, HttpServletResponse
- 第二步:在配置类中使用http.exceptionHandling()处理
- 第一步:自定义MyAccessDeniedHandler implements AccessDeniedHandler
(5)基于表达式的访问控制:access自定义方法
- 基于access表达式的访问控制:
- 规则
- 所有的权限访问控制方法(包括了:内置访问控制方法、根据权限判断、根据角色判断、根据其它(如IP地址)判断)的底层,都是基于access表达式。
- access表达式和之前使用的,内置访问控制方法、根据权限判断、根据角色判断、根据其它(如IP地址)判断的授权方式是一样的,没有特别大的一个区别,甚至可以说是没区别。
- 使用案例:
- 规则
- 自定义判断逻辑
- 原因:
- 内置访问控制方法、根据权限判断、根据角色判断、根据其它(如IP地址)判断的授权方式是固定好的规则,如果想自定义判断逻辑以满足特殊情况下的需求,就需要有access了。
- 案例:
- 判断当前登录的用户是否具有访问当前url的权限。如果有权限,则可以访问。如果没有权限,报出403错误。
- 实现步骤
- 怎么才能让当前用户具有某个权限呢?:
@Service public class UserServiceImpl implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if(StringUtils.isEmpty(username) || !(username.equals("admin"))){ System.out.println("用户不存在!"); throw new UsernameNotFoundException("用户记录不存在!"); } // 到数据库查询用户记录 String password = passwordEncoder.encode("123"); // 给当前登录的用户设置:admin、normal权限 // 给当前登录的用户设置:QQ、AA两个角色 return new User(username,password, AuthorityUtils. commaSeparatedStringToAuthorityList("admin,normal,ROLE_QQ,ROLE_AA")); } }
- 第二步:编写接口:
package com.yjxxt.springsecuritydemo.service; import org.springframework.security.core.Authentication; import javax.servlet.http.HttpServletRequest; public interface MyService { /* 描述:自定义方法 * 作用:判断当前登录的用户是否具有权限 * 参数: * 参数1:HttpServletRequest request。用于获取当前url。 * 参数2:Authentication authentication:用于获取当前登录的用户 * 返回:布尔值 */ boolean hasPermission(HttpServletRequest request, Authentication authentication); }
-
第三步:编写接口实现类:
/** * * @since 1.0.0 */ @Component public class MyServiceImpl implements MyService { @Override public boolean hasPermission(HttpServletRequest request, Authentication authentication) { Object obj = authentication.getPrincipal(); if (obj instanceof UserDetails){ UserDetails userDetails = (UserDetails) obj; Collection<? extends GrantedAuthority> authorities =userDetails.getAuthorities(); return authorities.contains(newSimpleGrantedAuthority(request.getRequestURI())); } return false; } }
-
第四步:修改配置类:
//url拦截 http.authorizeRequests() //login.html不需要被认证 // .antMatchers("/login.html").permitAll() .antMatchers("/login.html").access("permitAll") // .antMatchers("/main.html").hasRole("abc") .antMatchers("/main.html").access("hasRole('abc')") .anyRequest().access("@myServiceImpl.hasPermission(request,authentication) ")
- 怎么才能让当前用户具有某个权限呢?:
- 原因:
(6)基于注解的访问控制:一共5个注解,常用的就是2个(@Secured、@PreAuthorize/@PostAuthorize)
- 原因
- 之前我们对url访问控制权限的配置都是通过配置类来完成的,但是在springsecurity也提供了许多访问控制权限相关的注解供大家使用。
- 可以提高开发效率,代码更加简洁。
- 注解的格式
-
这些注解可以写到 Service 接口或方法上,
-
也可以写到 Controller或 Controller 的方法上。
-
通常情况下都是写在Controller的方法上,控制接口URL是否允许被访问。
-
-
常用注解
-
@Secured:
-
专门判断是否具有角色的。能写在方法或类上。参数要以 ROLE_开头。
-
等价于:内置方法hasRole()。
-
-
@PreAuthorize/@PostAuthorize
- 相同点:
-
@PreAuthorize 和@PostAuthorize 都是方法或类级别注解。
-
@PreAuthorize 和@PostAuthorize 都是用来判断权限的。
-
等价于:内置方法hasAuthority()
-
-
区别:
-
@PreAuthorize 表示访问方法或类在执行之前先判断权限,大多情况下都是使用这个注解,注解的参数和access()方法参数取值相同,都是权限表达式。
-
@PostAuthorize 表示方法或类执行结束后判断权限,此注解很少被使用到。
-
- 相同点:
-
-
注意事项
- 这些注解默认是不能使用的,需要通过在启动类顶部加上@EnableGlobalMethodSecurity 进行开启后使用。
- 如果设置的条件允许,程序正常执行。如果不允许会报 500。
- 报500的工作原理:
- 第一步:用户登录
- 第二步:条件判断
- 第1次进来,发现条件不符合,就会判断有没有登录。
- 如果已经登录过了,报500
- 如果没有登录,那需要登录以后,再次进行条件判断。
- 如果符合条件,可访问。
- 如果条件不符合,报500
- 第1次进来,发现条件不符合,就会判断有没有登录。
- 报500的工作原理:
4 其它
(1)RememberMe功能实现:http.rememberMe()
- 应用场景
- 在前端页面记住我的对话框中打勾,可以记住3天 或者 5天 或者 1个礼拜。
- 3天 或者 5天 或者 1个礼拜,可以设置。
- 下次登录的时候,不需要输入用户名和密码,直接就登录成功了。
- springsecurity RememberMe的工作原理
-
第一步:Spring Security 中 Remember Me 为 “ 记住我 ” 功能,用户只需要在登录时添加 remember-me复选框,
-
第二步:取值为 true 。
-
第三步:Spring Security 会自动把用户信息存储到数据源中( 建议使用数据库 ,测试时可以把数据存储到内存中),
-
第四步:以后就可以不登录进行访问
-
-
详细步骤
-
第一步:添加依赖
-
mybatis-spring-boot-starter、mysql-connector-java
-
-
第二步:配置文件spring.datasource.driver-class-name= com.mysql.cj.jdbc.Driverspring.datasource.url= jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaispring.datasource.username= rootspring.datasource.password= root
-
第三步:编码
-
第1步:配置类
-
第a步:http.rememberMe().userDetailsService(userService).tokenRepository(persistentTokenRepository);
-
其中userService,是我们的自定义登录逻辑类的实例,可从spring容器中获取。
-
其中persistentTokenRepository,是持久层对象类的实例,需要新建。
-
-
-
第2步:新建持久层对象类@Beanpublic PersistentTokenRepository getPersistentTokenRepository(){JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);//自动建表,第一次启动时需要,第二次启动时注释掉(不然报错)jdbcTokenRepository.setCreateTableOnStartup(true);return jdbcTokenRepository;}
-
第3步:重启项目
-
第a步:数据库自动创建了相应的表
-
-
-
第四步:前端,加入记住我对话框
-
第1步:<input type="checkbox" name="remember-me" value="true"/><br/> ,这里的name必须为remember-me。
-
-
第五步:验证
-
第1步: 重启项目(注释掉)
-
第2步:勾选记住我,点击登录 ,跳转到main.html页面
-
第3步:关掉浏览器
-
第4步:重新打开浏览器,直接在地址栏中写入,localhost:8080/main.html可以直接进,不需要登录了
-
第5步:不可以到数据库中查看数据
-
-
-
其它
-
记住我默认可记住2周。可以改, . tokenValiditySeconds ( 120 )。
-
自定义参数(即可改前端的 name="remember-me"):.rememberMeParameter()。
- 自定义记住我的实现:rememberMeService()。
-
(2)Thymeleaf中SpringSecurity的使用
- 应用场景
- 主要应用在非前后端分离的项目中,在jsp或Thymeleaf中SpringSecurity的使用。
- 注:而对于前后端分离的项目,权限的控制一般都是由前端开发人员来控制的,不会用到springsecurity。
- 详细使用步骤:
- 第一步:pom.xml加入依赖。这样我们才能在Thymeleaf中使用SpringSecurity
- 第二步:前端。如何在Thymeleaf中使用SpringSecurity?
- 第1步:在 html 页面中引入 thymeleaf 命名空间和 security 命名空间
- 第2步:在thymeleaf中使用SpringSecurity
- 获取属性:比如,可以获取当前登录用户的信息(用户名、密码、权限等)
- 权限判断:比如,可以根据当前登录用户的权限来控制按钮的显示与隐藏。
- 其实就是我们的access表达式。
(3)退出登录:http.logout()(Thymeleaf中SpringSecurity的使用)
- 详细步骤
- 第一步:前端
- 第1步:加入“退出登录”标签。就这么整完了,就这么快。
- 第二步:验证
- 第1步:重启
- 第2步:点击“退出登录”,返回“登录页面”,但地址栏url地址后面多了“?logout”
- 第一步:前端
- 退出登录可配置:http.logout()
- logoutUrl():退出登录url。
- logoutSuccessUrl():退出登录成功跳转的页面。
- addLogoutHandler(LogoutHandler):退出登录处理器。
- clearAuthentication(boolean):是否清除认证状态,默认为 true。
- invalidateHttpSession(boolean):是否销毁 HttpSession 对象,默认为 true。
- logoutSuccessHandler(LogoutSuccessHandler):退出成功处理器。
- 注意项目:
- 一般情况下,我们做“退出登录”的话,使用Thymeleaf+SpringSecurity默认的使用方式即可。但如果想变量,可以变更。
- 一般情况下,我们最多使用到logoutUrl()、logoutSuccessUrl()就可以了,因为SpringSecurity底层已经为我们做了很多很多工作了。
(4)SpringSecurity中的CSRF:http.csrf().disable()(Thymeleaf中SpringSecurity的使用)
- 前面:简单把CSRF理解为一个防火墙。
- 什么是CSRF?
-
跨站(跨域)请求伪造。
-
跨域: 只要网络协议、ip 地址、端口中任何一个不相同就是跨域请求。
-
cookie: 客户端与服务进行交互时,由于 http 协议本身是无状态协议,所以引入了cookie进行记录客 户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id 可能被第三方恶意劫持,通过这个 session id 向服务端发起请求时,服务端会认为这个请求是 合法的,可能发生很多意想不到的事情。
-
防护:从 Spring Security4开始CSRF防护默认开启。默认会拦截请求。进行CSRF处理。CSRF为了保 证不是其他第三方网站访问,要求访问时携带参数名为 _csrf 值为token(token 在服务端产生) 的内容,如果token和服务端的token匹配成功,则正常访问。
-
-
- CSRF的工作原理
- 第一步:你第1次登录的时候,会颁发一个csrf的token给你的客户端浏览器。
- 第二步:客户端浏览器以后的每次请求都要携带这个token才得。
- 如果客户端浏览器给的token为空或不正确(即使sessionId一样),就会被认为此次访问是一次非法访问。
- 总结:也就是除了验证sessionId以外,还额外要验证token,多加了一层验证。
- 前端展示CSRF
-
非常简单<input type = "hidden" th:value = "${_csrf.token}" name = "_csrf" th:if = "${_csrf}" />
-
- 为什么要关CSRF?
- 实际项目中要不要关闭CSRF?
二 oathu2协议
1 简介
(1)业务
- 因为要实现跨系统跨语言的第三方认证(第三方登录),各系统之间要遵循一定的接口协议。
- 有很多网站都有第三方登录,比如都去接入并登录微信、QQ、新浪、百度等。那网站去做第三方登录时,就要求第三方做认证(即第三方认证)。网站在做第三方认证的时候,就要去第三方获取用户资源(比如用户名、密码)。
问题是,网站和第三方系统可能使用不同的技术,甚至不同的语言(java、c、c++、php)去实现。而不同的语言之间,它们的认证协议可能有所不同,就很难做到每个语言的认证协议都是统一。你比如说,网站第三方登录的是微信,那么不可能让微信针对每种语言都写一套认证协议,太麻烦了。
这时就需要有一个通用的标准,每种语言都遵循这种标准进行认证。这样就屏蔽了,不同系统的不同语言之间认证的差异。
- 有很多网站都有第三方登录,比如都去接入并登录微信、QQ、新浪、百度等。那网站去做第三方登录时,就要求第三方做认证(即第三方认证)。网站在做第三方认证的时候,就要去第三方获取用户资源(比如用户名、密码)。
(2)需求
- 第三方认证技术方案最主要是解决认证协议的通用标准问题。
(3)思路
- oathu2只是一个协议,一个通用标准,第三方认证的通用标准。
- 标准是什么?就是秦国在统一六国之前,每个国家都有自己的货币。那么国与国之间进行交易就比较麻烦(那时,会以物换物)。而在秦国统一六国之后,大家都用统一的货币,交易起来就很方便了。
- 这个标准是什么呢?OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。
- 任何第三方都可以使用OAUTH认证服务,任何服务提供商都可以实现自身的OAUTH认证服务。
- 而具体的实现都要求各个语言自己去实现。
(4)原理
- 用户资源:简单来说就是用户名和密码。
- 用户资源的授权:比如网站需要微信的用户资源(用户名和密码),微信不会直接给到网站。怎么样才能让微信把用户资源给到网站呢?因为微信上保存的用户资源本来就只是属于用户本人所有的,因此这就要用户去授权、去确认(如扫码登录微信,并点击“授权”按钮),微信才会把用户资源发送给网站。
(5)工作流程
- 第一步:用户来到网站的登录页面,快速登录栏有:微信、QQ、新浪、百度。
- 第二步:用户点击微信图标(不输入用户名和密码)。
- 第三步:网站向微信发送第三方认证。
- 第四步:微信发送授权页面给用户,让用户确认是否把用户资源信息发送给网站,比如微信扫码登录。
- 第五步:用户跟微信说确认授权(点击授权按钮)。
- 第六步:微信授权服务器会把一个授权码给到网站。
- 第七步:网站根据授权码去微信认证服务器去申请令牌。
- 第八步:如果授权码有效,微信会返回令牌给网站。
- 第九步:网站拿着令牌去微信用户服务器拿用户的信息。
- 注意:一般第三方的认证服务器和用户资源服务器是分开的。
- 第十步:微信用户服务器校验令牌的合法性。
- 第十一步:如果令牌合法,就把用户信息返回给(响应)网站。
- 第十二步:微信响应完成后,与微信相关的操作就没有了,剩下呢,都是网站自己内部的操作。比如,拿着微信响应的用户资源去数据库查询验证并登录。
2 授权模式
(1)授权码模式,最常用,最重要(Authorization Code)
- Client:我们的客户端,是我们自己开发的应用。
- User-Agent:用户代理。比如,别人要通过浏览器访问我们的应用,其中的浏览器就是用户代理。
- Resource Owner:资源拥有者。就是用户本身
- Authorization Server:授权服务器
- Client Identifier:客户(端)凭证
- Redirection URI:重定向的URI
- User authenticates:用户授权
- Authorization Code:授权码
- Authorization Token:访问令牌
- Optional Refresh Token:刷新令牌
- 第一步:A:Client和User-Agent关联上
- 第二步:用户点击了微信登录。
- 第三步:A1:User-Agent携带Client Identifier和Redirection URI去找Authorization Server
- 第四步:B1:Authorization Server要求User authenticates
- 第五步:B:用户授权完成
- 第六步:B1:User-Agent告诉Authorization Server用户已经授权了
- 第七步:C1:Authorization Server返回一个Authorization Code给到User-Agent
- 第八步:C:Client拿到Authorization Code
- 第九步:D:Client根据Authorization Code和Redirection URI去请求Authorization Server
- 第十步:E:Authorization Server返回一个Authorization Token或Optional Refresh Token给Client
- 第十一步:有了Authorization Token就可以去访问“资源服务器(没画出来)”获取用户资源。
- 第十二步:只剩下User-Agent本身的操作了。
- 场景类比:买电影票看电影。即使用电子凭据去领取真实的电影票,才能进去看电影。
(2)简化授权模式,很少用,加2步就可以用授权码模式了(Implicit)
- Web-Hosted Client Resource:客户端源码
- Access Token In Fragment:访问令牌。
- 第一步:A1:User-Agent携带Client Identifier和Redirection URI去找Authorization Server
- 第二步:B1:Authorization Server要求User authenticates
- 第三步:B:用户授权完成
- 第四步:C1:Authorization Server直接把Redirection URI和Access Token In Fragment返回给User-Agen。
- 注意:这里简化了,没有授权码的事情了。简化的就是授权码的获取,以及根据授权码去获取令牌。
- 注意:这次的令牌给在Redirection URI后面,我们需要手动获取。
- 第五步:D:User-Agent把Redirection URI和Access Token In Fragment发送到Web-Hosted Client Resource
- 第六步:E:令牌返回给User-Agent
- 第七步:G:Client获取令牌
- 第八步:有了Authorization Token就可以去访问“资源服务器(没画出来)”获取用户资源。
- 第九步:只剩下User-Agent本身的操作了。
(3)密码模式,偶尔用(Resource Owner PasswordCredentials)
- 第一步:密码模式:用户直接在我们的网站上输入第三方的用户名和密码。
- 第二步:我们带着用户输入的第三方用户名和密码去找第三方认证服务器(授权服务器),进行认证。认证成功,认证服务器(授权服务器)直接给令牌。
- 第三步:根据令牌获取用户资源
- 应用场景:
- 公司开发了一套应用,一个app版的,一个是网站版的。其中,app版和网站版使用的是同一个数据库。现在网站版的应用想要获取app版应用中的用户资源,因为使用的是同一个数据库、授权服务器、资源服务器,所以直接返回令牌获取用户资源即可。
- 比如,阿里巴巴的淘宝、天猫、支付宝。可以用支付宝账户去登录淘宝和天猫。
(4)客户端模式,最简单,很少用(Client Credentials)
- 第一步:A:client授权
- 第二步:B:Authorization Server给令牌
- 应用场景:
- 一般都是用在设备上面,一些软件也在用。
- 比如,docker。docker要去拉取镜像之前,也要获取授权。获取授权之后,才会给你拉取镜像。这个时候docker就会主动去找服务器,要求授权,然后服务器就会给docker一个令牌,最后docker就可以完整地去拉取镜像了。
(5)刷新令牌
- 第一步:A:client拿着授权码去找Authorization Server
- 第二步:B:Authorization Server返回Refresh Token
- 为什么要有Refresh Token呢?因为Access Token(访问令牌)是有过期时间的。过期时间可以设置。
- 第三步:G:通过Refresh Token找Authorization Server
- 第四步:H:Authorization Server颁发新的Access Token和Refresh Token。
- 这样省去了授权码模式下,关于使用授权码获取令牌的复杂步骤
- 第五步:使用Access Token获取资源服务器的用户资源
- 类比场景:电影票过期了,还没进去看,服务员给你换到下一场(同一部影片)。
三 springsecurity oathu2框架(springsecurity实现oathu2,即java层配合springsecurity框架搞出来的一个oathu2的具体实现)
1 授权服务器
- 注:括号中写的就是到时候我们要去调用的接口,只是到时候调用时没有“2”字。
- /oauth/authorize:调用授权
- /oauth/token:调用令牌
2 Spring Security Oauth2架构
体系组织:如上图所示
- springsecurity oathu2框架不仅仅包含springsecurity oathu框架,里面还带有springsecurity框架,还有用户自己实现的一些代码。
- spring security框架:橙色
- springsecurity oathu框架:黄色
- Implemented by Deveplper:用户自己实现的代码
工作流程1-me:
- (1)
- 第一步:User通过User agent去访问,此时Client和User agent都没有token。
- 第二步:请求到达Client的OAuth2RestTemplate,因为没有token而报错。
- 第三步:Client的OAuth2ClientContextFilter拦截这个报错。
- (3)
- 第一步:拦截这个报错后Client重定向到Authorization server。
- (2)
- 第一步:经过授权端点Authorization Endpoint进行授权。
- 第二步:授权端点Authorization Endpoint会使用授权服务AuthorizationServerTokenServices服务生成授权码。
- (3)
- 第一步:授权码返回给Client
- 第二步:Client拿到授权码后再次请求Authorization server,这次请求的是令牌端点TokenEnpoint
- (2)
- 第一步:令牌端点TokenEnpoint拿着授权码去找AuthorizationServerTokenServices服务,验证授权码。如果授权码正确,就颁发令牌给
- (3)
- 第一步:令牌返回给Client。
- (4)
- 第一步:Client携带令牌去找资源服务器Resouce server
- 第二步:资源服务器Resouce server的认证管理器OAuth2AuthenticationManager
- 第三步:认证管理器OAuth2AuthenticationManager调用ResourceServerTokenServices服务进行令牌的校验。
- 如果校验通过则,把用户资源返回给Client。
工作流程2:
- 用户访问,此时没有Token。Oauth2RestTemplate会报错,这个报错信息会被Oauth2ClientContextFilter捕获并重定向到认证服务器
- 认证服务器通过Authorization Endpoint进行授权,并通过AuthorizationServerTokenServices生成授权码并返回给客户端
- 客户端拿到授权码去认证服务器通过Token Endpoint调用 AuthorizationServerTokenServices生成Token并返回给客户端
- 客户端拿到Token去资源服务器访问资源,一般会通过Oauth2AuthenticationManager调用ResourceServerTokenServices进行校验。校验通过可以获取资源。
3 Spring Security Oauth2授权码模式
- 第一步:创建maven、springboot、spring web、Spring Security项目
- 第二步:pom.xm
- 剩下的依赖去文档中拷贝,因为我们要用springcloud的方式来引入springsecurity oathu2。为什么要这样做呢?因为springboot的springsecurity oathu2有点问题,虽然它支持资源服务器的配置,而授权服务器的配置太麻烦了,所以换成简单一点的方式。
- springcloud的版本号:<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
- springcloud的依赖:<artifactId>spring-cloud-dependencies</artifactId>,专门用来引入springcloud
- 第三步:application.properties
- 第四步:编码 - 配置springSecurity框架 - 并配置自定义登录逻辑
- 第1步:自定义实体类
- 注意:正常开发过程中,用户名、密码、权限都是要去数据库查询出来的。
- 第2步:自定义登录逻辑:UserDetailsService.loadUserByUsername()
- 第a步:返回上面的new User,并配置角色和权限。
- 第3步:编写 - 配置springSecurity框架
- 第a步:注入密码加密工具bean。
- 第b步:关闭csrf
- 第c步:放行授权端点、资源端点的url。
- 第d步:剩下的,都要求认证。
- 第e步:允许表单提交
- 注意:通过and()就可以回到http.的状态,就可以继续往下“点”。比如,案例中就可以.formLogin(),再.permitAll()放行。因此,如果想在配置类中使用多个http.的话,通过and()返回到http.状态后,就可以继续往下“点”了。
- 第1步:自定义实体类
- 第五步:编码 - 配置Spring Security Oauth2
- 概述
- 思考一下Spring Security Oauth2有哪些地方要配置?第一个配置验证服务器;第二个配置资源服务器,比如配置资源作用域(范围)。
- 详细步骤
- 第1步:编写代码 - 配置验证服务器(授权服务器):AuthorizationServerConfifig.java
- 第a步:配置客户凭证(用户名admin、密码(加密)112233)。客户凭证,用来确定你这个网站,并确定你这个网站需要什么样的授权码(获取对应权限范围的令牌)。
- 第b步:配置用于获取授权码的重定向uri。这里不管你重定向到哪里都可以,因为重定向uri,只是为了获取授权码。
- 第c步:授权范围。不同的client要求的授权范围不一样,有些只要用户的用户名和密码,有些可能需要用户的图片(如微信头像)......
- 第d步:授权类型
- 注意:正常开发过程中,上面的很多信息都是要去数据库查询出来的。
- 第3步:准备资源
- 概述:
- 即用户信息,这里返回当前登录用户的信息:Authentication authentication
- 详细步骤:
- 第a步:为了方便,这里使用的是一个XxxController.java,使用Authentication返回当前登录的用户即可。
- 概述:
- 第2步:编写代码 - 配置资源服务器:ResourceServerConfig.java
- .anyRequest().authenticated():所有的请求都需要被授权服务器进行授权(认证)
- and():回到http.
- .requestMatchers().antMatchers("/user/**"):当你有令牌的时候,就放行类似"/user/**"的请求。
- 第1步:编写代码 - 配置验证服务器(授权服务器):AuthorizationServerConfifig.java
- 概述
- 第六步:验证
- 第1步:启动项目
- 第2步:验证:获取授权码
- 授权端点:
http://localhost:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all
-
response_type=code:表示返回的是授权码
-
client_id=admin:客户id。在授权服务器配置中配置的
-
redirect_uri =http://www.baidu.com:重定向uri。在授权服务器配置中配置的。
-
scope=all:把的的作用域。在授权服务器配置中配置的。
-
-
登录
-
获取授权码(在重定向uri后面):thttps://www.baidu.com/?code=lFIilh
- 授权端点:
-
第3步:验证:根据授权码获取令牌(必须POST请求,建议使用postman发送post请求)
-
端点:localhost:8080/oauth/token
- postman如下图操作:
- 报错:Error: Header name must be a valid HTTP token [“授权“]
-
"status": 401
-
用非汉化的postman是成功的:
-
access_token:访问令牌;token_type:token的类型;expires_in:失效时间;scope:范围。
-
-
-
第3步:验证:根据token去资源服务器拿资源(可用Get请求,也可用POST请求,建议使用postman发送post请求)
- 地址:为XxxController.java的映射地址:localhost:8080/user/getCurrentUser
- 如果没传令牌,就报没有授权的错误
- 如果令牌不正确,就报令牌不合法的错误
- postman:传入令牌,获取到用户资源
- 地址:为XxxController.java的映射地址:localhost:8080/user/getCurrentUser
- 注意事项:
-
授权码只能用一次,如果获取不成功,那么只能重新获取新的授权码。
-
4 Spring Security Oauth2 密码模式
- 密码模式非常简单,只要在授权码模式上稍做调整就可以了
- 总配置类的调整:
- 注入授权管理器:
@Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
- 注入授权管理器:
- 授权服务器配置调整:
- 覆盖方法:
/** * 密码模式 * @param endpoints * @throws Exception * 参数1:AuthorizationServerEndpointsConfigurer:授权服务器端点的配置 */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { // .authenticationManager(authenticationManager):授权管理器。 // .userDetailsService(userService):使用自定义的登录逻辑。 endpoints.authenticationManager(authenticationManager) .userDetailsService(userService); }
- 设置授权类型为密码模式:
- .authorizedGrantTypes("authorization_code","password");
- 注:这里可以配置多个模式,即可以允许它有多个模式去用
- 覆盖方法:
- 重启项目,并验证:成功获取令牌,根据令牌成功获取用户资源
- 总配置类的调整:
5 在Redis中存储token
- 业务
- 之前我们生成的token都是直接在内存里面的,在生产环境中这样做是不合理的。因为token放在内存中的话,可能会造成token丢失,所以token理应放到数据库中或者是缓存中。那放在关系型数据库中还是放在缓存中呢?因为放在关系型数据库中可能获取的时候会比较慢,所以我们一般会把token放在缓存(如redis中)。
- 需求:把token放到redis中
- 详细步骤:
- 第一步:pom.xml,添加redis相关依赖
- 第二步:application.properties
- 第1步:配置redis的用户名和密码
- 第三步:编码 - token放入redis
- 第1步:编写redis配置类 - 使用redis存储token的配置 - 返回TokenStore
- 第2步:授权服务器的调整
-
. tokenStore(tokenStore); :token存储的位置tokenStore,位置可以是:内存、数据库、缓存。
-
-
第四步:验证
-
第1步:重启项目
-
第2步:密码模式下获取令牌
-
第3步:查看redis工具:
-
access:令牌。auth:授权。auth_to_access:授权到访问。client_id_to_access:授权到访问。
-
-
四 jwt
1 常见的认证机制
(1)HTTP Basic Auth:http基本认证
- 最简单的
- 为什么每次都要输入username和password呢?因为http是无状态的,没办法保存你的状态,所以要在每次http请求时都得携带username和passwrod。
(2)Cookie Auth:cookie认证,Servlet时就学了
- 第一步:在browser端使用验证码、用户名、密码进行登录。
- 第二步:server返回Http 200 OK响应,在服务端创建Session,同时在browser端创建cookie,并把服务端的sessionId保存到cookie中。
- 第三步:之后browser对server的每次请求都要携带保存有sessionId的这个cookie。
- 第四步:server通过cookie中的sessionId查找和序列化化session。
- 第五步:如果校验session成功,则返回http 200 ok。
(3)OAuth:oauth协议认证,上面刚刚讲过,所以我们应该很熟悉
- 无需将用户名和密码提供给第三方应用的前提是,不使用oauth的密码模式,因为如果使用的是oauth的密码模式就要提供用户名和密码。
- 工作流程图:
- 应用场景
- oauth协议认证比较适合个人消费者类的互联网产品,比如说社交类的app使用,如论坛、电商网站都可能有第三方认证。反之,传统的系统很少用到第三方登录(oauth协议认证)。
- 工作流程
- 第一步:客户端请求授权。
- 第二步:用户确认授权,授权码返回给客户端。
- 第三步:客户端根据授权码去找授权服务器,校验授权码。
- 第四步:授权服务器返回访问令牌给客户端。
- 第五步:客户端拿着访问令牌去找资源服务器,获取用户的被保护的用户资源。
- 第六步:资源服务器返回用户资源给到客户端。
- 应用场景
(4)Token Auth:基于Token的认证。
- Token Auth的特点
- 在server端不用去存储用户的登录记录(与cookie auth相反)。
- 工作流程(注意与cookie auth的区别)
- 第一步:在browser端使用验证码、用户名、密码进行登录。
- 第二步:server返回Http 200 OK响应,把发送一个token给browser保存(如放在cookie 或 local Storage 或 session storage) 。
- 第三步:之后browser对server的每次请求都要携带token。
- 第四步:server校验token。
- 第五步:如果校验token成功,则返回http 200 ok和数据。
- Token Auth与cookie auth的区别是什么?
- 如果把token放在cookie里面,那么Token Auth与cookie auth看起来没有太多的区别。唯一的区别在哪里呢?cookie auth时会把用户的信息保存在session里面,然后去做校验。而Token Auth是不需要去存任何用户信息的,即在server端没有任何的用户信息,因此一般情况下token是无状态的。
- Token Auth比cookie auth更节约服务器资源,因为不需要在Server端存Session了,不再创建Session对象,所以Token Auth比cookie auth更节约服务器资源。
- Token Auth的优点(Token机制相对于Cookie机制又有什么好处呢?)
- 支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息(Token)时通过HTTP请求头进行传输(ps:而不是把Token放到cookie,携带cookie进行传输,因为说过了cookie不允许跨域。而相反,把token放在http请求头中,就可以跨域了)。
- 无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session(对象)信息,因为Token自身包含了所有登录用户的信息(ps:但也不要直接把用户名和密码放到里面不然黑客一解析就可以得到你的用户名和密码了。开发时要懂得,哪些信息是可以放到其中,哪些敏感信息不能放到其中的,把握到度),只需要在客户端的cookie或本地介质存储状态信息。(ps:相反,Cookie auth需要在服务器创建session对象,用于保存用户信息。)
- 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可。(ps:以前在使用oauth协议认证的时候,有专门的授权服务器。授权服务器主要做两个事情,第一个是颁发授权码,第二个是根据授权码去颁发令牌。但如果使用Token auth的话,是可以不存在授权服务器的。也因此得出,Token auth比oauth更加轻量。)
- 无序列表基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在于多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google,Microsoft)。
2 jwt简介
(1)什么是jwt
- 一个标准,和oauth2一样jwt也是一个协议一个标准。
- 基于json的网络应用的一种令牌。
- 自包含的协议格式?jwt token本身就包含了一些要提供给server的信息。
- ps:jwt比oauth2更加的轻量,因为jwt不需要有授权服务器。jwt直接是通过资源服务器颁发token、解析token、校验token。
(2)jwt的组成
- 概述
- jwt本质是就是一个字符串(ps:因为json本身也是一个字符串)。
- 组成
- 头部(header)
- 作用:描述关于该JWT的最基本的信息。
- 格式:这也可以被表示成一个JSON对象。
- 展示:Base64加密
- 注意:PS:因为Base64是对称加解密的,所以Base64加密其实非常非常不安全。什么是对称加解密呢?就是把密文重新通过Base64解密,立即可以得到原来的明文信息。
- 负载 或 载荷 或 荷载(Payload):用的也是Base64是对称加解密,不安全
- 作用:真正存放有效信息的地方。
-
有效信息包含三个部分
- 第1部分:标准中注册的声明(建议但不强制使用)
- 第2部分:公共声明
- 第3部分:私有声明
- ps:提供者和消费者所共同定义的声明,中为什么要“共同”呢?因为就像协议、标准一样,提供者和消费者都要知道怎么解析,哪一部分代表什么信息,这样才能正确地进行处理。
- 签证、签名(signature)
- signature非常非常重要,因为前面的头部和负载都使用Base64对称加解密,而signature是(盐,一定要保密)的,可以说signature才是jwt安全性的保障。
- 因为盐是安全性加密的保障,所以盐一定要保密,不能被客户端知道。
- 完整的签证信息
- 完整的签证信息=第一部分base64编码的字符串 点 第二部分base64编码的字符串 点 (第一部分64 + 第二部分64 + 盐,按照之前设置的签名算法(如alg:HS256 或alg:HMAC)进行加密得到的字符串)
- 头部(header)
3 jjwt简介
(1)什么是jjwt?
- jjwt是jwt标准的一种实现方式。
- jwt的实现方式还有很多种,甚至用java去实现jwt的方式都有很多种啦。
(2)快速入门
- 第一步:创建SpringBoot工程
- 第二步:pom.xml,引入依赖
- 第三步:junit 单元测试类
- 第1步:jwt token的创建
- 第2步:jwt token的验证(校验)和解析
- 第3步:jwt token过期校验
- 第4步:自定义claims
五 springsecurity oathu2整合jwt
- springsecurity oathu2 密码模式整合jwt
- 前情:
- 使用的项目:springsecurity oathu2 密码模式
- 之前我们的令牌是bearer token令牌,非常短
- 那我们想要整合jwt的话呢,要做一些改造
- 详细步骤
- 第一步:pom.xml
- 第1步:删除redis相关的依赖
- 第二步:application.properties
- 第1步:删除redis相关配置
- 第三步:编码
- 第1步:删除redis配置类:RedisConfig.java
- 第2步:jwt token的配置类:JwtTokenStoreConfig.java
- 第1步:注入(new)JwtTokenStore的bean到spring容器中。
- 第2步:注入access token到jwt token的转换器到spring容器中。
- 第3步:改造授权服务器配置类
- 第1步:删除redis存储token的代码
- 第2步:注入jwtTokenStore
- 第3步:注入access token到jwt token的转换器
- 第四步:验证
- 第1步:重启项目
- 第2步:postman发送post请求获取jwt token(令牌)
- 第3步:postman根据jwt token发送get请求获取用户资源
- 第一步:pom.xml
- 前情:
- 扩展JWT中存储的内容
- 前情
- 上面完成springsecurity oathu2 密码模式整合jwt。
- 拉下来,实现往jwt中添加自定义的内容。
- 注意:有区别于jjwt中使用.claim("logo","yjxxt.jpg");添加自定义内容的方式
- 详细步骤
- 第一步:创建jwt内容增强器类
- 第1步:添加增强信息:info.put("enhance","enhance info");
- 第二步:改造jwt配置类
- 第1步:注入jwt内容增强器类到spring容器中
- 第三步:改造授权服务器配置类
- 第1步:注入token内容增强器bean:private JwtTokenEnhancer jwtTokenEnhancer;
- 第2步:声明token内容增强器链,并放入token内容增强器、access token到jwt token转换器
- 第3步:配置token内容增强器链:.tokenEnhancer(enhancerChain);
- 第四步:验证
- 第1步:重启项目
- 第2步:postman获取令牌
- 第3步:去官网解析令牌,看到增强信息
- 第一步:创建jwt内容增强器类
- 前情
- Java中解析JWT中的内容
- 概述
- 目标:springsecurity oathu2 密码模式整合jwt项目中,解析jwt token。因为springsecurity oathu2 密码模式整合jwt项目只能把access token转换为jwt token,但是并不能解析。
- 这个解析本质上还是得用jjwt来完成
- 详细步骤
- 第一步:pom.xml
- 第1步:添加jjwt的依赖
- 第二步:改造获取用户资源的XxxController.java类
- 第1步:获取到请求头
- 第2步:获取请求头中的jwt token
- 第三步:验证
- 第1步:重启项目
- 第2步:postman发送post请求获取jwt token
- 第3步:postman中的请求头中加入jwt token,postman发送获取用户资源的get请求。
- 第4步:效果:解析出很多东西。
- 第一步:pom.xml
- 概述
- 刷新令牌
- 概述
- 为什么要有刷新令牌呢?因为令牌本身是无状态的,所以服务端是无法立即去控制令牌是否有效的,只能通过失效时间去控制。
- 如果令牌失效的话,要重新走一遍获取令牌的流程,很麻烦。为了简化一些步骤,快速得到新的令牌,我们直接通过刷新令牌去获取。
- 详细步骤
- 第一步:改造授权配置类
- 第1步:在授权类型中,加入一个"refresh_token"
- 第二步:验证
- 第1步:重启项目
- 第2步:postman发送post请求获取令牌,同时得到一个刷新令牌
- 第3步:postman根据刷新令牌获取新的令牌、新的刷新令牌
- 第4步:postman发送get请求成功获取用户资源
- 第一步:改造授权配置类
- 注意事项
- 令牌和刷新令牌都可以设置失效时间
- 概述
六 sso
- 单点登录sso:登录了百度新闻以后,在百度贴吧、百度视频、百度地图的页面,分别刷新一下浏览器就会自动(默认)登录百度贴吧、百度视频、百度地图。
- 原理图:
- 工作流程
- 第0步:用户在浏览器输入用户名和密码,想登录到应用系统1
- 第1步:登录信息发送到第三方认证系统,校验
- 第2步:第三方认证系统给浏览器返回1个ticket
- 第3步:浏览器携带此ticket去访问应用系统2
- 第a步:应用系统2内部会重定向(+ticket)到第三方认证系统
- 第4步:第三方认证系统对ticket进行校验。
- 如果校验通过,就认为是已经登录过的用户,就可以直接登录,不用再重新登录了。效果是:刷新浏览器,就默认给我们登录成功。
- 第5步:浏览器携带此ticket去访问应用系统3
- 第a步:应用系统3内部会重定向(+ticket)到第三方认证系统
- 第6步:第三方认证系统对ticket进行校验。
- 如果校验通过,就认为是已经登录过的用户,就可以直接登录,不用再重新登录了。效果是:刷新浏览器,就默认给我们登录成功。
- 关系
- 第三方认证系统,是独立于所有的应用系统的。
- 第三方认证系统的2个功能:
- 第1个:登录信息的校验,并且返回ticket
- 第2个:校验ticket是否合法
- 上面所说的ticket,就是前面提到的token(如access token,jwt token)。
- 工作流程
七 springsecurity oathu2整合sso(单点登录)
- 概述
- 单点登录要求首先要有一个认证系统,这里使用8080:springsecurity oathu2项目做第三方认证系统。为什么呢?因为在项目springsecurity oathu2中,之前有配置1个认证(授权)服务器的配置。
- 那接下来呢,我们要去准备客户端,我们唯一要准备的也就是客户端了,其它的基本上不需要去准备。
- 上面单点登录的原理图中,有好多个应用系统123,每一个应用系统对于第三方认证系统而言都是一个客户端。但在这里就准备了一个应用系统1,因为它们的配置基本上是一样的。
- 详细步骤
- 8081:应用系统 + 用户资源
- 第一步:创建springboot项目
- 第二步:pom.xml,依赖
- springboot、spring web、spring test
- spring cloud、spring cloud springsecurity、spring cloud spring security oathu2
- jwt
- 第三步:application.properties
server.port = 8081#如果有多个客户端的情况下,每个客户端的cookie的名称要不一致
#防止 Cookie 冲突,冲突会导致登录验证不通过server.servlet.session.cookie.name = OAUTH2-CLIENT-SESSIONID01# 授权服务器地址oauth2-server-url : http : //localhost : 8080# 与授权服务器对应的配置security.oauth2.client.client-id = adminsecurity.oauth2.client.client-secret = 112233security.oauth2.client.user-authorization-uri = ${oauth2-serverurl}/oauth/authorizesecurity.oauth2.client.access-token-uri = ${oauth2-server-url}/oauth/tokensecurity.oauth2.resource.jwt.key-uri = ${oauth2-server-url}/oauth/token_key - 第四步:准备“用户资源”
- 概述
- 为什么要准备"用户资源" 呢?因为我们要演示的是单点登录。什么叫单点登录呢?我们可以这样,为了去8081上获取用户资源。首先,得到8080上去进行授认证,认证成功再返回8081获取用户资源。
- 详细步骤
-
package com.yjxxt.controller; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { @GetMapping("/getCurrentUser") public Object getCurrentUser(Authentication authentication) { return authentication; } }
-
- 概述
- 第五步:启动类加上注解:@EnableOAuth2Sso
- 8080:springsecurity oathu2项目(即授权服务器)
- 改造授权服务器配置类:AuthorizationServerConfig.java
- 第一步:重定向到客户端应用程序:.redirectUris("http://localhost:8081/login")
- 区别:之前在获取授权码时,重定向到www.baidu.com,.redirectUris("www.baidu.com")
- 第二步:自动授权:.autoApprove(true),即忽略授权网页,自动授权
- 区别:之前是我们的第三方登录都会有一个页面,然后你点击”授权“按钮后,才是真正的确认授权。但在实际项目中,我们想不用点击任何按钮就能授权。
- 第三步:获取密钥需要身份认证,使用单点登录时必须配置:
@Override public void configure(AuthorizationServerSecurityConfigurer security) { // 获取密钥需要身份认证,使用单点登录时必须配置 security.tokenKeyAccess("isAuthenticated()"); }
- 第一步:重定向到客户端应用程序:.redirectUris("http://localhost:8081/login")
- 改造授权服务器配置类:AuthorizationServerConfig.java
- 验证:
- 第一步:重新启动授权服务器8080
- 第二步:重新启动应用系统8081
- 第三步:浏览器地址栏访问8081的用户资源接口:localhost:8081/user/getCurrentUser
- 第四步:自动跳转到的8080授权服务器去登录:http://localhost:8080/login
- 第a步:输入用户名和密码,点击登录按钮
- 第五步:自动跳回8081获取用户资源接口的信息:localhost:8081/user/getCurrentUser
- 注意:这样8080和8081来回跳,是不是就完成了跨域。
- 8081:应用系统 + 用户资源
- 扩展:配置多个单点登录应用系统123
- 第一步:多个应用系统就照着上面的8081配置,有几个重点在这里重复说一下
- 第1步:启动类加上注解:@EnableOAuth2Sso
- 第2步:配置文件:application.properties
- 改一下下面3个配置,让它们唯一,这样你就是在配置了一个新的应用系统(客户端)了。:
server.servlet.session.cookie.name=OAUTH2-CLIENT-SESSIONID01 security.oauth2.client.client-id=admin security.oauth2.client.client-secret=112233
- 改一下下面3个配置,让它们唯一,这样你就是在配置了一个新的应用系统(客户端)了。:
- 第二步:授权服务器
- 第1步:自动授权
- 第2步:重定向地址
- 第3步:配置多个应用系统:
- 第4步:必须要配置的,获取密钥需要身份认证,使用单点登录时必须配置:
@Override public void configure(AuthorizationServerSecurityConfigurer security) { // 获取密钥需要身份认证,使用单点登录时必须配置 security.tokenKeyAccess("isAuthenticated()"); }
- 第一步:多个应用系统就照着上面的8081配置,有几个重点在这里重复说一下